xref: /plugin/struct/meta/QueryBuilder.php (revision e65ff35effe42463534119d8b7188a730c4c4f18)
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 */
9d6d97f60SAnna Dabrowskaclass QueryBuilder
10d6d97f60SAnna Dabrowska{
118c551fd7SAndreas Gohr    /** @var array placeholder -> values */
127234bfb1Ssplitbrain    protected $values = [];
138c551fd7SAndreas Gohr    /** @var array (alias -> statement */
147234bfb1Ssplitbrain    protected $select = [];
158c551fd7SAndreas Gohr    /** @var array (alias -> statement) */
167234bfb1Ssplitbrain    protected $from = [];
172f68434dSAndreas Gohr    /** @var array (alias -> "table"|"join") keeps how tables were added, as table or join */
187234bfb1Ssplitbrain    protected $type = [];
198c551fd7SAndreas Gohr    /** @var QueryBuilderWhere */
208c551fd7SAndreas Gohr    protected $where;
2183cb959bSAndreas Gohr    /** @var  string[] */
227234bfb1Ssplitbrain    protected $orderby = [];
2383cb959bSAndreas Gohr    /** @var  string[] */
247234bfb1Ssplitbrain    protected $groupby = [];
258c551fd7SAndreas Gohr
268c551fd7SAndreas Gohr    /**
278c551fd7SAndreas Gohr     * QueryBuilder constructor.
288c551fd7SAndreas Gohr     */
29d6d97f60SAnna Dabrowska    public function __construct()
30d6d97f60SAnna Dabrowska    {
31af993d55SMichael Grosse        $this->where = new QueryBuilderWhere($this);
328c551fd7SAndreas Gohr    }
338c551fd7SAndreas Gohr
348c551fd7SAndreas Gohr    /**
358c551fd7SAndreas Gohr     * Adds a column to select
368c551fd7SAndreas Gohr     *
378c551fd7SAndreas Gohr     * If the alias already exists, the current statement for that alias will be overwritten.
388c551fd7SAndreas Gohr     *
398c551fd7SAndreas Gohr     * @param string $tablealias The table to select from
408c551fd7SAndreas Gohr     * @param string $column The column to select
418c551fd7SAndreas Gohr     * @param string $alias Under whichname to slect the column. blank for column name
428c551fd7SAndreas Gohr     */
43d6d97f60SAnna Dabrowska    public function addSelectColumn($tablealias, $column, $alias = '')
44d6d97f60SAnna Dabrowska    {
458c551fd7SAndreas Gohr        if ($alias === '') $alias = $column;
46aa1f5074SAndreas Gohr        if (!isset($this->from[$tablealias])) {
47aa1f5074SAndreas Gohr            throw new StructException('Table Alias does not exist');
48aa1f5074SAndreas Gohr        }
499ee8a979SAndreas Gohr        $this->select[$alias] = "$tablealias.$column";
508c551fd7SAndreas Gohr    }
518c551fd7SAndreas Gohr
528c551fd7SAndreas Gohr    /**
539ee8a979SAndreas Gohr     * Add a new column selection statement
548c551fd7SAndreas Gohr     *
550549dcc5SAndreas Gohr     * Basically the same as @param string $statement
560549dcc5SAndreas Gohr     * @param string $alias
570549dcc5SAndreas Gohr     * @see addSelectColumn() but accepts any statement. This is useful to
588c551fd7SAndreas Gohr     * select things like fixed strings or more complex function calls, but the correctness will not
598c551fd7SAndreas Gohr     * be checked.
608c551fd7SAndreas Gohr     *
618c551fd7SAndreas Gohr     * If the alias already exists, the current statement for that alias will be overwritten.
628c551fd7SAndreas Gohr     *
638c551fd7SAndreas Gohr     */
64d6d97f60SAnna Dabrowska    public function addSelectStatement($statement, $alias)
65d6d97f60SAnna Dabrowska    {
669ee8a979SAndreas Gohr        $this->select[$alias] = $statement;
679ee8a979SAndreas Gohr    }
689ee8a979SAndreas Gohr
699ee8a979SAndreas Gohr    /**
709ee8a979SAndreas Gohr     * Return an already defined column selection statement based on the alias
719ee8a979SAndreas Gohr     *
729ee8a979SAndreas Gohr     * @param string $alias
739ee8a979SAndreas Gohr     * @return string
749ee8a979SAndreas Gohr     * @throws StructException when the alias does not exist
759ee8a979SAndreas Gohr     */
76d6d97f60SAnna Dabrowska    public function getSelectStatement($alias)
77d6d97f60SAnna Dabrowska    {
789ee8a979SAndreas Gohr        if (!isset($this->select[$alias])) {
799ee8a979SAndreas Gohr            throw new StructException('No such select alias');
809ee8a979SAndreas Gohr        }
819ee8a979SAndreas Gohr
829ee8a979SAndreas Gohr        return $this->select[$alias];
838c551fd7SAndreas Gohr    }
848c551fd7SAndreas Gohr
858c551fd7SAndreas Gohr    /**
868c551fd7SAndreas Gohr     * Adds the the table to the FROM statement part
878c551fd7SAndreas Gohr     *
888c551fd7SAndreas Gohr     * @param string $table the table to add
898c551fd7SAndreas Gohr     * @param string $alias alias for the table, blank for table name
908c551fd7SAndreas Gohr     */
91d6d97f60SAnna Dabrowska    public function addTable($table, $alias = '')
92d6d97f60SAnna Dabrowska    {
938c551fd7SAndreas Gohr        if ($alias === '') $alias = $table;
94aa1f5074SAndreas Gohr        if (isset($this->from[$alias])) {
95aa1f5074SAndreas Gohr            throw new StructException('Table Alias exists');
96aa1f5074SAndreas Gohr        }
978c551fd7SAndreas Gohr        $this->from[$alias] = "$table AS $alias";
982f68434dSAndreas Gohr        $this->type[$alias] = 'table';
998c551fd7SAndreas Gohr    }
1008c551fd7SAndreas Gohr
1018c551fd7SAndreas Gohr    /**
1028c551fd7SAndreas Gohr     * Adds a LEFT JOIN clause to the FROM statement part, sorted at the correct spot
1038c551fd7SAndreas Gohr     *
1048c551fd7SAndreas Gohr     * @param string $leftalias the alias of the left table you're joining on, has to exist already
1058c551fd7SAndreas Gohr     * @param string $righttable the right table to be joined
1068c551fd7SAndreas Gohr     * @param string $rightalias an alias for the right table, blank for table name
1078c551fd7SAndreas Gohr     * @param string $onclause the ON clause condition the join is based on
1088c551fd7SAndreas Gohr     */
109d6d97f60SAnna Dabrowska    public function addLeftJoin($leftalias, $righttable, $rightalias, $onclause)
110d6d97f60SAnna Dabrowska    {
1118c551fd7SAndreas Gohr        if ($rightalias === '') $rightalias = $righttable;
112aa1f5074SAndreas Gohr        if (!isset($this->from[$leftalias])) {
113aa1f5074SAndreas Gohr            throw new StructException('Table Alias does not exist');
114aa1f5074SAndreas Gohr        }
115aa1f5074SAndreas Gohr        if (isset($this->from[$rightalias])) {
116aa1f5074SAndreas Gohr            throw new StructException('Table Alias already exists');
117aa1f5074SAndreas Gohr        }
1188c551fd7SAndreas Gohr
1198c551fd7SAndreas Gohr        $pos = array_search($leftalias, array_keys($this->from));
1208c551fd7SAndreas Gohr        $statement = "LEFT OUTER JOIN $righttable AS $rightalias ON $onclause";
1217234bfb1Ssplitbrain        $this->from = $this->arrayInsert($this->from, [$rightalias => $statement], $pos + 1);
1222f68434dSAndreas Gohr        $this->type[$rightalias] = 'join';
1238c551fd7SAndreas Gohr    }
1248c551fd7SAndreas Gohr
1258c551fd7SAndreas Gohr    /**
1268c551fd7SAndreas Gohr     * Returns the current WHERE filters and allows to set new ones
1278c551fd7SAndreas Gohr     *
1288c551fd7SAndreas Gohr     * @return QueryBuilderWhere
1298c551fd7SAndreas Gohr     */
130d6d97f60SAnna Dabrowska    public function filters()
131d6d97f60SAnna Dabrowska    {
1328c551fd7SAndreas Gohr        return $this->where;
1338c551fd7SAndreas Gohr    }
1348c551fd7SAndreas Gohr
1358c551fd7SAndreas Gohr    /**
13683cb959bSAndreas Gohr     * Add an ORDER BY clause
13783cb959bSAndreas Gohr     *
13883cb959bSAndreas Gohr     * @param string $sort a single sorting condition
13983cb959bSAndreas Gohr     */
140d6d97f60SAnna Dabrowska    public function addOrderBy($sort)
141d6d97f60SAnna Dabrowska    {
14283cb959bSAndreas Gohr        $this->orderby[] = $sort;
14383cb959bSAndreas Gohr    }
14483cb959bSAndreas Gohr
14583cb959bSAndreas Gohr    /**
14683cb959bSAndreas Gohr     * Add an GROUP BY clause
14783cb959bSAndreas Gohr     *
1482f68434dSAndreas Gohr     * @param string $tablealias
1492f68434dSAndreas Gohr     * @param string $column
15083cb959bSAndreas Gohr     */
151d6d97f60SAnna Dabrowska    public function addGroupByColumn($tablealias, $column)
152d6d97f60SAnna Dabrowska    {
153aa1f5074SAndreas Gohr        if (!isset($this->from[$tablealias])) {
154aa1f5074SAndreas Gohr            throw new StructException('Table Alias does not exist');
155aa1f5074SAndreas Gohr        }
1562f68434dSAndreas Gohr        $this->groupby[] = "$tablealias.$column";
1572f68434dSAndreas Gohr    }
1582f68434dSAndreas Gohr
1592f68434dSAndreas Gohr    /**
1602f68434dSAndreas Gohr     * Add an GROUP BY clause
1612f68434dSAndreas Gohr     *
1620549dcc5SAndreas Gohr     * Like @param string $statement a single grouping clause
1630549dcc5SAndreas Gohr     * @see addGroupByColumn but accepts an arbitrary statement
1642f68434dSAndreas Gohr     *
1652f68434dSAndreas Gohr     */
166d6d97f60SAnna Dabrowska    public function addGroupByStatement($statement)
167d6d97f60SAnna Dabrowska    {
1682f68434dSAndreas Gohr        $this->groupby[] = $statement;
16983cb959bSAndreas Gohr    }
17083cb959bSAndreas Gohr
17183cb959bSAndreas Gohr    /**
1728c551fd7SAndreas Gohr     * Adds a value to the statement
1738c551fd7SAndreas Gohr     *
174df30dbf7SAndreas Gohr     * This function returns the name of the placeholder you have to use in your statement, so whenever
175df30dbf7SAndreas Gohr     * you need to use a user value in a statement, call this first, then add the statement through the
176df30dbf7SAndreas Gohr     * other functions using the returned placeholder.
1778c551fd7SAndreas Gohr     *
1788c551fd7SAndreas Gohr     * @param mixed $value
1798c551fd7SAndreas Gohr     * @return string
1808c551fd7SAndreas Gohr     */
181d6d97f60SAnna Dabrowska    public function addValue($value)
182d6d97f60SAnna Dabrowska    {
1838c551fd7SAndreas Gohr        static $count = 0;
1848c551fd7SAndreas Gohr        $count++;
1858c551fd7SAndreas Gohr
186*e65ff35eSAnna Dabrowska        $placeholder = ":val$count";
1878c551fd7SAndreas Gohr        $this->values[$placeholder] = $value;
1888c551fd7SAndreas Gohr        return $placeholder;
1898c551fd7SAndreas Gohr    }
1908c551fd7SAndreas Gohr
1918c551fd7SAndreas Gohr    /**
1928c551fd7SAndreas Gohr     * Creates a new table alias that has not been used before
1938c551fd7SAndreas Gohr     *
194db7970caSAndreas Gohr     * @param string $prefix the prefix for the alias, helps with readability of the SQL
1958c551fd7SAndreas Gohr     * @return string
1968c551fd7SAndreas Gohr     */
197d6d97f60SAnna Dabrowska    public function generateTableAlias($prefix = 'T')
198d6d97f60SAnna Dabrowska    {
1998c551fd7SAndreas Gohr        static $count = 0;
2008c551fd7SAndreas Gohr        $count++;
201db7970caSAndreas Gohr        return $prefix . $count;
2028c551fd7SAndreas Gohr    }
2038c551fd7SAndreas Gohr
2048c551fd7SAndreas Gohr    /**
2058c551fd7SAndreas Gohr     * Returns the complete SQL statement and the values to apply
2068c551fd7SAndreas Gohr     *
20783cb959bSAndreas Gohr     * @return array ($sql, $vals)
2088c551fd7SAndreas Gohr     */
209d6d97f60SAnna Dabrowska    public function getSQL()
210d6d97f60SAnna Dabrowska    {
2112f68434dSAndreas Gohr        // FROM needs commas only for tables, not joins
2122f68434dSAndreas Gohr        $from = '';
2132f68434dSAndreas Gohr        foreach ($this->from as $alias => $statement) {
2142f68434dSAndreas Gohr            if ($this->type[$alias] == 'table' && $from) {
2152f68434dSAndreas Gohr                $from .= ",\n";
2162f68434dSAndreas Gohr            } else {
2172f68434dSAndreas Gohr                $from .= "\n";
2182f68434dSAndreas Gohr            }
2192f68434dSAndreas Gohr
2202f68434dSAndreas Gohr            $from .= $statement;
2212f68434dSAndreas Gohr        }
2222f68434dSAndreas Gohr
2239ee8a979SAndreas Gohr        // prepare aliases for the select columns
2247234bfb1Ssplitbrain        $selects = [];
2259ee8a979SAndreas Gohr        foreach ($this->select as $alias => $select) {
2269ee8a979SAndreas Gohr            $selects[] = "$select AS $alias";
2279ee8a979SAndreas Gohr        }
2289ee8a979SAndreas Gohr
2298c551fd7SAndreas Gohr        $sql =
2307234bfb1Ssplitbrain            ' SELECT ' . implode(",\n", $selects) . "\n" .
2312f68434dSAndreas Gohr            '   FROM ' . $from . "\n" .
2328c551fd7SAndreas Gohr            '  WHERE ' . $this->where->toSQL() . "\n";
2338c551fd7SAndreas Gohr
23483cb959bSAndreas Gohr        if ($this->groupby) {
23583cb959bSAndreas Gohr            $sql .=
2367234bfb1Ssplitbrain                'GROUP BY ' . implode(",\n", $this->groupby) . "\n";
23783cb959bSAndreas Gohr        }
23883cb959bSAndreas Gohr
2392f68434dSAndreas Gohr        if ($this->orderby) {
2402f68434dSAndreas Gohr            $sql .=
2417234bfb1Ssplitbrain                'ORDER BY ' . implode(",\n", $this->orderby) . "\n";
2422f68434dSAndreas Gohr        }
2432f68434dSAndreas Gohr
244*e65ff35eSAnna Dabrowska        return [$sql, $this->values];
2458c551fd7SAndreas Gohr    }
2468c551fd7SAndreas Gohr
2478c551fd7SAndreas Gohr    /**
2488c551fd7SAndreas Gohr     * Insert an array into another array at a given position in an associative array
2498c551fd7SAndreas Gohr     *
2508c551fd7SAndreas Gohr     * @param array $array The initial array
2518c551fd7SAndreas Gohr     * @param array $pairs The array to insert
2528c551fd7SAndreas Gohr     * @param string $key_pos The position at which to insert
2538c551fd7SAndreas Gohr     * @return array
2540549dcc5SAndreas Gohr     * @link https://gist.github.com/scribu/588429 simplified
2558c551fd7SAndreas Gohr     */
256748e747fSAnna Dabrowska    protected function arrayInsert($array, $pairs, $key_pos)
257d6d97f60SAnna Dabrowska    {
2588c551fd7SAndreas Gohr        $result = array_slice($array, 0, $key_pos);
2598c551fd7SAndreas Gohr        $result = array_merge($result, $pairs);
2608c551fd7SAndreas Gohr        $result = array_merge($result, array_slice($array, $key_pos));
2618c551fd7SAndreas Gohr        return $result;
2628c551fd7SAndreas Gohr    }
2638c551fd7SAndreas Gohr}
264