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