1*5153720fSfkaag71<?php 2*5153720fSfkaag71/** 3*5153720fSfkaag71 * DokuWiki Plugin strata (Helper Component) 4*5153720fSfkaag71 * 5*5153720fSfkaag71 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6*5153720fSfkaag71 * @author Brend Wanders <b.wanders@utwente.nl> 7*5153720fSfkaag71 */ 8*5153720fSfkaag71 9*5153720fSfkaag71// must be run within Dokuwiki 10*5153720fSfkaag71if (!defined('DOKU_INC')) die('Meh.'); 11*5153720fSfkaag71 12*5153720fSfkaag71/** 13*5153720fSfkaag71 * The triples helper is responsible for querying. 14*5153720fSfkaag71 */ 15*5153720fSfkaag71class helper_plugin_strata_triples extends DokuWiki_Plugin { 16*5153720fSfkaag71 public static $readable = 'data'; 17*5153720fSfkaag71 public static $writable = 'data'; 18*5153720fSfkaag71 19*5153720fSfkaag71 function __construct() { 20*5153720fSfkaag71 $this->_initialize(); 21*5153720fSfkaag71 } 22*5153720fSfkaag71 23*5153720fSfkaag71 function getMethods() { 24*5153720fSfkaag71 $result = array(); 25*5153720fSfkaag71 return $result; 26*5153720fSfkaag71 } 27*5153720fSfkaag71 28*5153720fSfkaag71 /** 29*5153720fSfkaag71 * Expands tokens in the DSN. 30*5153720fSfkaag71 * 31*5153720fSfkaag71 * @param str string the string to process 32*5153720fSfkaag71 * @return a string with replaced tokens 33*5153720fSfkaag71 */ 34*5153720fSfkaag71 function _expandTokens($str) { 35*5153720fSfkaag71 global $conf; 36*5153720fSfkaag71 $tokens = array('@METADIR@'); 37*5153720fSfkaag71 $replacers = array($conf['metadir']); 38*5153720fSfkaag71 return str_replace($tokens,$replacers,$str); 39*5153720fSfkaag71 } 40*5153720fSfkaag71 41*5153720fSfkaag71 /** 42*5153720fSfkaag71 * Initializes the triple helper. 43*5153720fSfkaag71 * 44*5153720fSfkaag71 * @param dsn string an optional alternative DSN 45*5153720fSfkaag71 * @return true if initialization succeeded, false otherwise 46*5153720fSfkaag71 */ 47*5153720fSfkaag71 function _initialize() { 48*5153720fSfkaag71 // load default DSN 49*5153720fSfkaag71 $dsn = $this->getConf('default_dsn'); 50*5153720fSfkaag71 $dsn = $this->_expandTokens($dsn); 51*5153720fSfkaag71 52*5153720fSfkaag71 $this->_dsn = $dsn; 53*5153720fSfkaag71 54*5153720fSfkaag71 // construct driver 55*5153720fSfkaag71 list($driver,$connection) = explode(':',$dsn,2); 56*5153720fSfkaag71 $driverFile = DOKU_PLUGIN."strata/driver/$driver.php"; 57*5153720fSfkaag71 if(!@file_exists($driverFile)) { 58*5153720fSfkaag71 msg(sprintf($this->getLang('error_triples_nodriver'), $driver), -1); 59*5153720fSfkaag71 return false; 60*5153720fSfkaag71 } 61*5153720fSfkaag71 require_once($driverFile); 62*5153720fSfkaag71 $driverClass = "plugin_strata_driver_$driver"; 63*5153720fSfkaag71 $this->_db = new $driverClass($this->getConf('debug')); 64*5153720fSfkaag71 65*5153720fSfkaag71 // connect driver 66*5153720fSfkaag71 if(!$this->_db->connect($dsn)) { 67*5153720fSfkaag71 return false; 68*5153720fSfkaag71 } 69*5153720fSfkaag71 70*5153720fSfkaag71 // initialize database if necessary 71*5153720fSfkaag71 if(!$this->_db->isInitialized()) { 72*5153720fSfkaag71 $this->_db->initializeDatabase(); 73*5153720fSfkaag71 } 74*5153720fSfkaag71 75*5153720fSfkaag71 76*5153720fSfkaag71 return true; 77*5153720fSfkaag71 } 78*5153720fSfkaag71 79*5153720fSfkaag71 /** 80*5153720fSfkaag71 * Makes the an SQL expression case insensitive. 81*5153720fSfkaag71 * 82*5153720fSfkaag71 * @param a string the expression to process 83*5153720fSfkaag71 * @return a SQL expression 84*5153720fSfkaag71 */ 85*5153720fSfkaag71 function _ci($a) { 86*5153720fSfkaag71 return $this->_db->ci($a); 87*5153720fSfkaag71 } 88*5153720fSfkaag71 89*5153720fSfkaag71 /** 90*5153720fSfkaag71 * Constructs a case insensitive string comparison in SQL. 91*5153720fSfkaag71 * 92*5153720fSfkaag71 * @param a string the left-hand side 93*5153720fSfkaag71 * @param b string the right-hand side 94*5153720fSfkaag71 * 95*5153720fSfkaag71 * @return a case insensitive SQL string comparison 96*5153720fSfkaag71 */ 97*5153720fSfkaag71 function _cic($a, $b) { 98*5153720fSfkaag71 return $this->_ci($a).' = '.$this->_ci($b); 99*5153720fSfkaag71 } 100*5153720fSfkaag71 101*5153720fSfkaag71 /** 102*5153720fSfkaag71 * Begins a preview. 103*5153720fSfkaag71 */ 104*5153720fSfkaag71 function beginPreview() { 105*5153720fSfkaag71 $this->_db->beginTransaction(); 106*5153720fSfkaag71 } 107*5153720fSfkaag71 108*5153720fSfkaag71 /** 109*5153720fSfkaag71 * Ends a preview. 110*5153720fSfkaag71 */ 111*5153720fSfkaag71 function endPreview() { 112*5153720fSfkaag71 $this->_db->rollback(); 113*5153720fSfkaag71 } 114*5153720fSfkaag71 115*5153720fSfkaag71 /** 116*5153720fSfkaag71 * Removes all triples matching the given triple pattern. One or more parameters 117*5153720fSfkaag71 * can be left out to indicate 'any'. 118*5153720fSfkaag71 */ 119*5153720fSfkaag71 function removeTriples($subject=null, $predicate=null, $object=null, $graph=null) { 120*5153720fSfkaag71 // construct triple filter 121*5153720fSfkaag71 $filters = array('1 = 1'); 122*5153720fSfkaag71 foreach(array('subject','predicate','object','graph') as $param) { 123*5153720fSfkaag71 if($$param != null) { 124*5153720fSfkaag71 $filters[]=$this->_cic($param, '?'); 125*5153720fSfkaag71 $values[] = $$param; 126*5153720fSfkaag71 } 127*5153720fSfkaag71 } 128*5153720fSfkaag71 129*5153720fSfkaag71 $sql .= "DELETE FROM ".self::$writable." WHERE ". implode(" AND ", $filters); 130*5153720fSfkaag71 131*5153720fSfkaag71 // prepare query 132*5153720fSfkaag71 $query = $this->_db->prepare($sql); 133*5153720fSfkaag71 if($query == false) return; 134*5153720fSfkaag71 135*5153720fSfkaag71 // execute query 136*5153720fSfkaag71 $res = $query->execute($values); 137*5153720fSfkaag71 if($res === false) { 138*5153720fSfkaag71 $error = $query->errorInfo(); 139*5153720fSfkaag71 msg(sprintf($this->getLang('error_triples_remove'),hsc($error[2])),-1); 140*5153720fSfkaag71 } 141*5153720fSfkaag71 142*5153720fSfkaag71 $query->closeCursor(); 143*5153720fSfkaag71 } 144*5153720fSfkaag71 145*5153720fSfkaag71 /** 146*5153720fSfkaag71 * Fetches all triples matching the given triple pattern. Onr or more of 147*5153720fSfkaag71 * parameters can be left out to indicate 'any'. 148*5153720fSfkaag71 */ 149*5153720fSfkaag71 function fetchTriples($subject=null, $predicate=null, $object=null, $graph=null) { 150*5153720fSfkaag71 global $ID; 151*5153720fSfkaag71 // construct filter 152*5153720fSfkaag71 $filters = array('1 = 1'); 153*5153720fSfkaag71 foreach(array('subject','predicate','object','graph') as $param) { 154*5153720fSfkaag71 if($$param != null) { 155*5153720fSfkaag71 $filters[]=$this->_cic($param,'?'); 156*5153720fSfkaag71 $values[] = $$param; 157*5153720fSfkaag71 } 158*5153720fSfkaag71 } 159*5153720fSfkaag71 $scopeRestriction = ($this->getConf('scoped')? ' AND graph like "'.getNS($ID).'%"':"" ); 160*5153720fSfkaag71 161*5153720fSfkaag71 $sql .= "SELECT subject, predicate, object, graph FROM ".self::$readable." WHERE ". implode(" AND ", $filters).$scopeRestriction; 162*5153720fSfkaag71 163*5153720fSfkaag71 // prepare queyr 164*5153720fSfkaag71 $query = $this->_db->prepare($sql); 165*5153720fSfkaag71 if($query == false) return; 166*5153720fSfkaag71 167*5153720fSfkaag71 // execute query 168*5153720fSfkaag71 $res = $query->execute($values); 169*5153720fSfkaag71 if($res === false) { 170*5153720fSfkaag71 $error = $query->errorInfo(); 171*5153720fSfkaag71 msg(sprintf($this->getLang('error_triples_fetch'),hsc($error[2])),-1); 172*5153720fSfkaag71 } 173*5153720fSfkaag71 174*5153720fSfkaag71 // fetch results and return them 175*5153720fSfkaag71 $result = $query->fetchAll(PDO::FETCH_ASSOC); 176*5153720fSfkaag71 $query->closeCursor(); 177*5153720fSfkaag71 178*5153720fSfkaag71 return $result; 179*5153720fSfkaag71 } 180*5153720fSfkaag71 181*5153720fSfkaag71 /** 182*5153720fSfkaag71 * Adds a single triple. 183*5153720fSfkaag71 * @param subject string 184*5153720fSfkaag71 * @param predicate string 185*5153720fSfkaag71 * @param object string 186*5153720fSfkaag71 * @param graph string 187*5153720fSfkaag71 * @return true of triple was added succesfully, false if not 188*5153720fSfkaag71 */ 189*5153720fSfkaag71 function addTriple($subject, $predicate, $object, $graph) { 190*5153720fSfkaag71 return $this->addTriples(array(array('subject'=>$subject, 'predicate'=>$predicate, 'object'=>$object)), $graph); 191*5153720fSfkaag71 } 192*5153720fSfkaag71 193*5153720fSfkaag71 /** 194*5153720fSfkaag71 * Adds multiple triples. 195*5153720fSfkaag71 * @param triples array contains all triples as arrays with subject, predicate and object keys 196*5153720fSfkaag71 * @param graph string graph name 197*5153720fSfkaag71 * @return true if the triples were comitted, false otherwise 198*5153720fSfkaag71 */ 199*5153720fSfkaag71 function addTriples($triples, $graph) { 200*5153720fSfkaag71 // prepare insertion query 201*5153720fSfkaag71 $sql = "INSERT INTO ".self::$writable."(subject, predicate, object, graph) VALUES(?, ?, ?, ?)"; 202*5153720fSfkaag71 $query = $this->_db->prepare($sql); 203*5153720fSfkaag71 if($query == false) return false; 204*5153720fSfkaag71 205*5153720fSfkaag71 // put the batch in a transaction 206*5153720fSfkaag71 $this->_db->beginTransaction(); 207*5153720fSfkaag71 foreach($triples as $t) { 208*5153720fSfkaag71 // insert a single triple 209*5153720fSfkaag71 $values = array($t['subject'],$t['predicate'],$t['object'],$graph); 210*5153720fSfkaag71 $res = $query->execute($values); 211*5153720fSfkaag71 212*5153720fSfkaag71 // handle errors 213*5153720fSfkaag71 if($res === false) { 214*5153720fSfkaag71 $error = $query->errorInfo(); 215*5153720fSfkaag71 msg(sprintf($this->getLang('error_triples_add'),hsc($error[2])),-1); 216*5153720fSfkaag71 $this->_db->rollBack(); 217*5153720fSfkaag71 return false; 218*5153720fSfkaag71 } 219*5153720fSfkaag71 $query->closeCursor(); 220*5153720fSfkaag71 } 221*5153720fSfkaag71 222*5153720fSfkaag71 // commit and return 223*5153720fSfkaag71 return $this->_db->commit(); 224*5153720fSfkaag71 } 225*5153720fSfkaag71 226*5153720fSfkaag71 /** 227*5153720fSfkaag71 * Executes the given abstract query tree as a query on the store. 228*5153720fSfkaag71 * 229*5153720fSfkaag71 * @param query array an abstract query tree 230*5153720fSfkaag71 * @return an iterator over the resulting rows 231*5153720fSfkaag71 */ 232*5153720fSfkaag71 function queryRelations($queryTree) { 233*5153720fSfkaag71 // create the SQL generator, and generate the SQL query 234*5153720fSfkaag71 $generator = new strata_sql_generator($this); 235*5153720fSfkaag71 list($sql, $literals, $projected, $grouped) = $generator->translate($queryTree); 236*5153720fSfkaag71 237*5153720fSfkaag71 // prepare the query 238*5153720fSfkaag71 $query = $this->_db->prepare($sql); 239*5153720fSfkaag71 if($query === false) { 240*5153720fSfkaag71 return false; 241*5153720fSfkaag71 } 242*5153720fSfkaag71 243*5153720fSfkaag71 // execute the query 244*5153720fSfkaag71 $res = $query->execute($literals); 245*5153720fSfkaag71 if($res === false) { 246*5153720fSfkaag71 $error = $query->errorInfo(); 247*5153720fSfkaag71 msg(sprintf($this->getLang('error_triples_query'),hsc($error[2])),-1); 248*5153720fSfkaag71 if($this->getConf('debug')) { 249*5153720fSfkaag71 msg(sprintf($this->getLang('debug_sql'),hsc($sql)),-1); 250*5153720fSfkaag71 msg(sprintf($this->getLang('debug_literals'), hsc(print_r($literals,1))),-1); 251*5153720fSfkaag71 } 252*5153720fSfkaag71 return false; 253*5153720fSfkaag71 } 254*5153720fSfkaag71 255*5153720fSfkaag71 // wrap the results in an iterator, and return it 256*5153720fSfkaag71 if($queryTree['grouping'] === false) { 257*5153720fSfkaag71 return new strata_relations_iterator($query, $projected); 258*5153720fSfkaag71 } else { 259*5153720fSfkaag71 return new strata_aggregating_iterator($query, $projected, $grouped); 260*5153720fSfkaag71 } 261*5153720fSfkaag71 } 262*5153720fSfkaag71 263*5153720fSfkaag71 /** 264*5153720fSfkaag71 * Executes the abstract query tree, and returns all properties of the matching subjects. 265*5153720fSfkaag71 * This method assumes that the root is a 'select' node. 266*5153720fSfkaag71 * 267*5153720fSfkaag71 * @param query array the abstract query tree 268*5153720fSfkaag71 * @return an iterator over the resources 269*5153720fSfkaag71 */ 270*5153720fSfkaag71 function queryResources($query) { 271*5153720fSfkaag71 // We transform the given query into a resource-centric query as follows: 272*5153720fSfkaag71 // Remember the single projected variable Vx. 273*5153720fSfkaag71 // Append two variables ?__p and ?__o to the projection 274*5153720fSfkaag71 // Add an extra triple pattern (Vx, ?__p, ?__o) 275*5153720fSfkaag71 // Append Vx to the ordering 276*5153720fSfkaag71 // The query is ready for execution. Result set can be transformed into a 277*5153720fSfkaag71 // resource-centric view by fetching all triples related to a single subject 278*5153720fSfkaag71 // (each subject is in a single continuous block, due to the ordering) 279*5153720fSfkaag71 280*5153720fSfkaag71 // add extra tuple 281*5153720fSfkaag71 $query['group'] = array( 282*5153720fSfkaag71 'type'=>'and', 283*5153720fSfkaag71 'lhs'=>$query['group'], 284*5153720fSfkaag71 'rhs'=>array( 285*5153720fSfkaag71 'type'=>'triple', 286*5153720fSfkaag71 'subject'=>array('type'=>'variable','text'=>$query['projection'][0]), 287*5153720fSfkaag71 'predicate'=>array('type'=>'variable','text'=>'__predicate'), 288*5153720fSfkaag71 'object'=>array('type'=>'variable','text'=>'__object') 289*5153720fSfkaag71 ) 290*5153720fSfkaag71 ); 291*5153720fSfkaag71 292*5153720fSfkaag71 // fix projection list 293*5153720fSfkaag71 $query['projection'] = array( 294*5153720fSfkaag71 $query['projection'][0], 295*5153720fSfkaag71 '__predicate', 296*5153720fSfkaag71 '__object' 297*5153720fSfkaag71 ); 298*5153720fSfkaag71 299*5153720fSfkaag71 // append tuple ordering 300*5153720fSfkaag71 $query['ordering'][] = array( 301*5153720fSfkaag71 'variable'=>$query['projection'][0], 302*5153720fSfkaag71 'direction'=>'asc' 303*5153720fSfkaag71 ); 304*5153720fSfkaag71 305*5153720fSfkaag71 // remove grouping 306*5153720fSfkaag71 $query['grouping'] = false; 307*5153720fSfkaag71 308*5153720fSfkaag71 // execute query 309*5153720fSfkaag71 $result = $this->queryRelations($query); 310*5153720fSfkaag71 311*5153720fSfkaag71 if($result === false) { 312*5153720fSfkaag71 return false; 313*5153720fSfkaag71 } 314*5153720fSfkaag71 315*5153720fSfkaag71 // invoke iterator that's going to aggregate the resulting relations 316*5153720fSfkaag71 return new strata_resource_iterator($result,$query['projection']); 317*5153720fSfkaag71 } 318*5153720fSfkaag71} 319*5153720fSfkaag71 320*5153720fSfkaag71/** 321*5153720fSfkaag71 * SQL generator. 322*5153720fSfkaag71 */ 323*5153720fSfkaag71class strata_sql_generator { 324*5153720fSfkaag71 /** 325*5153720fSfkaag71 * Stores all literal values keyed to their placeholder. 326*5153720fSfkaag71 */ 327*5153720fSfkaag71 private $literals = array(); 328*5153720fSfkaag71 329*5153720fSfkaag71 /** 330*5153720fSfkaag71 * Stores all projected variables. 331*5153720fSfkaag71 */ 332*5153720fSfkaag71 private $projected = array(); 333*5153720fSfkaag71 334*5153720fSfkaag71 /** 335*5153720fSfkaag71 * Stores all grouped variables. 336*5153720fSfkaag71 */ 337*5153720fSfkaag71 private $grouped = array(); 338*5153720fSfkaag71 339*5153720fSfkaag71 /** 340*5153720fSfkaag71 * Constructor. 341*5153720fSfkaag71 */ 342*5153720fSfkaag71 function __construct($triples) { 343*5153720fSfkaag71 $this->_triples = $triples; 344*5153720fSfkaag71 $this->_db = $this->_triples->_db; 345*5153720fSfkaag71 } 346*5153720fSfkaag71 347*5153720fSfkaag71 /** 348*5153720fSfkaag71 * Passes through localisation calls. 349*5153720fSfkaag71 */ 350*5153720fSfkaag71 function getLang($key) { 351*5153720fSfkaag71 return $this->_triples->getLang($key); 352*5153720fSfkaag71 } 353*5153720fSfkaag71 354*5153720fSfkaag71 /** 355*5153720fSfkaag71 * Wrap SQL expression in case insensitivisation. 356*5153720fSfkaag71 */ 357*5153720fSfkaag71 function _ci($a) { 358*5153720fSfkaag71 return $this->_triples->_ci($a); 359*5153720fSfkaag71 } 360*5153720fSfkaag71 361*5153720fSfkaag71 /** 362*5153720fSfkaag71 * Alias generator. 363*5153720fSfkaag71 */ 364*5153720fSfkaag71 private $_aliasCounter = 0; 365*5153720fSfkaag71 function _alias($prefix='a') { 366*5153720fSfkaag71 return $prefix.($this->_aliasCounter++); 367*5153720fSfkaag71 } 368*5153720fSfkaag71 369*5153720fSfkaag71 /** 370*5153720fSfkaag71 * All used literals. 371*5153720fSfkaag71 */ 372*5153720fSfkaag71 private $_literalLookup = array(); 373*5153720fSfkaag71 private $_variableLookup = array(); 374*5153720fSfkaag71 375*5153720fSfkaag71 /** 376*5153720fSfkaag71 * Name generator. Makes the distinction between literals 377*5153720fSfkaag71 * and variables, as they can have the same spelling (and 378*5153720fSfkaag71 * frequently do in simple queries). 379*5153720fSfkaag71 */ 380*5153720fSfkaag71 function _name($term) { 381*5153720fSfkaag71 if($term['type'] == 'variable') { 382*5153720fSfkaag71 // always try the cache 383*5153720fSfkaag71 if(empty($this->_variableLookup[$term['text']])) { 384*5153720fSfkaag71 $this->_variableLookup[$term['text']] = $this->_alias('v'); 385*5153720fSfkaag71 } 386*5153720fSfkaag71 387*5153720fSfkaag71 return $this->_variableLookup[$term['text']]; 388*5153720fSfkaag71 } elseif($term['type'] == 'literal') { 389*5153720fSfkaag71 // always try the cache 390*5153720fSfkaag71 if(empty($this->_literalLookup[$term['text']])) { 391*5153720fSfkaag71 // use aliases to represent literals 392*5153720fSfkaag71 $this->_literalLookup[$term['text']] = $this->_alias('lit'); 393*5153720fSfkaag71 } 394*5153720fSfkaag71 395*5153720fSfkaag71 // return literal name 396*5153720fSfkaag71 return $this->_literalLookup[$term['text']]; 397*5153720fSfkaag71 } 398*5153720fSfkaag71 } 399*5153720fSfkaag71 400*5153720fSfkaag71 /** 401*5153720fSfkaag71 * Test whether two things are equal (i.e., equal variables, or equal literals) 402*5153720fSfkaag71 */ 403*5153720fSfkaag71 function _patternEquals($pa, $pb) { 404*5153720fSfkaag71 return $pa['type'] == $pb['type'] && $pa['text'] == $pb['text']; 405*5153720fSfkaag71 } 406*5153720fSfkaag71 407*5153720fSfkaag71 /** 408*5153720fSfkaag71 * Generates a conditional for the given triple pattern. 409*5153720fSfkaag71 */ 410*5153720fSfkaag71 function _genCond($tp) { 411*5153720fSfkaag71 $conditions = array(); 412*5153720fSfkaag71 413*5153720fSfkaag71 // the subject is a literal 414*5153720fSfkaag71 if($tp['subject']['type'] == 'literal') { 415*5153720fSfkaag71 $id = $this->_alias('qv'); 416*5153720fSfkaag71 $conditions[] = $this->_ci('subject').' = '.$this->_ci(':'.$id); 417*5153720fSfkaag71 $this->literals[$id] = $tp['subject']['text']; 418*5153720fSfkaag71 } 419*5153720fSfkaag71 420*5153720fSfkaag71 // the predicate is a literal 421*5153720fSfkaag71 if($tp['predicate']['type'] == 'literal') { 422*5153720fSfkaag71 $id = $this->_alias('qv'); 423*5153720fSfkaag71 $conditions[] = $this->_ci('predicate').' = '.$this->_ci(':'.$id); 424*5153720fSfkaag71 $this->literals[$id] = $tp['predicate']['text']; 425*5153720fSfkaag71 } 426*5153720fSfkaag71 427*5153720fSfkaag71 // the object is a literal 428*5153720fSfkaag71 if($tp['object']['type'] == 'literal') { 429*5153720fSfkaag71 $id = $this->_alias('qv'); 430*5153720fSfkaag71 $conditions[] = $this->_ci('object').' = '.$this->_ci(':'.$id); 431*5153720fSfkaag71 $this->literals[$id] = $tp['object']['text']; 432*5153720fSfkaag71 } 433*5153720fSfkaag71 434*5153720fSfkaag71 // subject equals predicate 435*5153720fSfkaag71 if($this->_patternEquals($tp['subject'],$tp['predicate'])) { 436*5153720fSfkaag71 $conditions[] = $this->_ci('subject').' = '.$this->_ci('predicate'); 437*5153720fSfkaag71 } 438*5153720fSfkaag71 439*5153720fSfkaag71 // subject equals object 440*5153720fSfkaag71 if($this->_patternEquals($tp['subject'],$tp['object'])) { 441*5153720fSfkaag71 $conditions[] = $this->_ci('subject').' = '.$this->_ci('object'); 442*5153720fSfkaag71 } 443*5153720fSfkaag71 444*5153720fSfkaag71 // predicate equals object 445*5153720fSfkaag71 if($this->_patternEquals($tp['predicate'],$tp['object'])) { 446*5153720fSfkaag71 $conditions[] = $this->_ci('predicate').' = '.$this->_ci('object'); 447*5153720fSfkaag71 } 448*5153720fSfkaag71 449*5153720fSfkaag71 if(count($conditions)!=0) { 450*5153720fSfkaag71 return implode(' AND ',$conditions); 451*5153720fSfkaag71 } else { 452*5153720fSfkaag71 return '1 = 1'; 453*5153720fSfkaag71 } 454*5153720fSfkaag71 } 455*5153720fSfkaag71 456*5153720fSfkaag71 /** 457*5153720fSfkaag71 * Generates a projection for the given triple pattern. 458*5153720fSfkaag71 */ 459*5153720fSfkaag71 function _genPR($tp) { 460*5153720fSfkaag71 $list = array(); 461*5153720fSfkaag71 462*5153720fSfkaag71 // always project the subject 463*5153720fSfkaag71 $list[] = 'subject AS '.$this->_name($tp['subject']); 464*5153720fSfkaag71 465*5153720fSfkaag71 // project the predicate if it's different from the subject 466*5153720fSfkaag71 if(!$this->_patternEquals($tp['subject'], $tp['predicate'])) { 467*5153720fSfkaag71 $list[] = 'predicate AS '.$this->_name($tp['predicate']); 468*5153720fSfkaag71 } 469*5153720fSfkaag71 470*5153720fSfkaag71 // project the object if it's different from the subject and different from the predicate 471*5153720fSfkaag71 if(!$this->_patternEquals($tp['subject'], $tp['object']) && !$this->_patternEquals($tp['predicate'],$tp['object'])) { 472*5153720fSfkaag71 $list[] = 'object AS '.$this->_name($tp['object']); 473*5153720fSfkaag71 } 474*5153720fSfkaag71 475*5153720fSfkaag71 return implode(', ',$list); 476*5153720fSfkaag71 } 477*5153720fSfkaag71 478*5153720fSfkaag71 /** 479*5153720fSfkaag71 * Translates a triple pattern into a graph pattern. 480*5153720fSfkaag71 */ 481*5153720fSfkaag71 function _trans_tp($tp) { 482*5153720fSfkaag71 global $ID; 483*5153720fSfkaag71 $terms = array(); 484*5153720fSfkaag71 485*5153720fSfkaag71 // the subject is a variable 486*5153720fSfkaag71 if($tp['subject']['type'] == 'variable') { 487*5153720fSfkaag71 $terms[] = $this->_name($tp['subject']); 488*5153720fSfkaag71 } 489*5153720fSfkaag71 490*5153720fSfkaag71 // the predicate is a variable 491*5153720fSfkaag71 if($tp['predicate']['type'] == 'variable') { 492*5153720fSfkaag71 $terms[] = $this->_name($tp['predicate']); 493*5153720fSfkaag71 } 494*5153720fSfkaag71 495*5153720fSfkaag71 // the object is a variable 496*5153720fSfkaag71 if($tp['object']['type'] == 'variable') { 497*5153720fSfkaag71 $terms[] = $this->_name($tp['object']); 498*5153720fSfkaag71 } 499*5153720fSfkaag71 500*5153720fSfkaag71 $scopeRestriction = ($this->_triples->getConf('scoped')? ' AND graph like "'.getNS($ID).'%"':"" ); 501*5153720fSfkaag71 return array( 502*5153720fSfkaag71 'sql'=>'SELECT '.$this->_genPR($tp).' FROM '.helper_plugin_strata_triples::$readable.' WHERE '.$this->_genCond($tp).$scopeRestriction, 503*5153720fSfkaag71 'terms'=>array_unique($terms) 504*5153720fSfkaag71 ); 505*5153720fSfkaag71 } 506*5153720fSfkaag71 507*5153720fSfkaag71 /** 508*5153720fSfkaag71 * Translates a group operation on the two graph patterns. 509*5153720fSfkaag71 */ 510*5153720fSfkaag71 function _trans_group($gp1, $gp2, $join) { 511*5153720fSfkaag71 // determine the resulting terms 512*5153720fSfkaag71 $terms = array_unique(array_merge($gp1['terms'], $gp2['terms'])); 513*5153720fSfkaag71 514*5153720fSfkaag71 // determine the overlapping terms (we need to coalesce these) 515*5153720fSfkaag71 $common = array_intersect($gp1['terms'], $gp2['terms']); 516*5153720fSfkaag71 517*5153720fSfkaag71 // determine the non-overlapping terms (we can project them directly) 518*5153720fSfkaag71 $fields = array_diff($terms, $common); 519*5153720fSfkaag71 520*5153720fSfkaag71 // handle overlapping terms by coalescing them into a single term 521*5153720fSfkaag71 if(count($common)>0) { 522*5153720fSfkaag71 $intersect = array(); 523*5153720fSfkaag71 foreach($common as $c) { 524*5153720fSfkaag71 $intersect[] = '('.$this->_ci('r1.'.$c).' = '.$this->_ci('r2.'.$c).' OR r1.'.$c.' IS NULL OR r2.'.$c.' IS NULL)'; 525*5153720fSfkaag71 $fields[]='COALESCE(r1.'.$c.', r2.'.$c.') AS '.$c; 526*5153720fSfkaag71 } 527*5153720fSfkaag71 $intersect = implode(' AND ',$intersect); 528*5153720fSfkaag71 } else { 529*5153720fSfkaag71 $intersect = '(1=1)'; 530*5153720fSfkaag71 } 531*5153720fSfkaag71 532*5153720fSfkaag71 $fields = implode(', ',$fields); 533*5153720fSfkaag71 534*5153720fSfkaag71 return array( 535*5153720fSfkaag71 'sql'=>'SELECT DISTINCT '.$fields.' FROM ('.$gp1['sql'].') AS r1 '.$join.' ('.$gp2['sql'].') AS r2 ON '.$intersect, 536*5153720fSfkaag71 'terms'=>$terms 537*5153720fSfkaag71 ); 538*5153720fSfkaag71 } 539*5153720fSfkaag71 540*5153720fSfkaag71 /** 541*5153720fSfkaag71 * Translate an optional operation. 542*5153720fSfkaag71 */ 543*5153720fSfkaag71 function _trans_opt($query) { 544*5153720fSfkaag71 $gp1 = $this->_dispatch($query['lhs']); 545*5153720fSfkaag71 $gp2 = $this->_dispatch($query['rhs']); 546*5153720fSfkaag71 return $this->_trans_group($gp1, $gp2, 'LEFT OUTER JOIN'); 547*5153720fSfkaag71 } 548*5153720fSfkaag71 549*5153720fSfkaag71 /** 550*5153720fSfkaag71 * Translate an and operation. 551*5153720fSfkaag71 */ 552*5153720fSfkaag71 function _trans_and($query) { 553*5153720fSfkaag71 $gp1 = $this->_dispatch($query['lhs']); 554*5153720fSfkaag71 $gp2 = $this->_dispatch($query['rhs']); 555*5153720fSfkaag71 return $this->_trans_group($gp1, $gp2, 'INNER JOIN'); 556*5153720fSfkaag71 } 557*5153720fSfkaag71 558*5153720fSfkaag71 /** 559*5153720fSfkaag71 * Translate a filter operation. The filters are a conjunction of separate expressions. 560*5153720fSfkaag71 */ 561*5153720fSfkaag71 function _trans_filter($query) { 562*5153720fSfkaag71 $gp = $this->_dispatch($query['lhs']); 563*5153720fSfkaag71 $fs = $query['rhs']; 564*5153720fSfkaag71 565*5153720fSfkaag71 $filters = array(); 566*5153720fSfkaag71 567*5153720fSfkaag71 foreach($fs as $f) { 568*5153720fSfkaag71 // determine representation of left-hand side 569*5153720fSfkaag71 if($f['lhs']['type'] == 'variable') { 570*5153720fSfkaag71 $lhs = $this->_name($f['lhs']); 571*5153720fSfkaag71 } else { 572*5153720fSfkaag71 $id = $this->_alias('qv'); 573*5153720fSfkaag71 $lhs = ':'.$id; 574*5153720fSfkaag71 $this->literals[$id] = $f['lhs']['text']; 575*5153720fSfkaag71 } 576*5153720fSfkaag71 577*5153720fSfkaag71 // determine representation of right-hand side 578*5153720fSfkaag71 if($f['rhs']['type'] == 'variable') { 579*5153720fSfkaag71 $rhs = $this->_name($f['rhs']); 580*5153720fSfkaag71 } else { 581*5153720fSfkaag71 $id = $this->_alias('qv'); 582*5153720fSfkaag71 $rhs = ':'.$id; 583*5153720fSfkaag71 $this->literals[$id] = $f['rhs']['text']; 584*5153720fSfkaag71 } 585*5153720fSfkaag71 586*5153720fSfkaag71 // the escaping constants (head, tail and modifier) 587*5153720fSfkaag71 $eh= "REPLACE(REPLACE(REPLACE("; 588*5153720fSfkaag71 $et= ",'!','!!'),'_','!_'),'%','!%')"; 589*5153720fSfkaag71 $em= " ESCAPE '!'"; 590*5153720fSfkaag71 591*5153720fSfkaag71 // handle different operators 592*5153720fSfkaag71 switch($f['operator']) { 593*5153720fSfkaag71 case '=': 594*5153720fSfkaag71 case '!=': 595*5153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' '.$f['operator'].' ' . $this->_ci($rhs). ' )'; 596*5153720fSfkaag71 break; 597*5153720fSfkaag71 case '>': 598*5153720fSfkaag71 case '<': 599*5153720fSfkaag71 case '>=': 600*5153720fSfkaag71 case '<=': 601*5153720fSfkaag71 $filters[] = '( ' . $this->_triples->_db->castToNumber($lhs) . ' ' . $f['operator'] . ' ' . $this->_triples->_db->castToNumber($rhs) . ' )'; 602*5153720fSfkaag71 break; 603*5153720fSfkaag71 case '~': 604*5153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' '.$this->_db->stringCompare().' '. $this->_ci('(\'%\' || ' .$eh.$rhs.$et. ' || \'%\')') .$em. ')'; 605*5153720fSfkaag71 break; 606*5153720fSfkaag71 case '!~': 607*5153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' NOT '.$this->_db->stringCompare().' '. $this->_ci('(\'%\' || ' . $eh.$rhs.$et. ' || \'%\')') .$em. ')'; 608*5153720fSfkaag71 break; 609*5153720fSfkaag71 case '^~': 610*5153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' '.$this->_db->stringCompare().' ' .$this->_ci('('. $eh.$rhs.$et . ' || \'%\')').$em. ')'; 611*5153720fSfkaag71 break; 612*5153720fSfkaag71 case '!^~': 613*5153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' NOT '.$this->_db->stringCompare().' ' .$this->_ci('('. $eh.$rhs.$et . ' || \'%\')').$em. ')'; 614*5153720fSfkaag71 break; 615*5153720fSfkaag71 case '$~': 616*5153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' '.$this->_db->stringCompare().' '.$this->_ci('(\'%\' || ' . $eh.$rhs.$et. ')') .$em. ')'; 617*5153720fSfkaag71 break; 618*5153720fSfkaag71 case '!$~': 619*5153720fSfkaag71 $filters[] = '( ' . $this->_ci($lhs) . ' NOT '.$this->_db->stringCompare().' '.$this->_ci('(\'%\' || ' . $eh.$rhs.$et. ')') .$em. ')'; 620*5153720fSfkaag71 break; 621*5153720fSfkaag71 622*5153720fSfkaag71 default: 623*5153720fSfkaag71 } 624*5153720fSfkaag71 } 625*5153720fSfkaag71 626*5153720fSfkaag71 $filters = implode(' AND ', $filters); 627*5153720fSfkaag71 628*5153720fSfkaag71 return array( 629*5153720fSfkaag71 'sql'=>'SELECT * FROM ('.$gp['sql'].') r WHERE '.$filters, 630*5153720fSfkaag71 'terms'=>$gp['terms'] 631*5153720fSfkaag71 ); 632*5153720fSfkaag71 } 633*5153720fSfkaag71 634*5153720fSfkaag71 /** 635*5153720fSfkaag71 * Translate minus operation. 636*5153720fSfkaag71 */ 637*5153720fSfkaag71 function _trans_minus($query) { 638*5153720fSfkaag71 $gp1 = $this->_dispatch($query['lhs']); 639*5153720fSfkaag71 $gp2 = $this->_dispatch($query['rhs']); 640*5153720fSfkaag71 641*5153720fSfkaag71 // determine overlapping terms (we need to substitute these) 642*5153720fSfkaag71 $common = array_intersect($gp1['terms'], $gp2['terms']); 643*5153720fSfkaag71 644*5153720fSfkaag71 // create conditional that 'substitutes' terms by requiring equality 645*5153720fSfkaag71 $terms = array(); 646*5153720fSfkaag71 foreach($common as $c) { 647*5153720fSfkaag71 $terms[] = '('.$this->_ci('r1.'.$c).' = '.$this->_ci('r2.'.$c).')'; 648*5153720fSfkaag71 } 649*5153720fSfkaag71 650*5153720fSfkaag71 if(count($terms)>0) { 651*5153720fSfkaag71 $terms = implode(' AND ',$terms); 652*5153720fSfkaag71 } else { 653*5153720fSfkaag71 $terms = '1=1'; 654*5153720fSfkaag71 } 655*5153720fSfkaag71 656*5153720fSfkaag71 return array( 657*5153720fSfkaag71 'sql'=>'SELECT DISTINCT * FROM ('.$gp1['sql'].') r1 WHERE NOT EXISTS (SELECT * FROM ('.$gp2['sql'].') r2 WHERE '.$terms.')', 658*5153720fSfkaag71 'terms'=>$gp1['terms'] 659*5153720fSfkaag71 ); 660*5153720fSfkaag71 } 661*5153720fSfkaag71 662*5153720fSfkaag71 /** 663*5153720fSfkaag71 * Translate union operation. 664*5153720fSfkaag71 */ 665*5153720fSfkaag71 function _trans_union($query) { 666*5153720fSfkaag71 // dispatch the child graph patterns 667*5153720fSfkaag71 $gp1 = $this->_dispatch($query['lhs']); 668*5153720fSfkaag71 $gp2 = $this->_dispatch($query['rhs']); 669*5153720fSfkaag71 670*5153720fSfkaag71 // dispatch them again to get new literal binding aliases 671*5153720fSfkaag71 // (This is required by PDO, as no named variable may be used twice) 672*5153720fSfkaag71 $gp1x = $this->_dispatch($query['lhs']); 673*5153720fSfkaag71 $gp2x = $this->_dispatch($query['rhs']); 674*5153720fSfkaag71 675*5153720fSfkaag71 // determine non-overlapping terms 676*5153720fSfkaag71 $ta = array_diff($gp1['terms'], $gp2['terms']); 677*5153720fSfkaag71 $tb = array_diff($gp2['terms'], $gp1['terms']); 678*5153720fSfkaag71 679*5153720fSfkaag71 // determine overlapping terms 680*5153720fSfkaag71 $tc = array_intersect($gp1['terms'], $gp2['terms']); 681*5153720fSfkaag71 682*5153720fSfkaag71 // determine final terms 683*5153720fSfkaag71 $terms = array_unique(array_merge($gp1['terms'], $gp2['terms'])); 684*5153720fSfkaag71 685*5153720fSfkaag71 // construct selected term list 686*5153720fSfkaag71 $sa = array_merge($ta, $tb); 687*5153720fSfkaag71 $sb = array_merge($ta, $tb); 688*5153720fSfkaag71 689*5153720fSfkaag71 // append common terms with renaming 690*5153720fSfkaag71 foreach($tc as $c) { 691*5153720fSfkaag71 $sa[] = 'r1.'.$c.' AS '.$c; 692*5153720fSfkaag71 $sb[] = 'r3.'.$c.' AS '.$c; 693*5153720fSfkaag71 } 694*5153720fSfkaag71 695*5153720fSfkaag71 $sa = implode(', ', $sa); 696*5153720fSfkaag71 $sb = implode(', ', $sb); 697*5153720fSfkaag71 698*5153720fSfkaag71 return array( 699*5153720fSfkaag71 '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)', 700*5153720fSfkaag71 'terms'=>$terms 701*5153720fSfkaag71 ); 702*5153720fSfkaag71 } 703*5153720fSfkaag71 704*5153720fSfkaag71 /** 705*5153720fSfkaag71 * Translate projection and ordering. 706*5153720fSfkaag71 */ 707*5153720fSfkaag71 function _trans_select($query) { 708*5153720fSfkaag71 $gp = $this->_dispatch($query['group']); 709*5153720fSfkaag71 $vars = $query['projection']; 710*5153720fSfkaag71 $order = $query['ordering']; 711*5153720fSfkaag71 $group = $query['grouping']; 712*5153720fSfkaag71 $consider = $query['considering']; 713*5153720fSfkaag71 $terms = array(); 714*5153720fSfkaag71 $fields = array(); 715*5153720fSfkaag71 716*5153720fSfkaag71 // if we get a don't group, put sentinel value in place 717*5153720fSfkaag71 if($group === false) $group = array(); 718*5153720fSfkaag71 719*5153720fSfkaag71 // massage ordering to comply with grouping 720*5153720fSfkaag71 // we do this by combining ordering and sorting information as follows: 721*5153720fSfkaag71 // The new ordering is {i, Gc, Oc} where 722*5153720fSfkaag71 // i = (order intersect group) 723*5153720fSfkaag71 // Gc = (group diff order) 724*5153720fSfkaag71 // Oc = (order diff group) 725*5153720fSfkaag71 $order_vars = array(); 726*5153720fSfkaag71 $order_lookup = array(); 727*5153720fSfkaag71 foreach($order as $o) { 728*5153720fSfkaag71 $order_vars[] = $o['variable']; 729*5153720fSfkaag71 $order_lookup[$o['variable']] = $o; 730*5153720fSfkaag71 } 731*5153720fSfkaag71 732*5153720fSfkaag71 // determine the three components 733*5153720fSfkaag71 $order_i = array_intersect($order_vars, $group); 734*5153720fSfkaag71 $group_c = array_diff($group, $order_vars); 735*5153720fSfkaag71 $order_c = array_diff($order_vars, $group); 736*5153720fSfkaag71 $order_n = array_merge($order_i, $group_c, $order_c); 737*5153720fSfkaag71 738*5153720fSfkaag71 // construct new ordering array 739*5153720fSfkaag71 $neworder = array(); 740*5153720fSfkaag71 foreach($order_n as $ovar) { 741*5153720fSfkaag71 if(!empty($order_lookup[$ovar])) { 742*5153720fSfkaag71 $neworder[] = $order_lookup[$ovar]; 743*5153720fSfkaag71 } else { 744*5153720fSfkaag71 $neworder[] = array('variable'=>$ovar, 'direction'=>'asc'); 745*5153720fSfkaag71 } 746*5153720fSfkaag71 } 747*5153720fSfkaag71 748*5153720fSfkaag71 // project extra fields that are required for the grouping 749*5153720fSfkaag71 foreach($group as $v) { 750*5153720fSfkaag71 // only project them if they're not projected somewhere else 751*5153720fSfkaag71 if(!in_array($v, $vars)) { 752*5153720fSfkaag71 $name = $this->_name(array('type'=>'variable', 'text'=>$v)); 753*5153720fSfkaag71 $fields[] = $name; 754*5153720fSfkaag71 755*5153720fSfkaag71 // store grouping translation 756*5153720fSfkaag71 $this->grouped[$name] = $v; 757*5153720fSfkaag71 } 758*5153720fSfkaag71 } 759*5153720fSfkaag71 760*5153720fSfkaag71 761*5153720fSfkaag71 // determine exact projection 762*5153720fSfkaag71 foreach($vars as $v) { 763*5153720fSfkaag71 // determine projection translation 764*5153720fSfkaag71 $name = $this->_name(array('type'=>'variable','text'=>$v)); 765*5153720fSfkaag71 766*5153720fSfkaag71 // fix projected variable into SQL 767*5153720fSfkaag71 $terms[] = $name; 768*5153720fSfkaag71 $fields[] = $name; 769*5153720fSfkaag71 770*5153720fSfkaag71 // store projection translation 771*5153720fSfkaag71 $this->projected[$name] = $v; 772*5153720fSfkaag71 773*5153720fSfkaag71 // store grouping translation 774*5153720fSfkaag71 if(in_array($v, $group)) { 775*5153720fSfkaag71 $this->grouped[$name] = $v; 776*5153720fSfkaag71 } 777*5153720fSfkaag71 } 778*5153720fSfkaag71 779*5153720fSfkaag71 // add fields suggested for consideration 780*5153720fSfkaag71 foreach($consider as $v) { 781*5153720fSfkaag71 $name = $this->_name(array('type'=>'variable', 'text'=>$v)); 782*5153720fSfkaag71 $alias = $this->_alias('c'); 783*5153720fSfkaag71 $fields[] = "$name AS $alias"; 784*5153720fSfkaag71 } 785*5153720fSfkaag71 786*5153720fSfkaag71 // assign ordering if required 787*5153720fSfkaag71 $ordering = array(); 788*5153720fSfkaag71 foreach($neworder as $o) { 789*5153720fSfkaag71 $name = $this->_name(array('type'=>'variable','text'=>$o['variable'])); 790*5153720fSfkaag71 $orderTerms = $this->_db->orderBy($name); 791*5153720fSfkaag71 foreach($orderTerms as $term) { 792*5153720fSfkaag71 $a = $this->_alias('o'); 793*5153720fSfkaag71 $fields[] = "$term AS $a"; 794*5153720fSfkaag71 $ordering[] = "$a ".($o['direction'] == 'asc'?'ASC':'DESC'); 795*5153720fSfkaag71 } 796*5153720fSfkaag71 } 797*5153720fSfkaag71 798*5153720fSfkaag71 // construct select list 799*5153720fSfkaag71 $fields = implode(', ',$fields); 800*5153720fSfkaag71 801*5153720fSfkaag71 // construct ordering 802*5153720fSfkaag71 if(count($ordering)>0) { 803*5153720fSfkaag71 $ordering = ' ORDER BY '.implode(', ',$ordering); 804*5153720fSfkaag71 } else { 805*5153720fSfkaag71 $ordering = ''; 806*5153720fSfkaag71 } 807*5153720fSfkaag71 808*5153720fSfkaag71 return array( 809*5153720fSfkaag71 'sql'=>'SELECT DISTINCT '.$fields.' FROM ('.$gp['sql'].') r'.$ordering, 810*5153720fSfkaag71 'terms'=>$terms 811*5153720fSfkaag71 ); 812*5153720fSfkaag71 } 813*5153720fSfkaag71 814*5153720fSfkaag71 function _dispatch($query) { 815*5153720fSfkaag71 switch($query['type']) { 816*5153720fSfkaag71 case 'select': 817*5153720fSfkaag71 return $this->_trans_select($query); 818*5153720fSfkaag71 case 'union': 819*5153720fSfkaag71 return $this->_trans_union($query); 820*5153720fSfkaag71 case 'minus': 821*5153720fSfkaag71 return $this->_trans_minus($query); 822*5153720fSfkaag71 case 'optional': 823*5153720fSfkaag71 return $this->_trans_opt($query); 824*5153720fSfkaag71 case 'filter': 825*5153720fSfkaag71 return $this->_trans_filter($query); 826*5153720fSfkaag71 case 'triple': 827*5153720fSfkaag71 return $this->_trans_tp($query); 828*5153720fSfkaag71 case 'and': 829*5153720fSfkaag71 return $this->_trans_and($query); 830*5153720fSfkaag71 default: 831*5153720fSfkaag71 msg(sprintf($this->getLang('error_triples_node'),hsc($query['type'])),-1); 832*5153720fSfkaag71 return array('sql'=>'<<INVALID QUERY NODE>>', 'terms'=>array()); 833*5153720fSfkaag71 } 834*5153720fSfkaag71 } 835*5153720fSfkaag71 836*5153720fSfkaag71 /** 837*5153720fSfkaag71 * Translates an abstract query tree to SQL. 838*5153720fSfkaag71 */ 839*5153720fSfkaag71 function translate($query) { 840*5153720fSfkaag71 $q = $this->_dispatch($query); 841*5153720fSfkaag71 return array($q['sql'], $this->literals, $this->projected, $this->grouped); 842*5153720fSfkaag71 } 843*5153720fSfkaag71} 844*5153720fSfkaag71 845*5153720fSfkaag71/** 846*5153720fSfkaag71 * This iterator is used to offer an interface over a 847*5153720fSfkaag71 * relations query result. 848*5153720fSfkaag71 */ 849*5153720fSfkaag71class strata_relations_iterator implements Iterator { 850*5153720fSfkaag71 function __construct($pdostatement, $projection) { 851*5153720fSfkaag71 // backend iterator 852*5153720fSfkaag71 $this->data = $pdostatement; 853*5153720fSfkaag71 854*5153720fSfkaag71 // state information 855*5153720fSfkaag71 $this->closed = false; 856*5153720fSfkaag71 $this->id = 0; 857*5153720fSfkaag71 858*5153720fSfkaag71 // projection data 859*5153720fSfkaag71 $this->projection = $projection; 860*5153720fSfkaag71 861*5153720fSfkaag71 // initialize the iterator 862*5153720fSfkaag71 $this->next(); 863*5153720fSfkaag71 } 864*5153720fSfkaag71 865*5153720fSfkaag71 function current() { 866*5153720fSfkaag71 return $this->row; 867*5153720fSfkaag71 } 868*5153720fSfkaag71 869*5153720fSfkaag71 function key() { 870*5153720fSfkaag71 return $this->id; 871*5153720fSfkaag71 } 872*5153720fSfkaag71 873*5153720fSfkaag71 function next() { 874*5153720fSfkaag71 // fetch... 875*5153720fSfkaag71 $this->row = $this->data->fetch(PDO::FETCH_ASSOC); 876*5153720fSfkaag71 877*5153720fSfkaag71 if($this->row) { 878*5153720fSfkaag71 $row = array(); 879*5153720fSfkaag71 880*5153720fSfkaag71 // ...project... 881*5153720fSfkaag71 foreach($this->projection as $alias=>$field) { 882*5153720fSfkaag71 $row[$field] = $this->row[$alias] != null ? array($this->row[$alias]) : array(); 883*5153720fSfkaag71 } 884*5153720fSfkaag71 $this->row = $row; 885*5153720fSfkaag71 886*5153720fSfkaag71 // ...and increment the id. 887*5153720fSfkaag71 $this->id++; 888*5153720fSfkaag71 } 889*5153720fSfkaag71 890*5153720fSfkaag71 // Close the backend if we're out of rows. 891*5153720fSfkaag71 // (This should not be necessary if everyone closes 892*5153720fSfkaag71 // their iterator after use -- but experience dictates that 893*5153720fSfkaag71 // this is a good safety net) 894*5153720fSfkaag71 if(!$this->valid()) { 895*5153720fSfkaag71 $this->closeCursor(); 896*5153720fSfkaag71 } 897*5153720fSfkaag71 } 898*5153720fSfkaag71 899*5153720fSfkaag71 function rewind() { 900*5153720fSfkaag71 // noop 901*5153720fSfkaag71 } 902*5153720fSfkaag71 903*5153720fSfkaag71 function valid() { 904*5153720fSfkaag71 return $this->row != null; 905*5153720fSfkaag71 } 906*5153720fSfkaag71 907*5153720fSfkaag71 /** 908*5153720fSfkaag71 * Closes this result set. 909*5153720fSfkaag71 */ 910*5153720fSfkaag71 function closeCursor() { 911*5153720fSfkaag71 if(!$this->closed) { 912*5153720fSfkaag71 $this->data->closeCursor(); 913*5153720fSfkaag71 $this->closed = true; 914*5153720fSfkaag71 } 915*5153720fSfkaag71 } 916*5153720fSfkaag71} 917*5153720fSfkaag71 918*5153720fSfkaag71/** 919*5153720fSfkaag71 * This iterator is used to offer an interface over a 920*5153720fSfkaag71 * resources query result. 921*5153720fSfkaag71 */ 922*5153720fSfkaag71class strata_resource_iterator implements Iterator { 923*5153720fSfkaag71 function __construct($relations, $projection) { 924*5153720fSfkaag71 // backend iterator (ordered by tuple) 925*5153720fSfkaag71 $this->data = $relations; 926*5153720fSfkaag71 927*5153720fSfkaag71 // state information 928*5153720fSfkaag71 $this->closed = false; 929*5153720fSfkaag71 $this->valid = true; 930*5153720fSfkaag71 $this->item = null; 931*5153720fSfkaag71 $this->subject = null; 932*5153720fSfkaag71 933*5153720fSfkaag71 // projection data 934*5153720fSfkaag71 list($this->__subject, $this->__predicate, $this->__object) = $projection; 935*5153720fSfkaag71 936*5153720fSfkaag71 // initialize the iterator 937*5153720fSfkaag71 $this->next(); 938*5153720fSfkaag71 } 939*5153720fSfkaag71 940*5153720fSfkaag71 function current() { 941*5153720fSfkaag71 return $this->item; 942*5153720fSfkaag71 } 943*5153720fSfkaag71 944*5153720fSfkaag71 function key() { 945*5153720fSfkaag71 return $this->subject; 946*5153720fSfkaag71 } 947*5153720fSfkaag71 948*5153720fSfkaag71 function next() { 949*5153720fSfkaag71 if(!$this->data->valid()) { 950*5153720fSfkaag71 $this->valid = false; 951*5153720fSfkaag71 return; 952*5153720fSfkaag71 } 953*5153720fSfkaag71 954*5153720fSfkaag71 // the current relation 955*5153720fSfkaag71 $peekRow = $this->data->current(); 956*5153720fSfkaag71 957*5153720fSfkaag71 // construct a new subject 958*5153720fSfkaag71 $this->item = array(); 959*5153720fSfkaag71 $this->subject = $peekRow[$this->__subject][0]; 960*5153720fSfkaag71 961*5153720fSfkaag71 // continue aggregating data as long as the subject doesn't change and 962*5153720fSfkaag71 // there is data available 963*5153720fSfkaag71 while($this->data->valid() && $peekRow[$this->__subject][0] == $this->subject) { 964*5153720fSfkaag71 $p = $peekRow[$this->__predicate][0]; 965*5153720fSfkaag71 $o = $peekRow[$this->__object][0]; 966*5153720fSfkaag71 if(!isset($this->item[$p])) $this->item[$p] = array(); 967*5153720fSfkaag71 $this->item[$p][] = $o; 968*5153720fSfkaag71 969*5153720fSfkaag71 $this->data->next(); 970*5153720fSfkaag71 $peekRow = $this->data->current(); 971*5153720fSfkaag71 } 972*5153720fSfkaag71 973*5153720fSfkaag71 return $this->item; 974*5153720fSfkaag71 } 975*5153720fSfkaag71 976*5153720fSfkaag71 function rewind() { 977*5153720fSfkaag71 // noop 978*5153720fSfkaag71 } 979*5153720fSfkaag71 980*5153720fSfkaag71 function valid() { 981*5153720fSfkaag71 return $this->valid; 982*5153720fSfkaag71 } 983*5153720fSfkaag71 984*5153720fSfkaag71 /** 985*5153720fSfkaag71 * Closes this result set. 986*5153720fSfkaag71 */ 987*5153720fSfkaag71 function closeCursor() { 988*5153720fSfkaag71 if(!$this->closed) { 989*5153720fSfkaag71 $this->data->closeCursor(); 990*5153720fSfkaag71 $this->closed = true; 991*5153720fSfkaag71 } 992*5153720fSfkaag71 } 993*5153720fSfkaag71} 994*5153720fSfkaag71 995*5153720fSfkaag71/** 996*5153720fSfkaag71 * This iterator aggregates the results of the underlying 997*5153720fSfkaag71 * iterator for the given grouping key. 998*5153720fSfkaag71 */ 999*5153720fSfkaag71class strata_aggregating_iterator implements Iterator { 1000*5153720fSfkaag71 function __construct($pdostatement, $projection, $grouped) { 1001*5153720fSfkaag71 // backend iterator (ordered by tuple) 1002*5153720fSfkaag71 $this->data = $pdostatement; 1003*5153720fSfkaag71 1004*5153720fSfkaag71 // state information 1005*5153720fSfkaag71 $this->closed = false; 1006*5153720fSfkaag71 $this->valid = true; 1007*5153720fSfkaag71 $this->item = null; 1008*5153720fSfkaag71 $this->subject = 0; 1009*5153720fSfkaag71 1010*5153720fSfkaag71 $this->groupKey = $grouped; 1011*5153720fSfkaag71 $this->projection = $projection; 1012*5153720fSfkaag71 1013*5153720fSfkaag71 // initialize the iterator 1014*5153720fSfkaag71 $this->peekRow = $this->data->fetch(PDO::FETCH_ASSOC); 1015*5153720fSfkaag71 $this->next(); 1016*5153720fSfkaag71 } 1017*5153720fSfkaag71 1018*5153720fSfkaag71 function current() { 1019*5153720fSfkaag71 return $this->item; 1020*5153720fSfkaag71 } 1021*5153720fSfkaag71 1022*5153720fSfkaag71 function key() { 1023*5153720fSfkaag71 return $this->subject; 1024*5153720fSfkaag71 } 1025*5153720fSfkaag71 1026*5153720fSfkaag71 private function extractKey($row) { 1027*5153720fSfkaag71 $result = array(); 1028*5153720fSfkaag71 foreach($this->groupKey as $alias=>$field) { 1029*5153720fSfkaag71 $result[$field] = $row[$alias]; 1030*5153720fSfkaag71 } 1031*5153720fSfkaag71 return $result; 1032*5153720fSfkaag71 } 1033*5153720fSfkaag71 1034*5153720fSfkaag71 private function keyCheck($a, $b) { 1035*5153720fSfkaag71 return $a === $b; 1036*5153720fSfkaag71 } 1037*5153720fSfkaag71 1038*5153720fSfkaag71 function next() { 1039*5153720fSfkaag71 if($this->peekRow == null) { 1040*5153720fSfkaag71 $this->valid = false; 1041*5153720fSfkaag71 return; 1042*5153720fSfkaag71 } 1043*5153720fSfkaag71 1044*5153720fSfkaag71 // the current relation 1045*5153720fSfkaag71 $key = $this->extractKey($this->peekRow); 1046*5153720fSfkaag71 1047*5153720fSfkaag71 // construct a new subject 1048*5153720fSfkaag71 $this->subject++; 1049*5153720fSfkaag71 $this->item = array(); 1050*5153720fSfkaag71 1051*5153720fSfkaag71 // continue aggregating data as long as the subject doesn't change and 1052*5153720fSfkaag71 // there is data available 1053*5153720fSfkaag71 while($this->peekRow != null && $this->keyCheck($key,$this->extractKey($this->peekRow))) { 1054*5153720fSfkaag71 foreach($this->projection as $alias=>$field) { 1055*5153720fSfkaag71 if(in_array($field, $this->groupKey)) { 1056*5153720fSfkaag71 // it is a key field, grab it directly from the key 1057*5153720fSfkaag71 $this->item[$field] = $key[$field]!=null ? array($key[$field]) : array(); 1058*5153720fSfkaag71 } else { 1059*5153720fSfkaag71 // lazy create the field's bucket 1060*5153720fSfkaag71 if(empty($this->item[$field])) { 1061*5153720fSfkaag71 $this->item[$field] = array(); 1062*5153720fSfkaag71 } 1063*5153720fSfkaag71 1064*5153720fSfkaag71 // push the item into the bucket if we have an item 1065*5153720fSfkaag71 if($this->peekRow[$alias] != null) { 1066*5153720fSfkaag71 $this->item[$field][] = $this->peekRow[$alias]; 1067*5153720fSfkaag71 } 1068*5153720fSfkaag71 } 1069*5153720fSfkaag71 } 1070*5153720fSfkaag71 1071*5153720fSfkaag71 $this->peekRow = $this->data->fetch(PDO::FETCH_ASSOC); 1072*5153720fSfkaag71 } 1073*5153720fSfkaag71 1074*5153720fSfkaag71 if($this->peekRow == null) { 1075*5153720fSfkaag71 $this->closeCursor(); 1076*5153720fSfkaag71 } 1077*5153720fSfkaag71 1078*5153720fSfkaag71 return $this->item; 1079*5153720fSfkaag71 } 1080*5153720fSfkaag71 1081*5153720fSfkaag71 function rewind() { 1082*5153720fSfkaag71 // noop 1083*5153720fSfkaag71 } 1084*5153720fSfkaag71 1085*5153720fSfkaag71 function valid() { 1086*5153720fSfkaag71 return $this->valid; 1087*5153720fSfkaag71 } 1088*5153720fSfkaag71 1089*5153720fSfkaag71 /** 1090*5153720fSfkaag71 * Closes this result set. 1091*5153720fSfkaag71 */ 1092*5153720fSfkaag71 function closeCursor() { 1093*5153720fSfkaag71 if(!$this->closed) { 1094*5153720fSfkaag71 $this->data->closeCursor(); 1095*5153720fSfkaag71 $this->closed = true; 1096*5153720fSfkaag71 } 1097*5153720fSfkaag71 } 1098*5153720fSfkaag71} 1099