xref: /plugin/struct/meta/QueryBuilder.php (revision 2b5df26e5a33d0e9ee80d2f3cd3f6665b87a04a8)
18c551fd7SAndreas Gohr<?php
28c551fd7SAndreas Gohr
38c551fd7SAndreas Gohrnamespace dokuwiki\plugin\struct\meta;
48c551fd7SAndreas Gohr
58c551fd7SAndreas Gohr/**
68c551fd7SAndreas Gohr * Class QueryBuilder
78c551fd7SAndreas Gohr * @package dokuwiki\plugin\struct\meta
88c551fd7SAndreas Gohr */
98c551fd7SAndreas Gohrclass QueryBuilder {
108c551fd7SAndreas Gohr
118c551fd7SAndreas Gohr    /** @var array placeholder -> values */
128c551fd7SAndreas Gohr    protected $values = array();
138c551fd7SAndreas Gohr    /** @var array (alias -> statement */
148c551fd7SAndreas Gohr    protected $select = array();
158c551fd7SAndreas Gohr    /** @var array (alias -> statement) */
168c551fd7SAndreas Gohr    protected $from = array();
178c551fd7SAndreas Gohr    /** @var QueryBuilderWhere */
188c551fd7SAndreas Gohr    protected $where;
198c551fd7SAndreas Gohr
208c551fd7SAndreas Gohr    /**
218c551fd7SAndreas Gohr     * QueryBuilder constructor.
228c551fd7SAndreas Gohr     */
238c551fd7SAndreas Gohr    public function __construct() {
248c551fd7SAndreas Gohr        $this->where = new QueryBuilderWhere();
258c551fd7SAndreas Gohr    }
268c551fd7SAndreas Gohr
278c551fd7SAndreas Gohr    /**
288c551fd7SAndreas Gohr     * Adds a column to select
298c551fd7SAndreas Gohr     *
308c551fd7SAndreas Gohr     * If the alias already exists, the current statement for that alias will be overwritten.
318c551fd7SAndreas Gohr     *
328c551fd7SAndreas Gohr     * @param string $tablealias The table to select from
338c551fd7SAndreas Gohr     * @param string $column The column to select
348c551fd7SAndreas Gohr     * @param string $alias Under whichname to slect the column. blank for column name
358c551fd7SAndreas Gohr     */
368c551fd7SAndreas Gohr    public function addSelectColumn($tablealias, $column, $alias = '') {
378c551fd7SAndreas Gohr        if($alias === '') $alias = $column;
388c551fd7SAndreas Gohr        if(!isset($this->from[$tablealias])) throw new StructException('Table Alias does not exist');
398c551fd7SAndreas Gohr        $this->select[$alias] = "$tablealias.$column AS $alias";
408c551fd7SAndreas Gohr    }
418c551fd7SAndreas Gohr
428c551fd7SAndreas Gohr    /**
438c551fd7SAndreas Gohr     * Add a new select statement (the column part of it)
448c551fd7SAndreas Gohr     *
458c551fd7SAndreas Gohr     * Basically the same as @see addSelectColumn but accepts any statement. This is useful to
468c551fd7SAndreas Gohr     * select things like fixed strings or more complex function calls, but the correctness will not
478c551fd7SAndreas Gohr     * be checked.
488c551fd7SAndreas Gohr     *
498c551fd7SAndreas Gohr     * If the alias already exists, the current statement for that alias will be overwritten.
508c551fd7SAndreas Gohr     *
518c551fd7SAndreas Gohr     * @param string $statement
528c551fd7SAndreas Gohr     * @param string $alias
538c551fd7SAndreas Gohr     */
548c551fd7SAndreas Gohr    public function addSelectStatement($statement, $alias) {
558c551fd7SAndreas Gohr        $this->select[$alias] = "$statement AS $alias";
568c551fd7SAndreas Gohr    }
578c551fd7SAndreas Gohr
588c551fd7SAndreas Gohr    /**
598c551fd7SAndreas Gohr     * Adds the the table to the FROM statement part
608c551fd7SAndreas Gohr     *
618c551fd7SAndreas Gohr     * @param string $table the table to add
628c551fd7SAndreas Gohr     * @param string $alias alias for the table, blank for table name
638c551fd7SAndreas Gohr     */
648c551fd7SAndreas Gohr    public function addTable($table, $alias = '') {
658c551fd7SAndreas Gohr        if($alias === '') $alias = $table;
668c551fd7SAndreas Gohr        if(isset($this->from[$alias])) throw new StructException('Table Alias exists');
678c551fd7SAndreas Gohr        $this->from[$alias] = "$table AS $alias";
688c551fd7SAndreas Gohr    }
698c551fd7SAndreas Gohr
708c551fd7SAndreas Gohr    /**
718c551fd7SAndreas Gohr     * Adds a LEFT JOIN clause to the FROM statement part, sorted at the correct spot
728c551fd7SAndreas Gohr     *
738c551fd7SAndreas Gohr     * @param string $leftalias the alias of the left table you're joining on, has to exist already
748c551fd7SAndreas Gohr     * @param string $righttable the right table to be joined
758c551fd7SAndreas Gohr     * @param string $rightalias an alias for the right table, blank for table name
768c551fd7SAndreas Gohr     * @param string $onclause the ON clause condition the join is based on
778c551fd7SAndreas Gohr     */
788c551fd7SAndreas Gohr    public function addLeftJoin($leftalias, $righttable, $rightalias, $onclause) {
798c551fd7SAndreas Gohr        if($rightalias === '') $rightalias = $righttable;
808c551fd7SAndreas Gohr        if(!isset($this->from[$leftalias])) throw new StructException('Table Alias does not exist');
818c551fd7SAndreas Gohr        if(isset($this->from[$rightalias])) throw new StructException('Table Alias already exists');
828c551fd7SAndreas Gohr
838c551fd7SAndreas Gohr        $pos = array_search($leftalias, array_keys($this->from));
848c551fd7SAndreas Gohr        $statement = "LEFT OUTER JOIN $righttable AS $rightalias ON $onclause";
858c551fd7SAndreas Gohr        $this->from = $this->array_insert($this->from, array($rightalias => $statement), $pos+1);
868c551fd7SAndreas Gohr    }
878c551fd7SAndreas Gohr
888c551fd7SAndreas Gohr    /**
898c551fd7SAndreas Gohr     * Returns the current WHERE filters and allows to set new ones
908c551fd7SAndreas Gohr     *
918c551fd7SAndreas Gohr     * @return QueryBuilderWhere
928c551fd7SAndreas Gohr     */
938c551fd7SAndreas Gohr    public function getFilters() {
948c551fd7SAndreas Gohr        return $this->where;
958c551fd7SAndreas Gohr    }
968c551fd7SAndreas Gohr
978c551fd7SAndreas Gohr    /**
988c551fd7SAndreas Gohr     * Adds a value to the statement
998c551fd7SAndreas Gohr     *
100df30dbf7SAndreas Gohr     * This function returns the name of the placeholder you have to use in your statement, so whenever
101df30dbf7SAndreas Gohr     * you need to use a user value in a statement, call this first, then add the statement through the
102df30dbf7SAndreas Gohr     * other functions using the returned placeholder.
1038c551fd7SAndreas Gohr     *
1048c551fd7SAndreas Gohr     * @param mixed $value
1058c551fd7SAndreas Gohr     * @return string
1068c551fd7SAndreas Gohr     */
1078c551fd7SAndreas Gohr    public function addValue($value) {
1088c551fd7SAndreas Gohr        static $count = 0;
1098c551fd7SAndreas Gohr        $count++;
1108c551fd7SAndreas Gohr
1118c551fd7SAndreas Gohr        $placeholder = ":!!val$count!!:"; // sqlite plugin does not support named parameters, yet so we have simulate it
1128c551fd7SAndreas Gohr        $this->values[$placeholder] = $value;
1138c551fd7SAndreas Gohr        return $placeholder;
1148c551fd7SAndreas Gohr    }
1158c551fd7SAndreas Gohr
1168c551fd7SAndreas Gohr    /**
1178c551fd7SAndreas Gohr     * Creates a new table alias that has not been used before
1188c551fd7SAndreas Gohr     *
1198c551fd7SAndreas Gohr     * @return string
1208c551fd7SAndreas Gohr     */
1218c551fd7SAndreas Gohr    public function generateTableAlias() {
1228c551fd7SAndreas Gohr        static $count = 0;
1238c551fd7SAndreas Gohr        $count++;
1248c551fd7SAndreas Gohr        return "T$count";
1258c551fd7SAndreas Gohr    }
1268c551fd7SAndreas Gohr
1278c551fd7SAndreas Gohr    /**
1288c551fd7SAndreas Gohr     * Returns the complete SQL statement and the values to apply
1298c551fd7SAndreas Gohr     *
1308c551fd7SAndreas Gohr     * @return array
1318c551fd7SAndreas Gohr     */
1328c551fd7SAndreas Gohr    public function getSQL() {
1338c551fd7SAndreas Gohr        $sql =
1348c551fd7SAndreas Gohr            'SELECT ' . join(",\n", $this->select) . "\n" .
1358c551fd7SAndreas Gohr            '  FROM ' . join(",\n", $this->from) . "\n" .
1368c551fd7SAndreas Gohr            ' WHERE ' . $this->where->toSQL() . "\n";
1378c551fd7SAndreas Gohr
1388c551fd7SAndreas Gohr        return $this->fixPlaceholders($sql);
1398c551fd7SAndreas Gohr    }
1408c551fd7SAndreas Gohr
1418c551fd7SAndreas Gohr    /**
1428c551fd7SAndreas Gohr     * Replaces the named placeholders with ? placeholders
1438c551fd7SAndreas Gohr     *
1448c551fd7SAndreas Gohr     * Until the sqlite plugin can use named placeholder properly
1458c551fd7SAndreas Gohr     *
1468c551fd7SAndreas Gohr     * @param string $sql
1478c551fd7SAndreas Gohr     * @return array
1488c551fd7SAndreas Gohr     */
1498c551fd7SAndreas Gohr    protected function fixPlaceholders($sql) {
1508c551fd7SAndreas Gohr        $vals = array();
1518c551fd7SAndreas Gohr
1528c551fd7SAndreas Gohr        while(preg_match('/(:!!val\d+!!:)/', $sql, $m)) {
1538c551fd7SAndreas Gohr            $pl = $m[1];
154*2b5df26eSAndreas Gohr
155*2b5df26eSAndreas Gohr            if(!isset($this->values[$pl])) throw new StructException('Placeholder not found');
156*2b5df26eSAndreas Gohr
157*2b5df26eSAndreas Gohr            $sql = preg_replace("/$pl/", '?', $sql, 1);
1588c551fd7SAndreas Gohr            $vals[] = $this->values[$pl];
1598c551fd7SAndreas Gohr        }
1608c551fd7SAndreas Gohr
1618c551fd7SAndreas Gohr        return array($sql, $vals);
1628c551fd7SAndreas Gohr    }
1638c551fd7SAndreas Gohr
1648c551fd7SAndreas Gohr    /**
1658c551fd7SAndreas Gohr     * Insert an array into another array at a given position in an associative array
1668c551fd7SAndreas Gohr     *
1678c551fd7SAndreas Gohr     * @param array $array The initial array
1688c551fd7SAndreas Gohr     * @param array $pairs The array to insert
1698c551fd7SAndreas Gohr     * @param string $key_pos The position at which to insert
1708c551fd7SAndreas Gohr     * @link https://gist.github.com/scribu/588429 simplified
1718c551fd7SAndreas Gohr     * @return array
1728c551fd7SAndreas Gohr     */
1738c551fd7SAndreas Gohr    protected function array_insert($array, $pairs, $key_pos) {
1748c551fd7SAndreas Gohr        $result = array_slice($array, 0, $key_pos);
1758c551fd7SAndreas Gohr        $result = array_merge($result, $pairs);
1768c551fd7SAndreas Gohr        $result = array_merge($result, array_slice($array, $key_pos));
1778c551fd7SAndreas Gohr        return $result;
1788c551fd7SAndreas Gohr    }
1798c551fd7SAndreas Gohr}
1808c551fd7SAndreas Gohr
181