15153720fSfkaag71<?php 25153720fSfkaag71/** 35153720fSfkaag71 * DokuWiki Plugin strata (Helper Component) 45153720fSfkaag71 * 55153720fSfkaag71 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 65153720fSfkaag71 * @author Brend Wanders <b.wanders@utwente.nl> 75153720fSfkaag71 */ 85153720fSfkaag71 95153720fSfkaag71// must be run within Dokuwiki 105153720fSfkaag71if (!defined('DOKU_INC')) die('Meh.'); 115153720fSfkaag71 125153720fSfkaag71/** 135153720fSfkaag71 * The triples helper is responsible for querying. 145153720fSfkaag71 */ 155153720fSfkaag71class helper_plugin_strata_triples extends DokuWiki_Plugin { 165153720fSfkaag71 public static $readable = 'data'; 175153720fSfkaag71 public static $writable = 'data'; 185153720fSfkaag71 195153720fSfkaag71 function __construct() { 205153720fSfkaag71 $this->_initialize(); 215153720fSfkaag71 } 225153720fSfkaag71 235153720fSfkaag71 function getMethods() { 245153720fSfkaag71 $result = array(); 255153720fSfkaag71 return $result; 265153720fSfkaag71 } 275153720fSfkaag71 285153720fSfkaag71 /** 295153720fSfkaag71 * Expands tokens in the DSN. 305153720fSfkaag71 * 315153720fSfkaag71 * @param str string the string to process 325153720fSfkaag71 * @return a string with replaced tokens 335153720fSfkaag71 */ 345153720fSfkaag71 function _expandTokens($str) { 355153720fSfkaag71 global $conf; 365153720fSfkaag71 $tokens = array('@METADIR@'); 375153720fSfkaag71 $replacers = array($conf['metadir']); 385153720fSfkaag71 return str_replace($tokens,$replacers,$str); 395153720fSfkaag71 } 405153720fSfkaag71 415153720fSfkaag71 /** 425153720fSfkaag71 * Initializes the triple helper. 435153720fSfkaag71 * 445153720fSfkaag71 * @param dsn string an optional alternative DSN 455153720fSfkaag71 * @return true if initialization succeeded, false otherwise 465153720fSfkaag71 */ 475153720fSfkaag71 function _initialize() { 485153720fSfkaag71 // load default DSN 495153720fSfkaag71 $dsn = $this->getConf('default_dsn'); 505153720fSfkaag71 $dsn = $this->_expandTokens($dsn); 515153720fSfkaag71 525153720fSfkaag71 $this->_dsn = $dsn; 535153720fSfkaag71 545153720fSfkaag71 // construct driver 555153720fSfkaag71 list($driver,$connection) = explode(':',$dsn,2); 565153720fSfkaag71 $driverFile = DOKU_PLUGIN."strata/driver/$driver.php"; 575153720fSfkaag71 if(!@file_exists($driverFile)) { 585153720fSfkaag71 msg(sprintf($this->getLang('error_triples_nodriver'), $driver), -1); 595153720fSfkaag71 return false; 605153720fSfkaag71 } 615153720fSfkaag71 require_once($driverFile); 625153720fSfkaag71 $driverClass = "plugin_strata_driver_$driver"; 635153720fSfkaag71 $this->_db = new $driverClass($this->getConf('debug')); 645153720fSfkaag71 655153720fSfkaag71 // connect driver 665153720fSfkaag71 if(!$this->_db->connect($dsn)) { 675153720fSfkaag71 return false; 685153720fSfkaag71 } 695153720fSfkaag71 705153720fSfkaag71 // initialize database if necessary 715153720fSfkaag71 if(!$this->_db->isInitialized()) { 725153720fSfkaag71 $this->_db->initializeDatabase(); 735153720fSfkaag71 } 745153720fSfkaag71 755153720fSfkaag71 765153720fSfkaag71 return true; 775153720fSfkaag71 } 785153720fSfkaag71 795153720fSfkaag71 /** 805153720fSfkaag71 * Makes the an SQL expression case insensitive. 815153720fSfkaag71 * 825153720fSfkaag71 * @param a string the expression to process 835153720fSfkaag71 * @return a SQL expression 845153720fSfkaag71 */ 855153720fSfkaag71 function _ci($a) { 865153720fSfkaag71 return $this->_db->ci($a); 875153720fSfkaag71 } 885153720fSfkaag71 895153720fSfkaag71 /** 905153720fSfkaag71 * Constructs a case insensitive string comparison in SQL. 915153720fSfkaag71 * 925153720fSfkaag71 * @param a string the left-hand side 935153720fSfkaag71 * @param b string the right-hand side 945153720fSfkaag71 * 955153720fSfkaag71 * @return a case insensitive SQL string comparison 965153720fSfkaag71 */ 975153720fSfkaag71 function _cic($a, $b) { 985153720fSfkaag71 return $this->_ci($a).' = '.$this->_ci($b); 995153720fSfkaag71 } 1005153720fSfkaag71 1015153720fSfkaag71 /** 1025153720fSfkaag71 * Begins a preview. 1035153720fSfkaag71 */ 1045153720fSfkaag71 function beginPreview() { 1055153720fSfkaag71 $this->_db->beginTransaction(); 1065153720fSfkaag71 } 1075153720fSfkaag71 1085153720fSfkaag71 /** 1095153720fSfkaag71 * Ends a preview. 1105153720fSfkaag71 */ 1115153720fSfkaag71 function endPreview() { 1125153720fSfkaag71 $this->_db->rollback(); 1135153720fSfkaag71 } 1145153720fSfkaag71 1155153720fSfkaag71 /** 1165153720fSfkaag71 * Removes all triples matching the given triple pattern. One or more parameters 1175153720fSfkaag71 * can be left out to indicate 'any'. 1185153720fSfkaag71 */ 1195153720fSfkaag71 function removeTriples($subject=null, $predicate=null, $object=null, $graph=null) { 1205153720fSfkaag71 // construct triple filter 1215153720fSfkaag71 $filters = array('1 = 1'); 1225153720fSfkaag71 foreach(array('subject','predicate','object','graph') as $param) { 1235153720fSfkaag71 if($$param != null) { 1245153720fSfkaag71 $filters[]=$this->_cic($param, '?'); 1255153720fSfkaag71 $values[] = $$param; 1265153720fSfkaag71 } 1275153720fSfkaag71 } 1285153720fSfkaag71 129*0847ebd2SFKaag $sql = "DELETE FROM ".self::$writable." WHERE ". implode(" AND ", $filters); 1305153720fSfkaag71 1315153720fSfkaag71 // prepare query 1325153720fSfkaag71 $query = $this->_db->prepare($sql); 1335153720fSfkaag71 if($query == false) return; 1345153720fSfkaag71 1355153720fSfkaag71 // execute query 1365153720fSfkaag71 $res = $query->execute($values); 1375153720fSfkaag71 if($res === false) { 1385153720fSfkaag71 $error = $query->errorInfo(); 1395153720fSfkaag71 msg(sprintf($this->getLang('error_triples_remove'),hsc($error[2])),-1); 1405153720fSfkaag71 } 1415153720fSfkaag71 1425153720fSfkaag71 $query->closeCursor(); 1435153720fSfkaag71 } 1445153720fSfkaag71 1455153720fSfkaag71 /** 1465153720fSfkaag71 * Fetches all triples matching the given triple pattern. Onr or more of 1475153720fSfkaag71 * parameters can be left out to indicate 'any'. 1485153720fSfkaag71 */ 1495153720fSfkaag71 function fetchTriples($subject=null, $predicate=null, $object=null, $graph=null) { 1505153720fSfkaag71 global $ID; 1515153720fSfkaag71 // construct filter 152*0847ebd2SFKaag 1535153720fSfkaag71 $filters = array('1 = 1'); 1545153720fSfkaag71 foreach(array('subject','predicate','object','graph') as $param) { 1555153720fSfkaag71 if($$param != null) { 1565153720fSfkaag71 $filters[]=$this->_cic($param,'?'); 1575153720fSfkaag71 $values[] = $$param; 1585153720fSfkaag71 } 1595153720fSfkaag71 } 160*0847ebd2SFKaag 1615153720fSfkaag71 $scopeRestriction = ($this->getConf('scoped')? ' AND graph like "'.getNS($ID).'%"':"" ); 1625153720fSfkaag71 163*0847ebd2SFKaag $sql = "SELECT subject, predicate, object, graph FROM ".self::$readable." WHERE ". implode(" AND ", $filters).$scopeRestriction; 1645153720fSfkaag71 1655153720fSfkaag71 // prepare queyr 1665153720fSfkaag71 $query = $this->_db->prepare($sql); 1675153720fSfkaag71 if($query == false) return; 1685153720fSfkaag71 1695153720fSfkaag71 // execute query 1705153720fSfkaag71 $res = $query->execute($values); 171*0847ebd2SFKaag 1725153720fSfkaag71 if($res === false) { 1735153720fSfkaag71 $error = $query->errorInfo(); 1745153720fSfkaag71 msg(sprintf($this->getLang('error_triples_fetch'),hsc($error[2])),-1); 1755153720fSfkaag71 } 1765153720fSfkaag71 1775153720fSfkaag71 // fetch results and return them 1785153720fSfkaag71 $result = $query->fetchAll(PDO::FETCH_ASSOC); 1795153720fSfkaag71 $query->closeCursor(); 1805153720fSfkaag71 1815153720fSfkaag71 return $result; 1825153720fSfkaag71 } 1835153720fSfkaag71 1845153720fSfkaag71 /** 1855153720fSfkaag71 * Adds a single triple. 1865153720fSfkaag71 * @param subject string 1875153720fSfkaag71 * @param predicate string 1885153720fSfkaag71 * @param object string 1895153720fSfkaag71 * @param graph string 1905153720fSfkaag71 * @return true of triple was added succesfully, false if not 1915153720fSfkaag71 */ 1925153720fSfkaag71 function addTriple($subject, $predicate, $object, $graph) { 1935153720fSfkaag71 return $this->addTriples(array(array('subject'=>$subject, 'predicate'=>$predicate, 'object'=>$object)), $graph); 1945153720fSfkaag71 } 1955153720fSfkaag71 1965153720fSfkaag71 /** 1975153720fSfkaag71 * Adds multiple triples. 1985153720fSfkaag71 * @param triples array contains all triples as arrays with subject, predicate and object keys 1995153720fSfkaag71 * @param graph string graph name 2005153720fSfkaag71 * @return true if the triples were comitted, false otherwise 2015153720fSfkaag71 */ 2025153720fSfkaag71 function addTriples($triples, $graph) { 2035153720fSfkaag71 // prepare insertion query 2045153720fSfkaag71 $sql = "INSERT INTO ".self::$writable."(subject, predicate, object, graph) VALUES(?, ?, ?, ?)"; 2055153720fSfkaag71 $query = $this->_db->prepare($sql); 2065153720fSfkaag71 if($query == false) return false; 2075153720fSfkaag71 2085153720fSfkaag71 // put the batch in a transaction 2095153720fSfkaag71 $this->_db->beginTransaction(); 2105153720fSfkaag71 foreach($triples as $t) { 2115153720fSfkaag71 // insert a single triple 2125153720fSfkaag71 $values = array($t['subject'],$t['predicate'],$t['object'],$graph); 2135153720fSfkaag71 $res = $query->execute($values); 2145153720fSfkaag71 2155153720fSfkaag71 // handle errors 2165153720fSfkaag71 if($res === false) { 2175153720fSfkaag71 $error = $query->errorInfo(); 2185153720fSfkaag71 msg(sprintf($this->getLang('error_triples_add'),hsc($error[2])),-1); 2195153720fSfkaag71 $this->_db->rollBack(); 2205153720fSfkaag71 return false; 2215153720fSfkaag71 } 2225153720fSfkaag71 $query->closeCursor(); 2235153720fSfkaag71 } 2245153720fSfkaag71 2255153720fSfkaag71 // commit and return 2265153720fSfkaag71 return $this->_db->commit(); 2275153720fSfkaag71 } 2285153720fSfkaag71 2295153720fSfkaag71 /** 2305153720fSfkaag71 * Executes the given abstract query tree as a query on the store. 2315153720fSfkaag71 * 2325153720fSfkaag71 * @param query array an abstract query tree 2335153720fSfkaag71 * @return an iterator over the resulting rows 2345153720fSfkaag71 */ 2355153720fSfkaag71 function queryRelations($queryTree) { 2365153720fSfkaag71 // create the SQL generator, and generate the SQL query 2375153720fSfkaag71 $generator = new strata_sql_generator($this); 2385153720fSfkaag71 list($sql, $literals, $projected, $grouped) = $generator->translate($queryTree); 2395153720fSfkaag71 2405153720fSfkaag71 // prepare the query 2415153720fSfkaag71 $query = $this->_db->prepare($sql); 2425153720fSfkaag71 if($query === false) { 2435153720fSfkaag71 return false; 2445153720fSfkaag71 } 2455153720fSfkaag71 2465153720fSfkaag71 // execute the query 2475153720fSfkaag71 $res = $query->execute($literals); 2485153720fSfkaag71 if($res === false) { 2495153720fSfkaag71 $error = $query->errorInfo(); 2505153720fSfkaag71 msg(sprintf($this->getLang('error_triples_query'),hsc($error[2])),-1); 2515153720fSfkaag71 if($this->getConf('debug')) { 2525153720fSfkaag71 msg(sprintf($this->getLang('debug_sql'),hsc($sql)),-1); 2535153720fSfkaag71 msg(sprintf($this->getLang('debug_literals'), hsc(print_r($literals,1))),-1); 2545153720fSfkaag71 } 2555153720fSfkaag71 return false; 2565153720fSfkaag71 } 2575153720fSfkaag71 2585153720fSfkaag71 // wrap the results in an iterator, and return it 2595153720fSfkaag71 if($queryTree['grouping'] === false) { 2605153720fSfkaag71 return new strata_relations_iterator($query, $projected); 2615153720fSfkaag71 } else { 2625153720fSfkaag71 return new strata_aggregating_iterator($query, $projected, $grouped); 2635153720fSfkaag71 } 2645153720fSfkaag71 } 2655153720fSfkaag71 2665153720fSfkaag71 /** 2675153720fSfkaag71 * Executes the abstract query tree, and returns all properties of the matching subjects. 2685153720fSfkaag71 * This method assumes that the root is a 'select' node. 2695153720fSfkaag71 * 2705153720fSfkaag71 * @param query array the abstract query tree 2715153720fSfkaag71 * @return an iterator over the resources 2725153720fSfkaag71 */ 2735153720fSfkaag71 function queryResources($query) { 2745153720fSfkaag71 // We transform the given query into a resource-centric query as follows: 2755153720fSfkaag71 // Remember the single projected variable Vx. 2765153720fSfkaag71 // Append two variables ?__p and ?__o to the projection 2775153720fSfkaag71 // Add an extra triple pattern (Vx, ?__p, ?__o) 2785153720fSfkaag71 // Append Vx to the ordering 2795153720fSfkaag71 // The query is ready for execution. Result set can be transformed into a 2805153720fSfkaag71 // resource-centric view by fetching all triples related to a single subject 2815153720fSfkaag71 // (each subject is in a single continuous block, due to the ordering) 2825153720fSfkaag71 2835153720fSfkaag71 // add extra tuple 2845153720fSfkaag71 $query['group'] = array( 2855153720fSfkaag71 'type'=>'and', 2865153720fSfkaag71 'lhs'=>$query['group'], 2875153720fSfkaag71 'rhs'=>array( 2885153720fSfkaag71 'type'=>'triple', 2895153720fSfkaag71 'subject'=>array('type'=>'variable','text'=>$query['projection'][0]), 2905153720fSfkaag71 'predicate'=>array('type'=>'variable','text'=>'__predicate'), 2915153720fSfkaag71 'object'=>array('type'=>'variable','text'=>'__object') 2925153720fSfkaag71 ) 2935153720fSfkaag71 ); 2945153720fSfkaag71 2955153720fSfkaag71 // fix projection list 2965153720fSfkaag71 $query['projection'] = array( 2975153720fSfkaag71 $query['projection'][0], 2985153720fSfkaag71 '__predicate', 2995153720fSfkaag71 '__object' 3005153720fSfkaag71 ); 3015153720fSfkaag71 3025153720fSfkaag71 // append tuple ordering 3035153720fSfkaag71 $query['ordering'][] = array( 3045153720fSfkaag71 'variable'=>$query['projection'][0], 3055153720fSfkaag71 'direction'=>'asc' 3065153720fSfkaag71 ); 3075153720fSfkaag71 3085153720fSfkaag71 // remove grouping 3095153720fSfkaag71 $query['grouping'] = false; 3105153720fSfkaag71 3115153720fSfkaag71 // execute query 3125153720fSfkaag71 $result = $this->queryRelations($query); 3135153720fSfkaag71 3145153720fSfkaag71 if($result === false) { 3155153720fSfkaag71 return false; 3165153720fSfkaag71 } 3175153720fSfkaag71 3185153720fSfkaag71 // invoke iterator that's going to aggregate the resulting relations 3195153720fSfkaag71 return new strata_resource_iterator($result,$query['projection']); 3205153720fSfkaag71 } 3215153720fSfkaag71} 3225153720fSfkaag71 3235153720fSfkaag71/** 3245153720fSfkaag71 * SQL generator. 3255153720fSfkaag71 */ 3265153720fSfkaag71class strata_sql_generator { 3275153720fSfkaag71 /** 3285153720fSfkaag71 * Stores all literal values keyed to their placeholder. 3295153720fSfkaag71 */ 3305153720fSfkaag71 private $literals = array(); 3315153720fSfkaag71 3325153720fSfkaag71 /** 3335153720fSfkaag71 * Stores all projected variables. 3345153720fSfkaag71 */ 3355153720fSfkaag71 private $projected = array(); 3365153720fSfkaag71 3375153720fSfkaag71 /** 3385153720fSfkaag71 * Stores all grouped variables. 3395153720fSfkaag71 */ 3405153720fSfkaag71 private $grouped = array(); 3415153720fSfkaag71 3425153720fSfkaag71 /** 3435153720fSfkaag71 * Constructor. 3445153720fSfkaag71 */ 3455153720fSfkaag71 function __construct($triples) { 3465153720fSfkaag71 $this->_triples = $triples; 3475153720fSfkaag71 $this->_db = $this->_triples->_db; 3485153720fSfkaag71 } 3495153720fSfkaag71 3505153720fSfkaag71 /** 3515153720fSfkaag71 * Passes through localisation calls. 3525153720fSfkaag71 */ 3535153720fSfkaag71 function getLang($key) { 3545153720fSfkaag71 return $this->_triples->getLang($key); 3555153720fSfkaag71 } 3565153720fSfkaag71 3575153720fSfkaag71 /** 3585153720fSfkaag71 * Wrap SQL expression in case insensitivisation. 3595153720fSfkaag71 */ 3605153720fSfkaag71 function _ci($a) { 3615153720fSfkaag71 return $this->_triples->_ci($a); 3625153720fSfkaag71 } 3635153720fSfkaag71 3645153720fSfkaag71 /** 3655153720fSfkaag71 * Alias generator. 3665153720fSfkaag71 */ 3675153720fSfkaag71 private $_aliasCounter = 0; 3685153720fSfkaag71 function _alias($prefix='a') { 3695153720fSfkaag71 return $prefix.($this->_aliasCounter++); 3705153720fSfkaag71 } 3715153720fSfkaag71 3725153720fSfkaag71 /** 3735153720fSfkaag71 * All used literals. 3745153720fSfkaag71 */ 3755153720fSfkaag71 private $_literalLookup = array(); 3765153720fSfkaag71 private $_variableLookup = array(); 3775153720fSfkaag71 3785153720fSfkaag71 /** 3795153720fSfkaag71 * Name generator. Makes the distinction between literals 3805153720fSfkaag71 * and variables, as they can have the same spelling (and 3815153720fSfkaag71 * frequently do in simple queries). 3825153720fSfkaag71 */ 3835153720fSfkaag71 function _name($term) { 3845153720fSfkaag71 if($term['type'] == 'variable') { 3855153720fSfkaag71 // always try the cache 3865153720fSfkaag71 if(empty($this->_variableLookup[$term['text']])) { 3875153720fSfkaag71 $this->_variableLookup[$term['text']] = $this->_alias('v'); 3885153720fSfkaag71 } 3895153720fSfkaag71 3905153720fSfkaag71 return $this->_variableLookup[$term['text']]; 3915153720fSfkaag71 } elseif($term['type'] == 'literal') { 3925153720fSfkaag71 // always try the cache 3935153720fSfkaag71 if(empty($this->_literalLookup[$term['text']])) { 3945153720fSfkaag71 // use aliases to represent literals 3955153720fSfkaag71 $this->_literalLookup[$term['text']] = $this->_alias('lit'); 3965153720fSfkaag71 } 3975153720fSfkaag71 3985153720fSfkaag71 // return literal name 3995153720fSfkaag71 return $this->_literalLookup[$term['text']]; 4005153720fSfkaag71 } 4015153720fSfkaag71 } 4025153720fSfkaag71 4035153720fSfkaag71 /** 4045153720fSfkaag71 * Test whether two things are equal (i.e., equal variables, or equal literals) 4055153720fSfkaag71 */ 4065153720fSfkaag71 function _patternEquals($pa, $pb) { 4075153720fSfkaag71 return $pa['type'] == $pb['type'] && $pa['text'] == $pb['text']; 4085153720fSfkaag71 } 4095153720fSfkaag71 4105153720fSfkaag71 /** 4115153720fSfkaag71 * Generates a conditional for the given triple pattern. 4125153720fSfkaag71 */ 4135153720fSfkaag71 function _genCond($tp) { 4145153720fSfkaag71 $conditions = array(); 4155153720fSfkaag71 4165153720fSfkaag71 // the subject is a literal 4175153720fSfkaag71 if($tp['subject']['type'] == 'literal') { 4185153720fSfkaag71 $id = $this->_alias('qv'); 4195153720fSfkaag71 $conditions[] = $this->_ci('subject').' = '.$this->_ci(':'.$id); 4205153720fSfkaag71 $this->literals[$id] = $tp['subject']['text']; 4215153720fSfkaag71 } 4225153720fSfkaag71 4235153720fSfkaag71 // the predicate is a literal 4245153720fSfkaag71 if($tp['predicate']['type'] == 'literal') { 4255153720fSfkaag71 $id = $this->_alias('qv'); 4265153720fSfkaag71 $conditions[] = $this->_ci('predicate').' = '.$this->_ci(':'.$id); 4275153720fSfkaag71 $this->literals[$id] = $tp['predicate']['text']; 4285153720fSfkaag71 } 4295153720fSfkaag71 4305153720fSfkaag71 // the object is a literal 4315153720fSfkaag71 if($tp['object']['type'] == 'literal') { 4325153720fSfkaag71 $id = $this->_alias('qv'); 4335153720fSfkaag71 $conditions[] = $this->_ci('object').' = '.$this->_ci(':'.$id); 4345153720fSfkaag71 $this->literals[$id] = $tp['object']['text']; 4355153720fSfkaag71 } 4365153720fSfkaag71 4375153720fSfkaag71 // subject equals predicate 4385153720fSfkaag71 if($this->_patternEquals($tp['subject'],$tp['predicate'])) { 4395153720fSfkaag71 $conditions[] = $this->_ci('subject').' = '.$this->_ci('predicate'); 4405153720fSfkaag71 } 4415153720fSfkaag71 4425153720fSfkaag71 // subject equals object 4435153720fSfkaag71 if($this->_patternEquals($tp['subject'],$tp['object'])) { 4445153720fSfkaag71 $conditions[] = $this->_ci('subject').' = '.$this->_ci('object'); 4455153720fSfkaag71 } 4465153720fSfkaag71 4475153720fSfkaag71 // predicate equals object 4485153720fSfkaag71 if($this->_patternEquals($tp['predicate'],$tp['object'])) { 4495153720fSfkaag71 $conditions[] = $this->_ci('predicate').' = '.$this->_ci('object'); 4505153720fSfkaag71 } 4515153720fSfkaag71 4525153720fSfkaag71 if(count($conditions)!=0) { 4535153720fSfkaag71 return implode(' AND ',$conditions); 4545153720fSfkaag71 } else { 4555153720fSfkaag71 return '1 = 1'; 4565153720fSfkaag71 } 4575153720fSfkaag71 } 4585153720fSfkaag71 4595153720fSfkaag71 /** 4605153720fSfkaag71 * Generates a projection for the given triple pattern. 4615153720fSfkaag71 */ 4625153720fSfkaag71 function _genPR($tp) { 4635153720fSfkaag71 $list = array(); 4645153720fSfkaag71 4655153720fSfkaag71 // always project the subject 4665153720fSfkaag71 $list[] = 'subject AS '.$this->_name($tp['subject']); 4675153720fSfkaag71 4685153720fSfkaag71 // project the predicate if it's different from the subject 4695153720fSfkaag71 if(!$this->_patternEquals($tp['subject'], $tp['predicate'])) { 4705153720fSfkaag71 $list[] = 'predicate AS '.$this->_name($tp['predicate']); 4715153720fSfkaag71 } 4725153720fSfkaag71 4735153720fSfkaag71 // project the object if it's different from the subject and different from the predicate 4745153720fSfkaag71 if(!$this->_patternEquals($tp['subject'], $tp['object']) && !$this->_patternEquals($tp['predicate'],$tp['object'])) { 4755153720fSfkaag71 $list[] = 'object AS '.$this->_name($tp['object']); 4765153720fSfkaag71 } 4775153720fSfkaag71 4785153720fSfkaag71 return implode(', ',$list); 4795153720fSfkaag71 } 4805153720fSfkaag71 4815153720fSfkaag71 /** 4825153720fSfkaag71 * Translates a triple pattern into a graph pattern. 4835153720fSfkaag71 */ 4845153720fSfkaag71 function _trans_tp($tp) { 4855153720fSfkaag71 global $ID; 4865153720fSfkaag71 $terms = array(); 4875153720fSfkaag71 4885153720fSfkaag71 // the subject is a variable 4895153720fSfkaag71 if($tp['subject']['type'] == 'variable') { 4905153720fSfkaag71 $terms[] = $this->_name($tp['subject']); 4915153720fSfkaag71 } 4925153720fSfkaag71 4935153720fSfkaag71 // the predicate is a variable 4945153720fSfkaag71 if($tp['predicate']['type'] == 'variable') { 4955153720fSfkaag71 $terms[] = $this->_name($tp['predicate']); 4965153720fSfkaag71 } 4975153720fSfkaag71 4985153720fSfkaag71 // the object is a variable 4995153720fSfkaag71 if($tp['object']['type'] == 'variable') { 5005153720fSfkaag71 $terms[] = $this->_name($tp['object']); 5015153720fSfkaag71 } 5025153720fSfkaag71 5035153720fSfkaag71 $scopeRestriction = ($this->_triples->getConf('scoped')? ' AND graph like "'.getNS($ID).'%"':"" ); 5045153720fSfkaag71 return array( 5055153720fSfkaag71 'sql'=>'SELECT '.$this->_genPR($tp).' FROM '.helper_plugin_strata_triples::$readable.' WHERE '.$this->_genCond($tp).$scopeRestriction, 5065153720fSfkaag71 'terms'=>array_unique($terms) 5075153720fSfkaag71 ); 5085153720fSfkaag71 } 5095153720fSfkaag71 5105153720fSfkaag71 /** 5115153720fSfkaag71 * Translates a group operation on the two graph patterns. 5125153720fSfkaag71 */ 5135153720fSfkaag71 function _trans_group($gp1, $gp2, $join) { 5145153720fSfkaag71 // determine the resulting terms 5155153720fSfkaag71 $terms = array_unique(array_merge($gp1['terms'], $gp2['terms'])); 5165153720fSfkaag71 5175153720fSfkaag71 // determine the overlapping terms (we need to coalesce these) 5185153720fSfkaag71 $common = array_intersect($gp1['terms'], $gp2['terms']); 5195153720fSfkaag71 5205153720fSfkaag71 // determine the non-overlapping terms (we can project them directly) 5215153720fSfkaag71 $fields = array_diff($terms, $common); 5225153720fSfkaag71 5235153720fSfkaag71 // handle overlapping terms by coalescing them into a single term 5245153720fSfkaag71 if(count($common)>0) { 5255153720fSfkaag71 $intersect = array(); 5265153720fSfkaag71 foreach($common as $c) { 5275153720fSfkaag71 $intersect[] = '('.$this->_ci('r1.'.$c).' = '.$this->_ci('r2.'.$c).' OR r1.'.$c.' IS NULL OR r2.'.$c.' IS NULL)'; 5285153720fSfkaag71 $fields[]='COALESCE(r1.'.$c.', r2.'.$c.') AS '.$c; 5295153720fSfkaag71 } 5305153720fSfkaag71 $intersect = implode(' AND ',$intersect); 5315153720fSfkaag71 } else { 5325153720fSfkaag71 $intersect = '(1=1)'; 5335153720fSfkaag71 } 5345153720fSfkaag71 5355153720fSfkaag71 $fields = implode(', ',$fields); 5365153720fSfkaag71 5375153720fSfkaag71 return array( 5385153720fSfkaag71 'sql'=>'SELECT DISTINCT '.$fields.' FROM ('.$gp1['sql'].') AS r1 '.$join.' ('.$gp2['sql'].') AS r2 ON '.$intersect, 5395153720fSfkaag71 'terms'=>$terms 5405153720fSfkaag71 ); 5415153720fSfkaag71 } 5425153720fSfkaag71 5435153720fSfkaag71 /** 5445153720fSfkaag71 * Translate an optional operation. 5455153720fSfkaag71 */ 5465153720fSfkaag71 function _trans_opt($query) { 5475153720fSfkaag71 $gp1 = $this->_dispatch($query['lhs']); 5485153720fSfkaag71 $gp2 = $this->_dispatch($query['rhs']); 5495153720fSfkaag71 return $this->_trans_group($gp1, $gp2, 'LEFT OUTER JOIN'); 5505153720fSfkaag71 } 5515153720fSfkaag71 5525153720fSfkaag71 /** 5535153720fSfkaag71 * Translate an and operation. 5545153720fSfkaag71 */ 5555153720fSfkaag71 function _trans_and($query) { 5565153720fSfkaag71 $gp1 = $this->_dispatch($query['lhs']); 5575153720fSfkaag71 $gp2 = $this->_dispatch($query['rhs']); 5585153720fSfkaag71 return $this->_trans_group($gp1, $gp2, 'INNER JOIN'); 5595153720fSfkaag71 } 5605153720fSfkaag71 5615153720fSfkaag71 /** 5625153720fSfkaag71 * Translate a filter operation. The filters are a conjunction of separate expressions. 5635153720fSfkaag71 */ 5645153720fSfkaag71 function _trans_filter($query) { 5655153720fSfkaag71 $gp = $this->_dispatch($query['lhs']); 5665153720fSfkaag71 $fs = $query['rhs']; 5675153720fSfkaag71 5685153720fSfkaag71 $filters = array(); 5695153720fSfkaag71 5705153720fSfkaag71 foreach($fs as $f) { 5715153720fSfkaag71 // determine representation of left-hand side 5725153720fSfkaag71 if($f['lhs']['type'] == 'variable') { 5735153720fSfkaag71 $lhs = $this->_name($f['lhs']); 5745153720fSfkaag71 } else { 5755153720fSfkaag71 $id = $this->_alias('qv'); 5765153720fSfkaag71 $lhs = ':'.$id; 5775153720fSfkaag71 $this->literals[$id] = $f['lhs']['text']; 5785153720fSfkaag71 } 5795153720fSfkaag71 5805153720fSfkaag71 // determine representation of right-hand side 5815153720fSfkaag71 if($f['rhs']['type'] == 'variable') { 5825153720fSfkaag71 $rhs = $this->_name($f['rhs']); 5835153720fSfkaag71 } else { 5845153720fSfkaag71 $id = $this->_alias('qv'); 5855153720fSfkaag71 $rhs = ':'.$id; 5865153720fSfkaag71 $this->literals[$id] = $f['rhs']['text']; 5875153720fSfkaag71 } 5885153720fSfkaag71 5895153720fSfkaag71 // the escaping constants (head, tail and modifier) 5905153720fSfkaag71 $eh= "REPLACE(REPLACE(REPLACE("; 5915153720fSfkaag71 $et= ",'!','!!'),'_','!_'),'%','!%')"; 5925153720fSfkaag71 $em= " ESCAPE '!'"; 5935153720fSfkaag71 5945153720fSfkaag71 // handle different operators 5955153720fSfkaag71 switch($f['operator']) { 5965153720fSfkaag71 case '=': 5975153720fSfkaag71 case '!=': 5985153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' '.$f['operator'].' ' . $this->_ci($rhs). ' )'; 5995153720fSfkaag71 break; 6005153720fSfkaag71 case '>': 6015153720fSfkaag71 case '<': 6025153720fSfkaag71 case '>=': 6035153720fSfkaag71 case '<=': 6045153720fSfkaag71 $filters[] = '( ' . $this->_triples->_db->castToNumber($lhs) . ' ' . $f['operator'] . ' ' . $this->_triples->_db->castToNumber($rhs) . ' )'; 6055153720fSfkaag71 break; 6065153720fSfkaag71 case '~': 6075153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' '.$this->_db->stringCompare().' '. $this->_ci('(\'%\' || ' .$eh.$rhs.$et. ' || \'%\')') .$em. ')'; 6085153720fSfkaag71 break; 6095153720fSfkaag71 case '!~': 6105153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' NOT '.$this->_db->stringCompare().' '. $this->_ci('(\'%\' || ' . $eh.$rhs.$et. ' || \'%\')') .$em. ')'; 6115153720fSfkaag71 break; 6125153720fSfkaag71 case '^~': 6135153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' '.$this->_db->stringCompare().' ' .$this->_ci('('. $eh.$rhs.$et . ' || \'%\')').$em. ')'; 6145153720fSfkaag71 break; 6155153720fSfkaag71 case '!^~': 6165153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' NOT '.$this->_db->stringCompare().' ' .$this->_ci('('. $eh.$rhs.$et . ' || \'%\')').$em. ')'; 6175153720fSfkaag71 break; 6185153720fSfkaag71 case '$~': 6195153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' '.$this->_db->stringCompare().' '.$this->_ci('(\'%\' || ' . $eh.$rhs.$et. ')') .$em. ')'; 6205153720fSfkaag71 break; 6215153720fSfkaag71 case '!$~': 6225153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' NOT '.$this->_db->stringCompare().' '.$this->_ci('(\'%\' || ' . $eh.$rhs.$et. ')') .$em. ')'; 6235153720fSfkaag71 break; 6245153720fSfkaag71 6255153720fSfkaag71 default: 6265153720fSfkaag71 } 6275153720fSfkaag71 } 6285153720fSfkaag71 6295153720fSfkaag71 $filters = implode(' AND ', $filters); 6305153720fSfkaag71 6315153720fSfkaag71 return array( 6325153720fSfkaag71 'sql'=>'SELECT * FROM ('.$gp['sql'].') r WHERE '.$filters, 6335153720fSfkaag71 'terms'=>$gp['terms'] 6345153720fSfkaag71 ); 6355153720fSfkaag71 } 6365153720fSfkaag71 6375153720fSfkaag71 /** 6385153720fSfkaag71 * Translate minus operation. 6395153720fSfkaag71 */ 6405153720fSfkaag71 function _trans_minus($query) { 6415153720fSfkaag71 $gp1 = $this->_dispatch($query['lhs']); 6425153720fSfkaag71 $gp2 = $this->_dispatch($query['rhs']); 6435153720fSfkaag71 6445153720fSfkaag71 // determine overlapping terms (we need to substitute these) 6455153720fSfkaag71 $common = array_intersect($gp1['terms'], $gp2['terms']); 6465153720fSfkaag71 6475153720fSfkaag71 // create conditional that 'substitutes' terms by requiring equality 6485153720fSfkaag71 $terms = array(); 6495153720fSfkaag71 foreach($common as $c) { 6505153720fSfkaag71 $terms[] = '('.$this->_ci('r1.'.$c).' = '.$this->_ci('r2.'.$c).')'; 6515153720fSfkaag71 } 6525153720fSfkaag71 6535153720fSfkaag71 if(count($terms)>0) { 6545153720fSfkaag71 $terms = implode(' AND ',$terms); 6555153720fSfkaag71 } else { 6565153720fSfkaag71 $terms = '1=1'; 6575153720fSfkaag71 } 6585153720fSfkaag71 6595153720fSfkaag71 return array( 6605153720fSfkaag71 'sql'=>'SELECT DISTINCT * FROM ('.$gp1['sql'].') r1 WHERE NOT EXISTS (SELECT * FROM ('.$gp2['sql'].') r2 WHERE '.$terms.')', 6615153720fSfkaag71 'terms'=>$gp1['terms'] 6625153720fSfkaag71 ); 6635153720fSfkaag71 } 6645153720fSfkaag71 6655153720fSfkaag71 /** 6665153720fSfkaag71 * Translate union operation. 6675153720fSfkaag71 */ 6685153720fSfkaag71 function _trans_union($query) { 6695153720fSfkaag71 // dispatch the child graph patterns 6705153720fSfkaag71 $gp1 = $this->_dispatch($query['lhs']); 6715153720fSfkaag71 $gp2 = $this->_dispatch($query['rhs']); 6725153720fSfkaag71 6735153720fSfkaag71 // dispatch them again to get new literal binding aliases 6745153720fSfkaag71 // (This is required by PDO, as no named variable may be used twice) 6755153720fSfkaag71 $gp1x = $this->_dispatch($query['lhs']); 6765153720fSfkaag71 $gp2x = $this->_dispatch($query['rhs']); 6775153720fSfkaag71 6785153720fSfkaag71 // determine non-overlapping terms 6795153720fSfkaag71 $ta = array_diff($gp1['terms'], $gp2['terms']); 6805153720fSfkaag71 $tb = array_diff($gp2['terms'], $gp1['terms']); 6815153720fSfkaag71 6825153720fSfkaag71 // determine overlapping terms 6835153720fSfkaag71 $tc = array_intersect($gp1['terms'], $gp2['terms']); 6845153720fSfkaag71 6855153720fSfkaag71 // determine final terms 6865153720fSfkaag71 $terms = array_unique(array_merge($gp1['terms'], $gp2['terms'])); 6875153720fSfkaag71 6885153720fSfkaag71 // construct selected term list 6895153720fSfkaag71 $sa = array_merge($ta, $tb); 6905153720fSfkaag71 $sb = array_merge($ta, $tb); 6915153720fSfkaag71 6925153720fSfkaag71 // append common terms with renaming 6935153720fSfkaag71 foreach($tc as $c) { 6945153720fSfkaag71 $sa[] = 'r1.'.$c.' AS '.$c; 6955153720fSfkaag71 $sb[] = 'r3.'.$c.' AS '.$c; 6965153720fSfkaag71 } 6975153720fSfkaag71 6985153720fSfkaag71 $sa = implode(', ', $sa); 6995153720fSfkaag71 $sb = implode(', ', $sb); 7005153720fSfkaag71 7015153720fSfkaag71 return array( 7025153720fSfkaag71 'sql'=>'SELECT DISTINCT '.$sa.' FROM ('.$gp1['sql'].') r1 LEFT OUTER JOIN ('.$gp2['sql'].') r2 ON (1=0) UNION SELECT DISTINCT '.$sb.' FROM ('.$gp2x['sql'].') r3 LEFT OUTER JOIN ('.$gp1x['sql'].') r4 ON (1=0)', 7035153720fSfkaag71 'terms'=>$terms 7045153720fSfkaag71 ); 7055153720fSfkaag71 } 7065153720fSfkaag71 7075153720fSfkaag71 /** 7085153720fSfkaag71 * Translate projection and ordering. 7095153720fSfkaag71 */ 7105153720fSfkaag71 function _trans_select($query) { 7115153720fSfkaag71 $gp = $this->_dispatch($query['group']); 7125153720fSfkaag71 $vars = $query['projection']; 7135153720fSfkaag71 $order = $query['ordering']; 7145153720fSfkaag71 $group = $query['grouping']; 7155153720fSfkaag71 $consider = $query['considering']; 7165153720fSfkaag71 $terms = array(); 7175153720fSfkaag71 $fields = array(); 7185153720fSfkaag71 7195153720fSfkaag71 // if we get a don't group, put sentinel value in place 7205153720fSfkaag71 if($group === false) $group = array(); 7215153720fSfkaag71 7225153720fSfkaag71 // massage ordering to comply with grouping 7235153720fSfkaag71 // we do this by combining ordering and sorting information as follows: 7245153720fSfkaag71 // The new ordering is {i, Gc, Oc} where 7255153720fSfkaag71 // i = (order intersect group) 7265153720fSfkaag71 // Gc = (group diff order) 7275153720fSfkaag71 // Oc = (order diff group) 7285153720fSfkaag71 $order_vars = array(); 7295153720fSfkaag71 $order_lookup = array(); 7305153720fSfkaag71 foreach($order as $o) { 7315153720fSfkaag71 $order_vars[] = $o['variable']; 7325153720fSfkaag71 $order_lookup[$o['variable']] = $o; 7335153720fSfkaag71 } 7345153720fSfkaag71 7355153720fSfkaag71 // determine the three components 7365153720fSfkaag71 $order_i = array_intersect($order_vars, $group); 7375153720fSfkaag71 $group_c = array_diff($group, $order_vars); 7385153720fSfkaag71 $order_c = array_diff($order_vars, $group); 7395153720fSfkaag71 $order_n = array_merge($order_i, $group_c, $order_c); 7405153720fSfkaag71 7415153720fSfkaag71 // construct new ordering array 7425153720fSfkaag71 $neworder = array(); 7435153720fSfkaag71 foreach($order_n as $ovar) { 7445153720fSfkaag71 if(!empty($order_lookup[$ovar])) { 7455153720fSfkaag71 $neworder[] = $order_lookup[$ovar]; 7465153720fSfkaag71 } else { 7475153720fSfkaag71 $neworder[] = array('variable'=>$ovar, 'direction'=>'asc'); 7485153720fSfkaag71 } 7495153720fSfkaag71 } 7505153720fSfkaag71 7515153720fSfkaag71 // project extra fields that are required for the grouping 7525153720fSfkaag71 foreach($group as $v) { 7535153720fSfkaag71 // only project them if they're not projected somewhere else 7545153720fSfkaag71 if(!in_array($v, $vars)) { 7555153720fSfkaag71 $name = $this->_name(array('type'=>'variable', 'text'=>$v)); 7565153720fSfkaag71 $fields[] = $name; 7575153720fSfkaag71 7585153720fSfkaag71 // store grouping translation 7595153720fSfkaag71 $this->grouped[$name] = $v; 7605153720fSfkaag71 } 7615153720fSfkaag71 } 7625153720fSfkaag71 7635153720fSfkaag71 7645153720fSfkaag71 // determine exact projection 7655153720fSfkaag71 foreach($vars as $v) { 7665153720fSfkaag71 // determine projection translation 7675153720fSfkaag71 $name = $this->_name(array('type'=>'variable','text'=>$v)); 7685153720fSfkaag71 7695153720fSfkaag71 // fix projected variable into SQL 7705153720fSfkaag71 $terms[] = $name; 7715153720fSfkaag71 $fields[] = $name; 7725153720fSfkaag71 7735153720fSfkaag71 // store projection translation 7745153720fSfkaag71 $this->projected[$name] = $v; 7755153720fSfkaag71 7765153720fSfkaag71 // store grouping translation 7775153720fSfkaag71 if(in_array($v, $group)) { 7785153720fSfkaag71 $this->grouped[$name] = $v; 7795153720fSfkaag71 } 7805153720fSfkaag71 } 7815153720fSfkaag71 7825153720fSfkaag71 // add fields suggested for consideration 7835153720fSfkaag71 foreach($consider as $v) { 7845153720fSfkaag71 $name = $this->_name(array('type'=>'variable', 'text'=>$v)); 7855153720fSfkaag71 $alias = $this->_alias('c'); 7865153720fSfkaag71 $fields[] = "$name AS $alias"; 7875153720fSfkaag71 } 7885153720fSfkaag71 7895153720fSfkaag71 // assign ordering if required 7905153720fSfkaag71 $ordering = array(); 7915153720fSfkaag71 foreach($neworder as $o) { 7925153720fSfkaag71 $name = $this->_name(array('type'=>'variable','text'=>$o['variable'])); 7935153720fSfkaag71 $orderTerms = $this->_db->orderBy($name); 7945153720fSfkaag71 foreach($orderTerms as $term) { 7955153720fSfkaag71 $a = $this->_alias('o'); 7965153720fSfkaag71 $fields[] = "$term AS $a"; 7975153720fSfkaag71 $ordering[] = "$a ".($o['direction'] == 'asc'?'ASC':'DESC'); 7985153720fSfkaag71 } 7995153720fSfkaag71 } 8005153720fSfkaag71 8015153720fSfkaag71 // construct select list 8025153720fSfkaag71 $fields = implode(', ',$fields); 8035153720fSfkaag71 8045153720fSfkaag71 // construct ordering 8055153720fSfkaag71 if(count($ordering)>0) { 8065153720fSfkaag71 $ordering = ' ORDER BY '.implode(', ',$ordering); 8075153720fSfkaag71 } else { 8085153720fSfkaag71 $ordering = ''; 8095153720fSfkaag71 } 8105153720fSfkaag71 8115153720fSfkaag71 return array( 8125153720fSfkaag71 'sql'=>'SELECT DISTINCT '.$fields.' FROM ('.$gp['sql'].') r'.$ordering, 8135153720fSfkaag71 'terms'=>$terms 8145153720fSfkaag71 ); 8155153720fSfkaag71 } 8165153720fSfkaag71 8175153720fSfkaag71 function _dispatch($query) { 8185153720fSfkaag71 switch($query['type']) { 8195153720fSfkaag71 case 'select': 8205153720fSfkaag71 return $this->_trans_select($query); 8215153720fSfkaag71 case 'union': 8225153720fSfkaag71 return $this->_trans_union($query); 8235153720fSfkaag71 case 'minus': 8245153720fSfkaag71 return $this->_trans_minus($query); 8255153720fSfkaag71 case 'optional': 8265153720fSfkaag71 return $this->_trans_opt($query); 8275153720fSfkaag71 case 'filter': 8285153720fSfkaag71 return $this->_trans_filter($query); 8295153720fSfkaag71 case 'triple': 8305153720fSfkaag71 return $this->_trans_tp($query); 8315153720fSfkaag71 case 'and': 8325153720fSfkaag71 return $this->_trans_and($query); 8335153720fSfkaag71 default: 8345153720fSfkaag71 msg(sprintf($this->getLang('error_triples_node'),hsc($query['type'])),-1); 8355153720fSfkaag71 return array('sql'=>'<<INVALID QUERY NODE>>', 'terms'=>array()); 8365153720fSfkaag71 } 8375153720fSfkaag71 } 8385153720fSfkaag71 8395153720fSfkaag71 /** 8405153720fSfkaag71 * Translates an abstract query tree to SQL. 8415153720fSfkaag71 */ 8425153720fSfkaag71 function translate($query) { 8435153720fSfkaag71 $q = $this->_dispatch($query); 8445153720fSfkaag71 return array($q['sql'], $this->literals, $this->projected, $this->grouped); 8455153720fSfkaag71 } 8465153720fSfkaag71} 8475153720fSfkaag71 8485153720fSfkaag71/** 8495153720fSfkaag71 * This iterator is used to offer an interface over a 8505153720fSfkaag71 * relations query result. 8515153720fSfkaag71 */ 8525153720fSfkaag71class strata_relations_iterator implements Iterator { 8535153720fSfkaag71 function __construct($pdostatement, $projection) { 8545153720fSfkaag71 // backend iterator 8555153720fSfkaag71 $this->data = $pdostatement; 8565153720fSfkaag71 8575153720fSfkaag71 // state information 8585153720fSfkaag71 $this->closed = false; 8595153720fSfkaag71 $this->id = 0; 8605153720fSfkaag71 8615153720fSfkaag71 // projection data 8625153720fSfkaag71 $this->projection = $projection; 8635153720fSfkaag71 8645153720fSfkaag71 // initialize the iterator 8655153720fSfkaag71 $this->next(); 8665153720fSfkaag71 } 8675153720fSfkaag71 8685153720fSfkaag71 function current() { 8695153720fSfkaag71 return $this->row; 8705153720fSfkaag71 } 8715153720fSfkaag71 8725153720fSfkaag71 function key() { 8735153720fSfkaag71 return $this->id; 8745153720fSfkaag71 } 8755153720fSfkaag71 8765153720fSfkaag71 function next() { 8775153720fSfkaag71 // fetch... 8785153720fSfkaag71 $this->row = $this->data->fetch(PDO::FETCH_ASSOC); 8795153720fSfkaag71 8805153720fSfkaag71 if($this->row) { 8815153720fSfkaag71 $row = array(); 8825153720fSfkaag71 8835153720fSfkaag71 // ...project... 8845153720fSfkaag71 foreach($this->projection as $alias=>$field) { 8855153720fSfkaag71 $row[$field] = $this->row[$alias] != null ? array($this->row[$alias]) : array(); 8865153720fSfkaag71 } 8875153720fSfkaag71 $this->row = $row; 8885153720fSfkaag71 8895153720fSfkaag71 // ...and increment the id. 8905153720fSfkaag71 $this->id++; 8915153720fSfkaag71 } 8925153720fSfkaag71 8935153720fSfkaag71 // Close the backend if we're out of rows. 8945153720fSfkaag71 // (This should not be necessary if everyone closes 8955153720fSfkaag71 // their iterator after use -- but experience dictates that 8965153720fSfkaag71 // this is a good safety net) 8975153720fSfkaag71 if(!$this->valid()) { 8985153720fSfkaag71 $this->closeCursor(); 8995153720fSfkaag71 } 9005153720fSfkaag71 } 9015153720fSfkaag71 9025153720fSfkaag71 function rewind() { 9035153720fSfkaag71 // noop 9045153720fSfkaag71 } 9055153720fSfkaag71 9065153720fSfkaag71 function valid() { 9075153720fSfkaag71 return $this->row != null; 9085153720fSfkaag71 } 9095153720fSfkaag71 9105153720fSfkaag71 /** 9115153720fSfkaag71 * Closes this result set. 9125153720fSfkaag71 */ 9135153720fSfkaag71 function closeCursor() { 9145153720fSfkaag71 if(!$this->closed) { 9155153720fSfkaag71 $this->data->closeCursor(); 9165153720fSfkaag71 $this->closed = true; 9175153720fSfkaag71 } 9185153720fSfkaag71 } 9195153720fSfkaag71} 9205153720fSfkaag71 9215153720fSfkaag71/** 9225153720fSfkaag71 * This iterator is used to offer an interface over a 9235153720fSfkaag71 * resources query result. 9245153720fSfkaag71 */ 9255153720fSfkaag71class strata_resource_iterator implements Iterator { 9265153720fSfkaag71 function __construct($relations, $projection) { 9275153720fSfkaag71 // backend iterator (ordered by tuple) 9285153720fSfkaag71 $this->data = $relations; 9295153720fSfkaag71 9305153720fSfkaag71 // state information 9315153720fSfkaag71 $this->closed = false; 9325153720fSfkaag71 $this->valid = true; 9335153720fSfkaag71 $this->item = null; 9345153720fSfkaag71 $this->subject = null; 9355153720fSfkaag71 9365153720fSfkaag71 // projection data 9375153720fSfkaag71 list($this->__subject, $this->__predicate, $this->__object) = $projection; 9385153720fSfkaag71 9395153720fSfkaag71 // initialize the iterator 9405153720fSfkaag71 $this->next(); 9415153720fSfkaag71 } 9425153720fSfkaag71 9435153720fSfkaag71 function current() { 9445153720fSfkaag71 return $this->item; 9455153720fSfkaag71 } 9465153720fSfkaag71 9475153720fSfkaag71 function key() { 9485153720fSfkaag71 return $this->subject; 9495153720fSfkaag71 } 9505153720fSfkaag71 9515153720fSfkaag71 function next() { 9525153720fSfkaag71 if(!$this->data->valid()) { 9535153720fSfkaag71 $this->valid = false; 9545153720fSfkaag71 return; 9555153720fSfkaag71 } 9565153720fSfkaag71 9575153720fSfkaag71 // the current relation 9585153720fSfkaag71 $peekRow = $this->data->current(); 9595153720fSfkaag71 9605153720fSfkaag71 // construct a new subject 9615153720fSfkaag71 $this->item = array(); 9625153720fSfkaag71 $this->subject = $peekRow[$this->__subject][0]; 9635153720fSfkaag71 9645153720fSfkaag71 // continue aggregating data as long as the subject doesn't change and 9655153720fSfkaag71 // there is data available 9665153720fSfkaag71 while($this->data->valid() && $peekRow[$this->__subject][0] == $this->subject) { 9675153720fSfkaag71 $p = $peekRow[$this->__predicate][0]; 9685153720fSfkaag71 $o = $peekRow[$this->__object][0]; 9695153720fSfkaag71 if(!isset($this->item[$p])) $this->item[$p] = array(); 9705153720fSfkaag71 $this->item[$p][] = $o; 9715153720fSfkaag71 9725153720fSfkaag71 $this->data->next(); 9735153720fSfkaag71 $peekRow = $this->data->current(); 9745153720fSfkaag71 } 9755153720fSfkaag71 9765153720fSfkaag71 return $this->item; 9775153720fSfkaag71 } 9785153720fSfkaag71 9795153720fSfkaag71 function rewind() { 9805153720fSfkaag71 // noop 9815153720fSfkaag71 } 9825153720fSfkaag71 9835153720fSfkaag71 function valid() { 9845153720fSfkaag71 return $this->valid; 9855153720fSfkaag71 } 9865153720fSfkaag71 9875153720fSfkaag71 /** 9885153720fSfkaag71 * Closes this result set. 9895153720fSfkaag71 */ 9905153720fSfkaag71 function closeCursor() { 9915153720fSfkaag71 if(!$this->closed) { 9925153720fSfkaag71 $this->data->closeCursor(); 9935153720fSfkaag71 $this->closed = true; 9945153720fSfkaag71 } 9955153720fSfkaag71 } 9965153720fSfkaag71} 9975153720fSfkaag71 9985153720fSfkaag71/** 9995153720fSfkaag71 * This iterator aggregates the results of the underlying 10005153720fSfkaag71 * iterator for the given grouping key. 10015153720fSfkaag71 */ 10025153720fSfkaag71class strata_aggregating_iterator implements Iterator { 10035153720fSfkaag71 function __construct($pdostatement, $projection, $grouped) { 10045153720fSfkaag71 // backend iterator (ordered by tuple) 10055153720fSfkaag71 $this->data = $pdostatement; 10065153720fSfkaag71 10075153720fSfkaag71 // state information 10085153720fSfkaag71 $this->closed = false; 10095153720fSfkaag71 $this->valid = true; 10105153720fSfkaag71 $this->item = null; 10115153720fSfkaag71 $this->subject = 0; 10125153720fSfkaag71 10135153720fSfkaag71 $this->groupKey = $grouped; 10145153720fSfkaag71 $this->projection = $projection; 10155153720fSfkaag71 10165153720fSfkaag71 // initialize the iterator 10175153720fSfkaag71 $this->peekRow = $this->data->fetch(PDO::FETCH_ASSOC); 10185153720fSfkaag71 $this->next(); 10195153720fSfkaag71 } 10205153720fSfkaag71 10215153720fSfkaag71 function current() { 10225153720fSfkaag71 return $this->item; 10235153720fSfkaag71 } 10245153720fSfkaag71 10255153720fSfkaag71 function key() { 10265153720fSfkaag71 return $this->subject; 10275153720fSfkaag71 } 10285153720fSfkaag71 10295153720fSfkaag71 private function extractKey($row) { 10305153720fSfkaag71 $result = array(); 10315153720fSfkaag71 foreach($this->groupKey as $alias=>$field) { 10325153720fSfkaag71 $result[$field] = $row[$alias]; 10335153720fSfkaag71 } 10345153720fSfkaag71 return $result; 10355153720fSfkaag71 } 10365153720fSfkaag71 10375153720fSfkaag71 private function keyCheck($a, $b) { 10385153720fSfkaag71 return $a === $b; 10395153720fSfkaag71 } 10405153720fSfkaag71 10415153720fSfkaag71 function next() { 10425153720fSfkaag71 if($this->peekRow == null) { 10435153720fSfkaag71 $this->valid = false; 10445153720fSfkaag71 return; 10455153720fSfkaag71 } 10465153720fSfkaag71 10475153720fSfkaag71 // the current relation 10485153720fSfkaag71 $key = $this->extractKey($this->peekRow); 10495153720fSfkaag71 10505153720fSfkaag71 // construct a new subject 10515153720fSfkaag71 $this->subject++; 10525153720fSfkaag71 $this->item = array(); 10535153720fSfkaag71 10545153720fSfkaag71 // continue aggregating data as long as the subject doesn't change and 10555153720fSfkaag71 // there is data available 10565153720fSfkaag71 while($this->peekRow != null && $this->keyCheck($key,$this->extractKey($this->peekRow))) { 10575153720fSfkaag71 foreach($this->projection as $alias=>$field) { 10585153720fSfkaag71 if(in_array($field, $this->groupKey)) { 10595153720fSfkaag71 // it is a key field, grab it directly from the key 10605153720fSfkaag71 $this->item[$field] = $key[$field]!=null ? array($key[$field]) : array(); 10615153720fSfkaag71 } else { 10625153720fSfkaag71 // lazy create the field's bucket 10635153720fSfkaag71 if(empty($this->item[$field])) { 10645153720fSfkaag71 $this->item[$field] = array(); 10655153720fSfkaag71 } 10665153720fSfkaag71 10675153720fSfkaag71 // push the item into the bucket if we have an item 10685153720fSfkaag71 if($this->peekRow[$alias] != null) { 10695153720fSfkaag71 $this->item[$field][] = $this->peekRow[$alias]; 10705153720fSfkaag71 } 10715153720fSfkaag71 } 10725153720fSfkaag71 } 10735153720fSfkaag71 10745153720fSfkaag71 $this->peekRow = $this->data->fetch(PDO::FETCH_ASSOC); 10755153720fSfkaag71 } 10765153720fSfkaag71 10775153720fSfkaag71 if($this->peekRow == null) { 10785153720fSfkaag71 $this->closeCursor(); 10795153720fSfkaag71 } 10805153720fSfkaag71 10815153720fSfkaag71 return $this->item; 10825153720fSfkaag71 } 10835153720fSfkaag71 10845153720fSfkaag71 function rewind() { 10855153720fSfkaag71 // noop 10865153720fSfkaag71 } 10875153720fSfkaag71 10885153720fSfkaag71 function valid() { 10895153720fSfkaag71 return $this->valid; 10905153720fSfkaag71 } 10915153720fSfkaag71 10925153720fSfkaag71 /** 10935153720fSfkaag71 * Closes this result set. 10945153720fSfkaag71 */ 10955153720fSfkaag71 function closeCursor() { 10965153720fSfkaag71 if(!$this->closed) { 10975153720fSfkaag71 $this->data->closeCursor(); 10985153720fSfkaag71 $this->closed = true; 10995153720fSfkaag71 } 11005153720fSfkaag71 } 11015153720fSfkaag71} 1102