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