J. King
8fc31cfc40
This involved changes to the driver interface as well as the database schemata. The most significantly altered queries were for article selection and marking, which relied upon unusual features of SQLite. Overall query efficiency should not be adversely affected (it may have even imprved) in the common case, while very rare cases (not presently triggered by any REST handlers) require more queries. One notable benefit of these changes is that functions which query articles can now have complete control over which columns are returned. This has not, however, been implemented yet: symbolic column groups are still used for now. Note that PostgreSQL still fails many tests, but the test suite runs to completion. Note also that one line of the Database class is not covered; later changes will eventually make it easier to cover the line in question.
196 lines
6 KiB
PHP
196 lines
6 KiB
PHP
<?php
|
|
/** @license MIT
|
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
|
* See LICENSE and AUTHORS files for details */
|
|
|
|
declare(strict_types=1);
|
|
namespace JKingWeb\Arsse\Misc;
|
|
|
|
class Query {
|
|
protected $qBody = ""; // main query body
|
|
protected $tBody = []; // main query parameter types
|
|
protected $vBody = []; // main query parameter values
|
|
protected $qCTE = []; // Common table expression query components
|
|
protected $tCTE = []; // Common table expression type bindings
|
|
protected $vCTE = []; // Common table expression binding values
|
|
protected $jCTE = []; // Common Table Expression joins
|
|
protected $qJoin = []; // JOIN clause components
|
|
protected $tJoin = []; // JOIN clause type bindings
|
|
protected $vJoin = []; // JOIN clause binding values
|
|
protected $qWhere = []; // WHERE clause components
|
|
protected $tWhere = []; // WHERE clause type bindings
|
|
protected $vWhere = []; // WHERE clause binding values
|
|
protected $group = []; // GROUP BY clause components
|
|
protected $order = []; // ORDER BY clause components
|
|
protected $limit = 0;
|
|
protected $offset = 0;
|
|
|
|
|
|
public function __construct(string $body = "", $types = null, $values = null) {
|
|
$this->setBody($body, $types, $values);
|
|
}
|
|
|
|
public function setBody(string $body = "", $types = null, $values = null): bool {
|
|
$this->qBody = $body;
|
|
if (!is_null($types)) {
|
|
$this->tBody[] = $types;
|
|
$this->vBody[] = $values;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function setCTE(string $tableSpec, string $body, $types = null, $values = null, string $join = ''): bool {
|
|
$this->qCTE[] = "$tableSpec as ($body)";
|
|
if (!is_null($types)) {
|
|
$this->tCTE[] = $types;
|
|
$this->vCTE[] = $values;
|
|
}
|
|
if (strlen($join)) { // the CTE might only participate in subqueries rather than a join on the main query
|
|
$this->jCTE[] = $join;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function setJoin(string $join, $types = null, $values = null): bool {
|
|
$this->qJoin[] = $join;
|
|
if (!is_null($types)) {
|
|
$this->tJoin[] = $types;
|
|
$this->vJoin[] = $values;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function setWhere(string $where, $types = null, $values = null): bool {
|
|
$this->qWhere[] = $where;
|
|
if (!is_null($types)) {
|
|
$this->tWhere[] = $types;
|
|
$this->vWhere[] = $values;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function setGroup(string ...$column): bool {
|
|
foreach ($column as $col) {
|
|
$this->group[] = $col;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function setOrder(string $order, bool $prepend = false): bool {
|
|
if ($prepend) {
|
|
array_unshift($this->order, $order);
|
|
} else {
|
|
$this->order[] = $order;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function setLimit(int $limit, int $offset = 0): bool {
|
|
$this->limit = $limit;
|
|
$this->offset = $offset;
|
|
return true;
|
|
}
|
|
|
|
public function pushCTE(string $tableSpec, string $join = ''): bool {
|
|
// 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
|
|
$this->setCTE($tableSpec, $this->buildQueryBody(), [$this->tBody, $this->tWhere], [$this->vBody, $this->vWhere]);
|
|
$this->jCTE = [];
|
|
$this->tBody = [];
|
|
$this->vBody = [];
|
|
$this->qWhere = [];
|
|
$this->tWhere = [];
|
|
$this->vWhere = [];
|
|
$this->qJoin = [];
|
|
$this->tJoin = [];
|
|
$this->vJoin = [];
|
|
$this->order = [];
|
|
$this->group = [];
|
|
$this->setLimit(0, 0);
|
|
if (strlen($join)) {
|
|
$this->jCTE[] = $join;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function __toString(): string {
|
|
$out = "";
|
|
if (sizeof($this->qCTE)) {
|
|
// start with common table expressions
|
|
$out .= "WITH RECURSIVE ".implode(", ", $this->qCTE)." ";
|
|
}
|
|
// add the body
|
|
$out .= $this->buildQueryBody();
|
|
return $out;
|
|
}
|
|
|
|
public function getQuery(): string {
|
|
return $this->__toString();
|
|
}
|
|
|
|
public function getTypes(): array {
|
|
return [$this->tCTE, $this->tBody, $this->tJoin, $this->tWhere];
|
|
}
|
|
|
|
public function getValues(): array {
|
|
return [$this->vCTE, $this->vBody, $this->vJoin, $this->vWhere];
|
|
}
|
|
|
|
public function getJoinTypes(): array {
|
|
return $this->tJoin;
|
|
}
|
|
|
|
public function getJoinValues(): array {
|
|
return $this->vJoin;
|
|
}
|
|
|
|
public function getWhereTypes(): array {
|
|
return $this->tWhere;
|
|
}
|
|
|
|
public function getWhereValues(): array {
|
|
return $this->vWhere;
|
|
}
|
|
|
|
public function getCTETypes(): array {
|
|
return $this->tCTE;
|
|
}
|
|
|
|
public function getCTEValues(): array {
|
|
return $this->vCTE;
|
|
}
|
|
|
|
protected function buildQueryBody(): string {
|
|
$out = "";
|
|
// add the body
|
|
$out .= $this->qBody;
|
|
if (sizeof($this->qCTE)) {
|
|
// add any joins against CTEs
|
|
$out .= " ".implode(" ", $this->jCTE);
|
|
}
|
|
// add any JOINs
|
|
if (sizeof($this->qJoin)) {
|
|
$out .= " ".implode(" ", $this->qJoin);
|
|
}
|
|
// add any WHERE terms
|
|
if (sizeof($this->qWhere)) {
|
|
$out .= " WHERE ".implode(" AND ", $this->qWhere);
|
|
}
|
|
// add any GROUP BY terms
|
|
if (sizeof($this->group)) {
|
|
$out .= " GROUP BY ".implode(", ", $this->group);
|
|
}
|
|
// add any ORDER BY terms
|
|
if (sizeof($this->order)) {
|
|
$out .= " ORDER BY ".implode(", ", $this->order);
|
|
}
|
|
// add LIMIT and OFFSET if the former is specified
|
|
if ($this->limit > 0) {
|
|
$out .= " LIMIT ".$this->limit;
|
|
if ($this->offset > 0) {
|
|
$out .= " OFFSET ".$this->offset;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
}
|