xref: /plugin/struct/meta/QueryBuilder.php (revision efcebcd0723daa86fec4d87f544c3627a68d1e1e)
1<?php
2
3namespace dokuwiki\plugin\struct\meta;
4
5/**
6 * Class QueryBuilder
7 * @package dokuwiki\plugin\struct\meta
8 */
9class QueryBuilder {
10
11    /** @var array placeholder -> values */
12    protected $values = array();
13    /** @var array (alias -> statement */
14    protected $select = array();
15    /** @var array (alias -> statement) */
16    protected $from = array();
17    /** @var QueryBuilderWhere */
18    protected $where;
19    /** @var  string[] */
20    protected $orderby;
21    /** @var  string[] */
22    protected $groupby;
23
24    /**
25     * QueryBuilder constructor.
26     */
27    public function __construct() {
28        $this->where = new QueryBuilderWhere();
29    }
30
31    /**
32     * Adds a column to select
33     *
34     * If the alias already exists, the current statement for that alias will be overwritten.
35     *
36     * @param string $tablealias The table to select from
37     * @param string $column The column to select
38     * @param string $alias Under whichname to slect the column. blank for column name
39     */
40    public function addSelectColumn($tablealias, $column, $alias = '') {
41        if($alias === '') $alias = $column;
42        if(!isset($this->from[$tablealias])) throw new StructException('Table Alias does not exist');
43        $this->select[$alias] = "$tablealias.$column AS $alias";
44    }
45
46    /**
47     * Add a new select statement (the column part of it)
48     *
49     * Basically the same as @see addSelectColumn but accepts any statement. This is useful to
50     * select things like fixed strings or more complex function calls, but the correctness will not
51     * be checked.
52     *
53     * If the alias already exists, the current statement for that alias will be overwritten.
54     *
55     * @param string $statement
56     * @param string $alias
57     */
58    public function addSelectStatement($statement, $alias) {
59        $this->select[$alias] = "$statement AS $alias";
60    }
61
62    /**
63     * Adds the the table to the FROM statement part
64     *
65     * @param string $table the table to add
66     * @param string $alias alias for the table, blank for table name
67     */
68    public function addTable($table, $alias = '') {
69        if($alias === '') $alias = $table;
70        if(isset($this->from[$alias])) throw new StructException('Table Alias exists');
71        $this->from[$alias] = "$table AS $alias";
72    }
73
74    /**
75     * Adds a LEFT JOIN clause to the FROM statement part, sorted at the correct spot
76     *
77     * @param string $leftalias the alias of the left table you're joining on, has to exist already
78     * @param string $righttable the right table to be joined
79     * @param string $rightalias an alias for the right table, blank for table name
80     * @param string $onclause the ON clause condition the join is based on
81     */
82    public function addLeftJoin($leftalias, $righttable, $rightalias, $onclause) {
83        if($rightalias === '') $rightalias = $righttable;
84        if(!isset($this->from[$leftalias])) throw new StructException('Table Alias does not exist');
85        if(isset($this->from[$rightalias])) throw new StructException('Table Alias already exists');
86
87        $pos = array_search($leftalias, array_keys($this->from));
88        $statement = "LEFT OUTER JOIN $righttable AS $rightalias ON $onclause";
89        $this->from = $this->array_insert($this->from, array($rightalias => $statement), $pos + 1);
90    }
91
92    /**
93     * Returns the current WHERE filters and allows to set new ones
94     *
95     * @return QueryBuilderWhere
96     */
97    public function getFilters() {
98        return $this->where;
99    }
100
101    /**
102     * Add an ORDER BY clause
103     *
104     * @param string $sort a single sorting condition
105     */
106    public function addOrderBy($sort) {
107        $this->orderby[] = $sort;
108    }
109
110    /**
111     * Add an GROUP BY clause
112     *
113     * @param string $group a single grouping clause
114     */
115    public function addGroupBy($group) {
116        $this->groupby[] = $group;
117    }
118
119    /**
120     * Adds a value to the statement
121     *
122     * This function returns the name of the placeholder you have to use in your statement, so whenever
123     * you need to use a user value in a statement, call this first, then add the statement through the
124     * other functions using the returned placeholder.
125     *
126     * @param mixed $value
127     * @return string
128     */
129    public function addValue($value) {
130        static $count = 0;
131        $count++;
132
133        $placeholder = ":!!val$count!!:"; // sqlite plugin does not support named parameters, yet so we have simulate it
134        $this->values[$placeholder] = $value;
135        return $placeholder;
136    }
137
138    /**
139     * Creates a new table alias that has not been used before
140     *
141     * @return string
142     */
143    public function generateTableAlias() {
144        static $count = 0;
145        $count++;
146        return "T$count";
147    }
148
149    /**
150     * Returns the complete SQL statement and the values to apply
151     *
152     * @return array ($sql, $vals)
153     */
154    public function getSQL() {
155        $sql =
156            ' SELECT ' . join(",\n", $this->select) . "\n" .
157            '   FROM ' . join(",\n", $this->from) . "\n" .
158            '  WHERE ' . $this->where->toSQL() . "\n";
159
160        if($this->orderby) {
161            $sql .=
162                'ORDER BY' . join(",\n", $this->orderby) . "\n";
163        }
164
165        if($this->groupby) {
166            $sql .=
167                'GROUP BY' . join(",\n", $this->groupby) . "\n";
168        }
169
170        return $this->fixPlaceholders($sql);
171    }
172
173    /**
174     * Replaces the named placeholders with ? placeholders
175     *
176     * Until the sqlite plugin can use named placeholder properly
177     *
178     * @param string $sql
179     * @return array
180     */
181    protected function fixPlaceholders($sql) {
182        $vals = array();
183
184        while(preg_match('/(:!!val\d+!!:)/', $sql, $m)) {
185            $pl = $m[1];
186
187            if(!isset($this->values[$pl])) throw new StructException('Placeholder not found');
188
189            $sql = preg_replace("/$pl/", '?', $sql, 1);
190            $vals[] = $this->values[$pl];
191        }
192
193        return array($sql, $vals);
194    }
195
196    /**
197     * Insert an array into another array at a given position in an associative array
198     *
199     * @param array $array The initial array
200     * @param array $pairs The array to insert
201     * @param string $key_pos The position at which to insert
202     * @link https://gist.github.com/scribu/588429 simplified
203     * @return array
204     */
205    protected function array_insert($array, $pairs, $key_pos) {
206        $result = array_slice($array, 0, $key_pos);
207        $result = array_merge($result, $pairs);
208        $result = array_merge($result, array_slice($array, $key_pos));
209        return $result;
210    }
211}
212
213