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