AutorÃa | Ultima modificación | Ver Log |
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Builds a query.
*
* @package block_dash
* @copyright 2019 bdecent gmbh <https://bdecent.de>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace block_dash\local\dash_framework\query_builder;
use coding_exception;
use dml_exception;
/**
* Builds a query.
*
* @package block_dash
*/
class builder {
/**
* @var string
*/
private $table;
/**
* @var string
*/
private $tablealias;
/**
* @var string[]
*/
private $selects = [];
/**
* @var array
*/
private $wheres = [];
/**
* @var array
*/
private $rawwhere;
/**
* @var array
*/
private $rawwhereparameters = [];
/**
* @var int Return a subset of records, starting at this point (optional).
*/
private $limitfrom = 0;
/**
* @var int Return a subset comprising this many records in total (optional, required if $limitfrom is set).
*/
private $limitnum = 0;
/**
* @var array ['field1' => 'ASC', 'field2' => 'DESC', ...]
*/
private $orderby = [];
/**
* @var join[]
*/
private $joins = [];
/**
* @var array ['field1', 'field2', ...]
*/
private $groupby = [];
/**
* Extra conditions to be added in WHERE clause.
*
* @var array
*/
private $rawconditions = [];
/**
* @var array
*/
private $rawconditionparameters = [];
/**
* @var array
*/
private $rawjoins = [];
/**
* @var array
*/
private $rawjoinsparameters = [];
/**
* Fields to retried from sql query. Sql select field.
* @param string $field
* @param string $alias
* @return builder
*/
public function select(string $field, string $alias): builder {
$this->selects[$alias] = $field;
return $this;
}
/**
* Set all selects on builder.
*
* @param array $selects [alias => field, ...]
* @return $this
*/
public function set_selects(array $selects): builder {
$this->selects = [];
foreach ($selects as $alias => $select) {
$this->selects[$alias] = $select;
}
return $this;
}
/**
* Set main table of query.
*
* @param string $table
* @param string $alias
* @return builder
*/
public function from(string $table, string $alias): builder {
$this->table = $table;
$this->tablealias = $alias;
return $this;
}
/**
* Join table in query.
*
* @param string $table Table name of joined table.
* @param string $alias Joined table alias.
* @param string $jointablefield Field of joined table to reference in join condition.
* @param string $origintablefield Field of origin table to join to.
* @param string $jointype SQL join type. See self::TYPE_*
* @param array $extraparameters Extra parameters used in join SQL.
* @return $this
*/
public function join(string $table, string $alias, string $jointablefield, string $origintablefield,
$jointype = join::TYPE_INNER_JOIN, array $extraparameters = []): builder {
$this->joins[] = new join($table, $alias, $jointablefield, $origintablefield, $jointype, $extraparameters);
return $this;
}
/**
* Join raw in query.
*
* @param string $joinsql SQL join type. See self::TYPE_*
* @param array $parameters Extra parameters used in join SQL.
* @return $this
*/
public function join_raw(string $joinsql, array $parameters = []): builder {
$this->rawjoins[] = [$joinsql, $parameters];
return $this;
}
/**
* Add additional join condition to existing join.
*
* @param string $alias
* @param string $condition
* @return $this
* @throws coding_exception
*/
public function join_condition(string $alias, string $condition): builder {
$added = false;
foreach ($this->joins as $join) {
if ($join->get_alias() == $alias) {
$join->add_join_condition($condition);
$added = true;
break;
}
}
if (!$added) {
throw new coding_exception('Table alias not found: ' . $alias);
}
return $this;
}
/**
* Add where clause to query.
*
* @param string $selector Field or alias of where clause.
* @param array $values Values that where clause will compare to.
* @param string $operator Equals, greater than, in, etc etc. See where::OPERATOR_*
* @param string $conjunctive AND, OR etc etc. See where::CONJUCTIVE_OPERATOR_*
*
* @return where
*/
public function where(string $selector, array $values, string $operator = where::OPERATOR_EQUAL,
string $conjunctive = where::CONJUNCTIVE_OPERATOR_AND): where {
$where = new where($selector, $values, $operator, $conjunctive);
$this->wheres[] = $where;
return $where;
}
/**
* Add where (in subquery) clause to query.
*
* @param string $selector Field or alias of where clause.
* @param string $query Subquery of WHERE IN (subquery) clause.
* @param array $params Any extra parameters used in subquery.
* @return where
*/
public function where_in_query(string $selector, string $query, array $params = []): where {
$where = new where($selector, []);
$where->set_query($query, $params);
$this->wheres[] = $where;
return $where;
}
/**
* Add where clause to query.
*
* @param string $wheresql
* @param array $parameters
* @return builder
*/
public function where_raw(string $wheresql, array $parameters = []): builder {
$this->rawwhere[] = $wheresql;
$this->rawwhereparameters = array_merge($this->rawwhereparameters, $parameters);
return $this;
}
/**
* Order by a field.
*
* @param string $field Field or alias to order by.
* @param string $direction 'ASC' or 'DESC'
* @return builder
* @throws coding_exception
*/
public function orderby(string $field, string $direction): builder {
if (!in_array(strtolower($direction), ['asc', 'desc'])) {
throw new coding_exception('Invalid order by direction ' . $direction);
}
$this->orderby[$field] = $direction;
return $this;
}
/**
* Remove order by conditions.
*
* @return builder
*/
public function remove_orderby(): builder {
$this->orderby = [];
return $this;
}
/**
* Group by field for aggregations.
*
* @param string $field
* @return builder
*/
public function groupby(string $field): builder {
$this->groupby[] = $field;
return $this;
}
/**
* Add raw condition to builder.
*
* @param string $condition
* @param array $parameters
* @return builder
*/
public function rawcondition(string $condition, array $parameters = []): builder {
$this->rawconditions[] = $condition;
$this->rawconditionparameters = $parameters;
return $this;
}
/**
* Get the query where conditions.
* @return where[]
*/
public function get_wheres(): array {
return $this->wheres;
}
/**
* Get the query limit from.
*
* @return int
*/
public function get_limitfrom(): int {
return $this->limitfrom;
}
/**
* Set the query limit from.
*
* @param int $limitfrom
* @return $this
*/
public function limitfrom(int $limitfrom): builder {
$this->limitfrom = $limitfrom;
return $this;
}
/**
* Get the query limit number.
*
* @return int
*/
public function get_limitnum(): int {
return $this->limitnum;
}
/**
* Set the query limit number.
*
* @param int $limitnum
* @return $this
*/
public function limitnum(int $limitnum): builder {
$this->limitnum = $limitnum;
return $this;
}
/**
* Build and return complete query select SQL.
*
* @return string
*/
protected function build_select(): string {
$selects = [];
foreach ($this->selects as $alias => $select) {
$selects[] = $select . ' AS ' . $alias;
}
return implode(',', $selects);
}
/**
* Get the query where condition and it parameters.
* @return array
* @throws exception\invalid_operator_exception
*/
protected function get_where_sql_and_params(): array {
$wheresql = [];
$params = [];
$wsql = ''; // Where builder queryies.
foreach ($this->get_wheres() as $where) {
[$sql, $wparams] = $where->get_sql_and_params();
$conjunc = $where->get_conjunctive_operator() ?: 'AND';
$wsql .= !empty($wsql) ? sprintf(' %s %s ', $conjunc, $sql) : $sql;
$params = array_merge($params, $wparams);
}
$wheresql[] = $wsql;
if ($this->rawwhere) {
foreach ($this->rawwhere as $where) {
$wheresql[] = $where;
}
$params = array_merge($params, $this->rawwhereparameters);
}
return [implode(' AND ', array_filter($wheresql)), $params];
}
/**
* Get the query and required parameters.
*
* @return array<string, array>
* @throws exception\invalid_operator_exception
*/
final public function get_sql_and_params(): array {
$sql = 'SELECT DISTINCT ' . $this->build_select() . ' FROM {' . $this->table . '} ' . $this->tablealias;
$params = [];
foreach ($this->joins as $join) {
[$jsql, $jparams] = $join->get_sql_and_params();
$sql .= ' ' . $jsql . ' ';
$params = array_merge($params, $jparams);
}
foreach ($this->rawjoins as $join) {
[$jsql, $jparams] = $join;
$sql .= ' ' . $jsql . ' ';
$params = array_merge($params, $jparams);
}
[$wsql, $wparams] = $this->get_where_sql_and_params();
if ($wsql) {
$sql .= ' WHERE ' . $wsql;
$params = array_merge($params, $wparams);
}
if (count($this->groupby) > 0) {
$sql .= ' GROUP BY ' . implode(', ', $this->groupby);
}
if ($this->orderby) {
$orderbys = [];
foreach ($this->orderby as $field => $direction) {
$orderbys[] = sprintf('%s %s', $field, $direction);
}
$sql .= ' ORDER BY ' . implode(', ', $orderbys);
}
return [$sql, $params];
}
/**
* Execute query and return results.
*
* @return array
* @throws dml_exception
* @throws exception\invalid_operator_exception
*/
public function query() {
global $DB;
[$sql, $params] = $this->get_sql_and_params();
return $DB->get_records_sql($sql, $params, $this->get_limitfrom(), $this->get_limitnum());
}
/**
* Get number of records this query will return.
*
* @return int
* @throws dml_exception
* @throws exception\invalid_operator_exception
*/
public function count(): int {
$builder = clone $this;
$builder->set_selects(['count' => 'COUNT(DISTINCT ' . $this->tablealias . '.id)']);
$builder->limitfrom(0)->limitnum(0)->remove_orderby();
if (!$records = $builder->query()) {
return 0;
}
return array_values($records)[0]->count;
}
}