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(); 172f68434dSAndreas Gohr /** @var array (alias -> "table"|"join") keeps how tables were added, as table or join */ 182f68434dSAndreas Gohr protected $type = array(); 198c551fd7SAndreas Gohr /** @var QueryBuilderWhere */ 208c551fd7SAndreas Gohr protected $where; 2183cb959bSAndreas Gohr /** @var string[] */ 222f68434dSAndreas Gohr protected $orderby = array(); 2383cb959bSAndreas Gohr /** @var string[] */ 242f68434dSAndreas Gohr protected $groupby = array(); 258c551fd7SAndreas Gohr 268c551fd7SAndreas Gohr /** 278c551fd7SAndreas Gohr * QueryBuilder constructor. 288c551fd7SAndreas Gohr */ 298c551fd7SAndreas Gohr public function __construct() { 308c551fd7SAndreas Gohr $this->where = new QueryBuilderWhere(); 318c551fd7SAndreas Gohr } 328c551fd7SAndreas Gohr 338c551fd7SAndreas Gohr /** 348c551fd7SAndreas Gohr * Adds a column to select 358c551fd7SAndreas Gohr * 368c551fd7SAndreas Gohr * If the alias already exists, the current statement for that alias will be overwritten. 378c551fd7SAndreas Gohr * 388c551fd7SAndreas Gohr * @param string $tablealias The table to select from 398c551fd7SAndreas Gohr * @param string $column The column to select 408c551fd7SAndreas Gohr * @param string $alias Under whichname to slect the column. blank for column name 418c551fd7SAndreas Gohr */ 428c551fd7SAndreas Gohr public function addSelectColumn($tablealias, $column, $alias = '') { 438c551fd7SAndreas Gohr if($alias === '') $alias = $column; 44aa1f5074SAndreas Gohr if(!isset($this->from[$tablealias])) { 45aa1f5074SAndreas Gohr throw new StructException('Table Alias does not exist'); 46aa1f5074SAndreas Gohr } 479ee8a979SAndreas Gohr $this->select[$alias] = "$tablealias.$column"; 488c551fd7SAndreas Gohr } 498c551fd7SAndreas Gohr 508c551fd7SAndreas Gohr /** 519ee8a979SAndreas Gohr * Add a new column selection statement 528c551fd7SAndreas Gohr * 539ee8a979SAndreas Gohr * Basically the same as @see addSelectColumn() but accepts any statement. This is useful to 548c551fd7SAndreas Gohr * select things like fixed strings or more complex function calls, but the correctness will not 558c551fd7SAndreas Gohr * be checked. 568c551fd7SAndreas Gohr * 578c551fd7SAndreas Gohr * If the alias already exists, the current statement for that alias will be overwritten. 588c551fd7SAndreas Gohr * 598c551fd7SAndreas Gohr * @param string $statement 608c551fd7SAndreas Gohr * @param string $alias 618c551fd7SAndreas Gohr */ 628c551fd7SAndreas Gohr public function addSelectStatement($statement, $alias) { 639ee8a979SAndreas Gohr $this->select[$alias] = $statement; 649ee8a979SAndreas Gohr } 659ee8a979SAndreas Gohr 669ee8a979SAndreas Gohr /** 679ee8a979SAndreas Gohr * Return an already defined column selection statement based on the alias 689ee8a979SAndreas Gohr * 699ee8a979SAndreas Gohr * @param string $alias 709ee8a979SAndreas Gohr * @return string 719ee8a979SAndreas Gohr * @throws StructException when the alias does not exist 729ee8a979SAndreas Gohr */ 739ee8a979SAndreas Gohr public function getSelectStatement($alias) { 749ee8a979SAndreas Gohr if(!isset($this->select[$alias])) { 759ee8a979SAndreas Gohr throw new StructException('No such select alias'); 769ee8a979SAndreas Gohr } 779ee8a979SAndreas Gohr 789ee8a979SAndreas Gohr return $this->select[$alias]; 798c551fd7SAndreas Gohr } 808c551fd7SAndreas Gohr 818c551fd7SAndreas Gohr /** 828c551fd7SAndreas Gohr * Adds the the table to the FROM statement part 838c551fd7SAndreas Gohr * 848c551fd7SAndreas Gohr * @param string $table the table to add 858c551fd7SAndreas Gohr * @param string $alias alias for the table, blank for table name 868c551fd7SAndreas Gohr */ 878c551fd7SAndreas Gohr public function addTable($table, $alias = '') { 888c551fd7SAndreas Gohr if($alias === '') $alias = $table; 89aa1f5074SAndreas Gohr if(isset($this->from[$alias])) { 90aa1f5074SAndreas Gohr throw new StructException('Table Alias exists'); 91aa1f5074SAndreas Gohr } 928c551fd7SAndreas Gohr $this->from[$alias] = "$table AS $alias"; 932f68434dSAndreas Gohr $this->type[$alias] = 'table'; 948c551fd7SAndreas Gohr } 958c551fd7SAndreas Gohr 968c551fd7SAndreas Gohr /** 978c551fd7SAndreas Gohr * Adds a LEFT JOIN clause to the FROM statement part, sorted at the correct spot 988c551fd7SAndreas Gohr * 998c551fd7SAndreas Gohr * @param string $leftalias the alias of the left table you're joining on, has to exist already 1008c551fd7SAndreas Gohr * @param string $righttable the right table to be joined 1018c551fd7SAndreas Gohr * @param string $rightalias an alias for the right table, blank for table name 1028c551fd7SAndreas Gohr * @param string $onclause the ON clause condition the join is based on 1038c551fd7SAndreas Gohr */ 1048c551fd7SAndreas Gohr public function addLeftJoin($leftalias, $righttable, $rightalias, $onclause) { 1058c551fd7SAndreas Gohr if($rightalias === '') $rightalias = $righttable; 106aa1f5074SAndreas Gohr if(!isset($this->from[$leftalias])) { 107aa1f5074SAndreas Gohr throw new StructException('Table Alias does not exist'); 108aa1f5074SAndreas Gohr } 109aa1f5074SAndreas Gohr if(isset($this->from[$rightalias])) { 110aa1f5074SAndreas Gohr throw new StructException('Table Alias already exists'); 111aa1f5074SAndreas Gohr } 1128c551fd7SAndreas Gohr 1138c551fd7SAndreas Gohr $pos = array_search($leftalias, array_keys($this->from)); 1148c551fd7SAndreas Gohr $statement = "LEFT OUTER JOIN $righttable AS $rightalias ON $onclause"; 1158c551fd7SAndreas Gohr $this->from = $this->array_insert($this->from, array($rightalias => $statement), $pos + 1); 1162f68434dSAndreas Gohr $this->type[$rightalias] = 'join'; 1178c551fd7SAndreas Gohr } 1188c551fd7SAndreas Gohr 1198c551fd7SAndreas Gohr /** 1208c551fd7SAndreas Gohr * Returns the current WHERE filters and allows to set new ones 1218c551fd7SAndreas Gohr * 1228c551fd7SAndreas Gohr * @return QueryBuilderWhere 1238c551fd7SAndreas Gohr */ 1242f68434dSAndreas Gohr public function filters() { 1258c551fd7SAndreas Gohr return $this->where; 1268c551fd7SAndreas Gohr } 1278c551fd7SAndreas Gohr 1288c551fd7SAndreas Gohr /** 12983cb959bSAndreas Gohr * Add an ORDER BY clause 13083cb959bSAndreas Gohr * 13183cb959bSAndreas Gohr * @param string $sort a single sorting condition 13283cb959bSAndreas Gohr */ 13383cb959bSAndreas Gohr public function addOrderBy($sort) { 13483cb959bSAndreas Gohr $this->orderby[] = $sort; 13583cb959bSAndreas Gohr } 13683cb959bSAndreas Gohr 13783cb959bSAndreas Gohr /** 13883cb959bSAndreas Gohr * Add an GROUP BY clause 13983cb959bSAndreas Gohr * 1402f68434dSAndreas Gohr * @param string $tablealias 1412f68434dSAndreas Gohr * @param string $column 14283cb959bSAndreas Gohr */ 1432f68434dSAndreas Gohr public function addGroupByColumn($tablealias, $column) { 144aa1f5074SAndreas Gohr if(!isset($this->from[$tablealias])) { 145aa1f5074SAndreas Gohr throw new StructException('Table Alias does not exist'); 146aa1f5074SAndreas Gohr } 1472f68434dSAndreas Gohr $this->groupby[] = "$tablealias.$column"; 1482f68434dSAndreas Gohr } 1492f68434dSAndreas Gohr 1502f68434dSAndreas Gohr /** 1512f68434dSAndreas Gohr * Add an GROUP BY clause 1522f68434dSAndreas Gohr * 1532f68434dSAndreas Gohr * Like @see addGroupByColumn but accepts an arbitrary statement 1542f68434dSAndreas Gohr * 1552f68434dSAndreas Gohr * @param string $statement a single grouping clause 1562f68434dSAndreas Gohr */ 1572f68434dSAndreas Gohr public function addGroupByStatement($statement) { 1582f68434dSAndreas Gohr $this->groupby[] = $statement; 15983cb959bSAndreas Gohr } 16083cb959bSAndreas Gohr 16183cb959bSAndreas Gohr /** 1628c551fd7SAndreas Gohr * Adds a value to the statement 1638c551fd7SAndreas Gohr * 164df30dbf7SAndreas Gohr * This function returns the name of the placeholder you have to use in your statement, so whenever 165df30dbf7SAndreas Gohr * you need to use a user value in a statement, call this first, then add the statement through the 166df30dbf7SAndreas Gohr * other functions using the returned placeholder. 1678c551fd7SAndreas Gohr * 1688c551fd7SAndreas Gohr * @param mixed $value 1698c551fd7SAndreas Gohr * @return string 1708c551fd7SAndreas Gohr */ 1718c551fd7SAndreas Gohr public function addValue($value) { 1728c551fd7SAndreas Gohr static $count = 0; 1738c551fd7SAndreas Gohr $count++; 1748c551fd7SAndreas Gohr 1758c551fd7SAndreas Gohr $placeholder = ":!!val$count!!:"; // sqlite plugin does not support named parameters, yet so we have simulate it 1768c551fd7SAndreas Gohr $this->values[$placeholder] = $value; 1778c551fd7SAndreas Gohr return $placeholder; 1788c551fd7SAndreas Gohr } 1798c551fd7SAndreas Gohr 1808c551fd7SAndreas Gohr /** 1818c551fd7SAndreas Gohr * Creates a new table alias that has not been used before 1828c551fd7SAndreas Gohr * 1838c551fd7SAndreas Gohr * @return string 1848c551fd7SAndreas Gohr */ 1858c551fd7SAndreas Gohr public function generateTableAlias() { 1868c551fd7SAndreas Gohr static $count = 0; 1878c551fd7SAndreas Gohr $count++; 1888c551fd7SAndreas Gohr return "T$count"; 1898c551fd7SAndreas Gohr } 1908c551fd7SAndreas Gohr 1918c551fd7SAndreas Gohr /** 1928c551fd7SAndreas Gohr * Returns the complete SQL statement and the values to apply 1938c551fd7SAndreas Gohr * 19483cb959bSAndreas Gohr * @return array ($sql, $vals) 1958c551fd7SAndreas Gohr */ 1968c551fd7SAndreas Gohr public function getSQL() { 1972f68434dSAndreas Gohr // FROM needs commas only for tables, not joins 1982f68434dSAndreas Gohr $from = ''; 1992f68434dSAndreas Gohr foreach($this->from as $alias => $statement) { 2002f68434dSAndreas Gohr if($this->type[$alias] == 'table' && $from) { 2012f68434dSAndreas Gohr $from .= ",\n"; 2022f68434dSAndreas Gohr } else { 2032f68434dSAndreas Gohr $from .= "\n"; 2042f68434dSAndreas Gohr } 2052f68434dSAndreas Gohr 2062f68434dSAndreas Gohr $from .= $statement; 2072f68434dSAndreas Gohr } 2082f68434dSAndreas Gohr 2099ee8a979SAndreas Gohr // prepare aliases for the select columns 2109ee8a979SAndreas Gohr $selects = array(); 2119ee8a979SAndreas Gohr foreach($this->select as $alias => $select) { 2129ee8a979SAndreas Gohr $selects[] = "$select AS $alias"; 2139ee8a979SAndreas Gohr } 2149ee8a979SAndreas Gohr 2158c551fd7SAndreas Gohr $sql = 2169ee8a979SAndreas Gohr ' SELECT ' . join(",\n", $selects) . "\n" . 2172f68434dSAndreas Gohr ' FROM ' . $from . "\n" . 2188c551fd7SAndreas Gohr ' WHERE ' . $this->where->toSQL() . "\n"; 2198c551fd7SAndreas Gohr 22083cb959bSAndreas Gohr if($this->groupby) { 22183cb959bSAndreas Gohr $sql .= 22283cb959bSAndreas Gohr 'GROUP BY ' . join(",\n", $this->groupby) . "\n"; 22383cb959bSAndreas Gohr } 22483cb959bSAndreas Gohr 2252f68434dSAndreas Gohr if($this->orderby) { 2262f68434dSAndreas Gohr $sql .= 2272f68434dSAndreas Gohr 'ORDER BY ' . join(",\n", $this->orderby) . "\n"; 2282f68434dSAndreas Gohr } 2292f68434dSAndreas Gohr 2308c551fd7SAndreas Gohr return $this->fixPlaceholders($sql); 2318c551fd7SAndreas Gohr } 2328c551fd7SAndreas Gohr 2338c551fd7SAndreas Gohr /** 2348c551fd7SAndreas Gohr * Replaces the named placeholders with ? placeholders 2358c551fd7SAndreas Gohr * 2368c551fd7SAndreas Gohr * Until the sqlite plugin can use named placeholder properly 2378c551fd7SAndreas Gohr * 2388c551fd7SAndreas Gohr * @param string $sql 2398c551fd7SAndreas Gohr * @return array 2408c551fd7SAndreas Gohr */ 2418c551fd7SAndreas Gohr protected function fixPlaceholders($sql) { 2428c551fd7SAndreas Gohr $vals = array(); 2438c551fd7SAndreas Gohr 2448c551fd7SAndreas Gohr while(preg_match('/(:!!val\d+!!:)/', $sql, $m)) { 2458c551fd7SAndreas Gohr $pl = $m[1]; 2462b5df26eSAndreas Gohr 247*45664274SAndreas Gohr if(!array_key_exists($pl, $this->values)) { 248aa1f5074SAndreas Gohr throw new StructException('Placeholder not found'); 249aa1f5074SAndreas Gohr } 2502b5df26eSAndreas Gohr 2512b5df26eSAndreas Gohr $sql = preg_replace("/$pl/", '?', $sql, 1); 2528c551fd7SAndreas Gohr $vals[] = $this->values[$pl]; 2538c551fd7SAndreas Gohr } 2548c551fd7SAndreas Gohr 2558c551fd7SAndreas Gohr return array($sql, $vals); 2568c551fd7SAndreas Gohr } 2578c551fd7SAndreas Gohr 2588c551fd7SAndreas Gohr /** 2598c551fd7SAndreas Gohr * Insert an array into another array at a given position in an associative array 2608c551fd7SAndreas Gohr * 2618c551fd7SAndreas Gohr * @param array $array The initial array 2628c551fd7SAndreas Gohr * @param array $pairs The array to insert 2638c551fd7SAndreas Gohr * @param string $key_pos The position at which to insert 2648c551fd7SAndreas Gohr * @link https://gist.github.com/scribu/588429 simplified 2658c551fd7SAndreas Gohr * @return array 2668c551fd7SAndreas Gohr */ 2678c551fd7SAndreas Gohr protected function array_insert($array, $pairs, $key_pos) { 2688c551fd7SAndreas Gohr $result = array_slice($array, 0, $key_pos); 2698c551fd7SAndreas Gohr $result = array_merge($result, $pairs); 2708c551fd7SAndreas Gohr $result = array_merge($result, array_slice($array, $key_pos)); 2718c551fd7SAndreas Gohr return $result; 2728c551fd7SAndreas Gohr } 2738c551fd7SAndreas Gohr} 2748c551fd7SAndreas Gohr 275