2017-06-04 22:00:18 +00:00
|
|
|
<?php
|
2017-11-17 01:23:18 +00:00
|
|
|
/** @license MIT
|
|
|
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
|
|
|
* See LICENSE and AUTHORS files for details */
|
|
|
|
|
2017-06-04 22:00:18 +00:00
|
|
|
declare(strict_types=1);
|
2017-06-18 14:23:37 +00:00
|
|
|
namespace JKingWeb\Arsse\Misc;
|
2017-06-04 22:00:18 +00:00
|
|
|
|
|
|
|
class Query {
|
2017-07-07 15:49:54 +00:00
|
|
|
protected $qBody = ""; // main query body
|
|
|
|
protected $tBody = []; // main query parameter types
|
|
|
|
protected $vBody = []; // main query parameter values
|
2017-06-04 22:00:18 +00:00
|
|
|
protected $qCTE = []; // Common table expression query components
|
|
|
|
protected $tCTE = []; // Common table expression type bindings
|
|
|
|
protected $vCTE = []; // Common table expression binding values
|
|
|
|
protected $qWhere = []; // WHERE clause components
|
|
|
|
protected $tWhere = []; // WHERE clause type bindings
|
|
|
|
protected $vWhere = []; // WHERE clause binding values
|
2019-02-26 03:41:12 +00:00
|
|
|
protected $qWhereNot = []; // WHERE NOT clause components
|
|
|
|
protected $tWhereNot = []; // WHERE NOT clause type bindings
|
|
|
|
protected $vWhereNot = []; // WHERE NOT clause binding values
|
2018-12-05 01:41:21 +00:00
|
|
|
protected $group = []; // GROUP BY clause components
|
2017-06-04 22:00:18 +00:00
|
|
|
protected $order = []; // ORDER BY clause components
|
|
|
|
protected $limit = 0;
|
|
|
|
protected $offset = 0;
|
|
|
|
|
2017-08-29 14:50:31 +00:00
|
|
|
public function __construct(string $body = "", $types = null, $values = null) {
|
2017-07-07 15:49:54 +00:00
|
|
|
$this->setBody($body, $types, $values);
|
2017-06-04 22:00:18 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:11:03 +00:00
|
|
|
public function setBody(string $body = "", $types = null, $values = null): self {
|
2017-07-07 15:49:54 +00:00
|
|
|
$this->qBody = $body;
|
2017-08-29 14:50:31 +00:00
|
|
|
if (!is_null($types)) {
|
2017-07-07 15:49:54 +00:00
|
|
|
$this->tBody[] = $types;
|
|
|
|
$this->vBody[] = $values;
|
|
|
|
}
|
2019-10-18 17:11:03 +00:00
|
|
|
return $this;
|
2017-07-07 15:49:54 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:11:03 +00:00
|
|
|
public function setCTE(string $tableSpec, string $body, $types = null, $values = null): self {
|
2017-07-07 15:49:54 +00:00
|
|
|
$this->qCTE[] = "$tableSpec as ($body)";
|
2017-08-29 14:50:31 +00:00
|
|
|
if (!is_null($types)) {
|
2017-06-04 22:00:18 +00:00
|
|
|
$this->tCTE[] = $types;
|
|
|
|
$this->vCTE[] = $values;
|
|
|
|
}
|
2019-10-18 17:11:03 +00:00
|
|
|
return $this;
|
2017-10-07 00:26:22 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:11:03 +00:00
|
|
|
public function setWhere(string $where, $types = null, $values = null): self {
|
2017-06-04 22:00:18 +00:00
|
|
|
$this->qWhere[] = $where;
|
2017-08-29 14:50:31 +00:00
|
|
|
if (!is_null($types)) {
|
2017-06-04 22:00:18 +00:00
|
|
|
$this->tWhere[] = $types;
|
|
|
|
$this->vWhere[] = $values;
|
|
|
|
}
|
2019-10-18 17:11:03 +00:00
|
|
|
return $this;
|
2017-06-04 22:00:18 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:11:03 +00:00
|
|
|
public function setWhereNot(string $where, $types = null, $values = null): self {
|
2019-02-26 03:41:12 +00:00
|
|
|
$this->qWhereNot[] = $where;
|
|
|
|
if (!is_null($types)) {
|
|
|
|
$this->tWhereNot[] = $types;
|
|
|
|
$this->vWhereNot[] = $values;
|
|
|
|
}
|
2019-10-18 17:11:03 +00:00
|
|
|
return $this;
|
2019-02-26 03:41:12 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:11:03 +00:00
|
|
|
public function setGroup(string ...$column): self {
|
2018-12-05 01:41:21 +00:00
|
|
|
foreach ($column as $col) {
|
|
|
|
$this->group[] = $col;
|
|
|
|
}
|
2019-10-18 17:11:03 +00:00
|
|
|
return $this;
|
2018-12-05 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:11:03 +00:00
|
|
|
public function setOrder(string ...$order): self {
|
|
|
|
foreach ($order as $o) {
|
|
|
|
$this->order[] = $o;
|
|
|
|
}
|
|
|
|
return $this;
|
2017-06-04 22:00:18 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:11:03 +00:00
|
|
|
public function setLimit(int $limit, int $offset = 0): self {
|
2017-07-07 15:49:54 +00:00
|
|
|
$this->limit = $limit;
|
|
|
|
$this->offset = $offset;
|
2019-10-18 17:11:03 +00:00
|
|
|
return $this;
|
2017-07-07 15:49:54 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:11:03 +00:00
|
|
|
public function pushCTE(string $tableSpec): self {
|
2017-07-07 15:49:54 +00:00
|
|
|
// this function takes the query body and converts it to a common table expression, putting it at the bottom of the existing CTE stack
|
|
|
|
// all WHERE, ORDER BY, and LIMIT parts belong to the new CTE and are removed from the main query
|
2019-02-26 03:41:12 +00:00
|
|
|
$this->setCTE($tableSpec, $this->buildQueryBody(), [$this->tBody, $this->tWhere, $this->tWhereNot], [$this->vBody, $this->vWhere, $this->vWhereNot]);
|
2017-07-07 15:49:54 +00:00
|
|
|
$this->tBody = [];
|
|
|
|
$this->vBody = [];
|
|
|
|
$this->qWhere = [];
|
|
|
|
$this->tWhere = [];
|
|
|
|
$this->vWhere = [];
|
2019-02-26 16:11:42 +00:00
|
|
|
$this->qWhereNot = [];
|
|
|
|
$this->tWhereNot = [];
|
|
|
|
$this->vWhereNot = [];
|
2017-07-07 15:49:54 +00:00
|
|
|
$this->order = [];
|
2018-12-05 01:41:21 +00:00
|
|
|
$this->group = [];
|
2017-08-29 14:50:31 +00:00
|
|
|
$this->setLimit(0, 0);
|
2019-10-18 17:11:03 +00:00
|
|
|
return $this;
|
2017-07-07 15:49:54 +00:00
|
|
|
}
|
|
|
|
|
2017-08-29 14:50:31 +00:00
|
|
|
public function __toString(): string {
|
2017-06-04 22:00:18 +00:00
|
|
|
$out = "";
|
2017-08-29 14:50:31 +00:00
|
|
|
if (sizeof($this->qCTE)) {
|
2017-06-04 22:00:18 +00:00
|
|
|
// start with common table expressions
|
|
|
|
$out .= "WITH RECURSIVE ".implode(", ", $this->qCTE)." ";
|
|
|
|
}
|
|
|
|
// add the body
|
2017-06-18 14:23:37 +00:00
|
|
|
$out .= $this->buildQueryBody();
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
|
2017-08-29 14:50:31 +00:00
|
|
|
public function getQuery(): string {
|
2017-07-07 15:49:54 +00:00
|
|
|
return $this->__toString();
|
|
|
|
}
|
|
|
|
|
2017-08-29 14:50:31 +00:00
|
|
|
public function getTypes(): array {
|
2019-10-18 17:11:03 +00:00
|
|
|
return ValueInfo::flatten([$this->tCTE, $this->tBody, $this->tWhere, $this->tWhereNot]);
|
2017-07-07 15:49:54 +00:00
|
|
|
}
|
|
|
|
|
2017-08-29 14:50:31 +00:00
|
|
|
public function getValues(): array {
|
2019-10-18 17:11:03 +00:00
|
|
|
return ValueInfo::flatten([$this->vCTE, $this->vBody, $this->vWhere, $this->vWhereNot]);
|
2017-06-18 14:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildQueryBody(): string {
|
|
|
|
$out = "";
|
|
|
|
// add the body
|
2017-07-07 15:49:54 +00:00
|
|
|
$out .= $this->qBody;
|
2017-06-04 22:00:18 +00:00
|
|
|
// add any WHERE terms
|
2019-02-26 03:41:12 +00:00
|
|
|
if (sizeof($this->qWhere) || sizeof($this->qWhereNot)) {
|
|
|
|
$where = implode(" AND ", $this->qWhere);
|
|
|
|
$whereNot = implode(" OR ", $this->qWhereNot);
|
|
|
|
$whereNot = strlen($whereNot) ? "NOT ($whereNot)" : "";
|
|
|
|
$where = implode(" AND ", array_filter([$where, $whereNot]));
|
|
|
|
$out .= " WHERE $where";
|
2017-06-04 22:00:18 +00:00
|
|
|
}
|
2018-12-05 01:41:21 +00:00
|
|
|
// add any GROUP BY terms
|
|
|
|
if (sizeof($this->group)) {
|
|
|
|
$out .= " GROUP BY ".implode(", ", $this->group);
|
|
|
|
}
|
2017-06-04 22:00:18 +00:00
|
|
|
// add any ORDER BY terms
|
2017-08-29 14:50:31 +00:00
|
|
|
if (sizeof($this->order)) {
|
2017-06-04 22:00:18 +00:00
|
|
|
$out .= " ORDER BY ".implode(", ", $this->order);
|
|
|
|
}
|
2019-10-18 17:11:03 +00:00
|
|
|
// add LIMIT and OFFSET if either is specified
|
|
|
|
if ($this->limit > 0 || $this->offset > 0) {
|
|
|
|
$out .= " LIMIT ".($this->limit < 1 ? -1 : $this->limit);
|
2017-08-29 14:50:31 +00:00
|
|
|
if ($this->offset > 0) {
|
2017-06-04 22:00:18 +00:00
|
|
|
$out .= " OFFSET ".$this->offset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $out;
|
|
|
|
}
|
2017-08-29 14:50:31 +00:00
|
|
|
}
|