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