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