xref: /plugin/strata/helper/triples.php (revision 5153720fcc1dd2b6e63035d45f7c2bc32e429371)
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