<?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; } }