<?php
namespace MailPoetVendor\Doctrine\DBAL\Query;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Doctrine\DBAL\Connection;
use MailPoetVendor\Doctrine\DBAL\Exception;
use MailPoetVendor\Doctrine\DBAL\ForwardCompatibility;
use MailPoetVendor\Doctrine\DBAL\ParameterType;
use MailPoetVendor\Doctrine\DBAL\Query\Expression\CompositeExpression;
use MailPoetVendor\Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use MailPoetVendor\Doctrine\DBAL\Types\Type;
use MailPoetVendor\Doctrine\Deprecations\Deprecation;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_unshift;
use function count;
use function func_get_args;
use function func_num_args;
use function implode;
use function is_array;
use function is_object;
use function key;
use function strtoupper;
use function substr;
class QueryBuilder
{
public const SELECT = 0;
public const DELETE = 1;
public const UPDATE = 2;
public const INSERT = 3;
public const STATE_DIRTY = 0;
public const STATE_CLEAN = 1;
private $connection;
private const SQL_PARTS_DEFAULTS = ['select' => [], 'distinct' => \false, 'from' => [], 'join' => [], 'set' => [], 'where' => null, 'groupBy' => [], 'having' => null, 'orderBy' => [], 'values' => []];
private $sqlParts = self::SQL_PARTS_DEFAULTS;
private $sql;
private $params = [];
private $paramTypes = [];
private $type = self::SELECT;
private $state = self::STATE_CLEAN;
private $firstResult = 0;
private $maxResults;
private $boundCounter = 0;
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
public function expr()
{
return $this->connection->getExpressionBuilder();
}
public function getType()
{
return $this->type;
}
public function getConnection()
{
return $this->connection;
}
public function getState()
{
return $this->state;
}
public function execute()
{
if ($this->type === self::SELECT) {
return ForwardCompatibility\Result::ensure($this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes));
}
return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes);
}
public function getSQL()
{
if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
return $this->sql;
}
switch ($this->type) {
case self::INSERT:
$sql = $this->getSQLForInsert();
break;
case self::DELETE:
$sql = $this->getSQLForDelete();
break;
case self::UPDATE:
$sql = $this->getSQLForUpdate();
break;
case self::SELECT:
default:
$sql = $this->getSQLForSelect();
break;
}
$this->state = self::STATE_CLEAN;
$this->sql = $sql;
return $sql;
}
public function setParameter($key, $value, $type = null)
{
if ($type !== null) {
$this->paramTypes[$key] = $type;
}
$this->params[$key] = $value;
return $this;
}
public function setParameters(array $params, array $types = [])
{
$this->paramTypes = $types;
$this->params = $params;
return $this;
}
public function getParameters()
{
return $this->params;
}
public function getParameter($key)
{
return $this->params[$key] ?? null;
}
public function getParameterTypes()
{
return $this->paramTypes;
}
public function getParameterType($key)
{
return $this->paramTypes[$key] ?? null;
}
public function setFirstResult($firstResult)
{
$this->state = self::STATE_DIRTY;
$this->firstResult = $firstResult;
return $this;
}
public function getFirstResult()
{
return $this->firstResult;
}
public function setMaxResults($maxResults)
{
$this->state = self::STATE_DIRTY;
$this->maxResults = $maxResults;
return $this;
}
public function getMaxResults()
{
return $this->maxResults;
}
public function add($sqlPartName, $sqlPart, $append = \false)
{
$isArray = is_array($sqlPart);
$isMultiple = is_array($this->sqlParts[$sqlPartName]);
if ($isMultiple && !$isArray) {
$sqlPart = [$sqlPart];
}
$this->state = self::STATE_DIRTY;
if ($append) {
if ($sqlPartName === 'orderBy' || $sqlPartName === 'groupBy' || $sqlPartName === 'select' || $sqlPartName === 'set') {
foreach ($sqlPart as $part) {
$this->sqlParts[$sqlPartName][] = $part;
}
} elseif ($isArray && is_array($sqlPart[key($sqlPart)])) {
$key = key($sqlPart);
$this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key];
} elseif ($isMultiple) {
$this->sqlParts[$sqlPartName][] = $sqlPart;
} else {
$this->sqlParts[$sqlPartName] = $sqlPart;
}
return $this;
}
$this->sqlParts[$sqlPartName] = $sqlPart;
return $this;
}
public function select($select = null)
{
$this->type = self::SELECT;
if (empty($select)) {
return $this;
}
if (is_array($select)) {
Deprecation::trigger('doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::select is deprecated, ' . 'pass each value as an individual variadic argument instead.');
}
$selects = is_array($select) ? $select : func_get_args();
return $this->add('select', $selects);
}
public function distinct() : self
{
$this->sqlParts['distinct'] = \true;
return $this;
}
public function addSelect($select = null)
{
$this->type = self::SELECT;
if (empty($select)) {
return $this;
}
if (is_array($select)) {
Deprecation::trigger('doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::addSelect is deprecated, ' . 'pass each value as an individual variadic argument instead.');
}
$selects = is_array($select) ? $select : func_get_args();
return $this->add('select', $selects, \true);
}
public function delete($delete = null, $alias = null)
{
$this->type = self::DELETE;
if (!$delete) {
return $this;
}
return $this->add('from', ['table' => $delete, 'alias' => $alias]);
}
public function update($update = null, $alias = null)
{
$this->type = self::UPDATE;
if (!$update) {
return $this;
}
return $this->add('from', ['table' => $update, 'alias' => $alias]);
}
public function insert($insert = null)
{
$this->type = self::INSERT;
if (!$insert) {
return $this;
}
return $this->add('from', ['table' => $insert]);
}
public function from($from, $alias = null)
{
return $this->add('from', ['table' => $from, 'alias' => $alias], \true);
}
public function join($fromAlias, $join, $alias, $condition = null)
{
return $this->innerJoin($fromAlias, $join, $alias, $condition);
}
public function innerJoin($fromAlias, $join, $alias, $condition = null)
{
return $this->add('join', [$fromAlias => ['joinType' => 'inner', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition]], \true);
}
public function leftJoin($fromAlias, $join, $alias, $condition = null)
{
return $this->add('join', [$fromAlias => ['joinType' => 'left', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition]], \true);
}
public function rightJoin($fromAlias, $join, $alias, $condition = null)
{
return $this->add('join', [$fromAlias => ['joinType' => 'right', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition]], \true);
}
public function set($key, $value)
{
return $this->add('set', $key . ' = ' . $value, \true);
}
public function where($predicates)
{
if (!(func_num_args() === 1 && $predicates instanceof CompositeExpression)) {
$predicates = CompositeExpression::and(...func_get_args());
}
return $this->add('where', $predicates);
}
public function andWhere($where)
{
$args = func_get_args();
$args = array_filter($args);
// https://github.com/doctrine/dbal/issues/4282
$where = $this->getQueryPart('where');
if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) {
if (count($args) > 0) {
$where = $where->with(...$args);
}
} else {
array_unshift($args, $where);
$where = CompositeExpression::and(...$args);
}
return $this->add('where', $where, \true);
}
public function orWhere($where)
{
$args = func_get_args();
$args = array_filter($args);
// https://github.com/doctrine/dbal/issues/4282
$where = $this->getQueryPart('where');
if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) {
if (count($args) > 0) {
$where = $where->with(...$args);
}
} else {
array_unshift($args, $where);
$where = CompositeExpression::or(...$args);
}
return $this->add('where', $where, \true);
}
public function groupBy($groupBy)
{
if (empty($groupBy)) {
return $this;
}
if (is_array($groupBy)) {
Deprecation::trigger('doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::groupBy is deprecated, ' . 'pass each value as an individual variadic argument instead.');
}
$groupBy = is_array($groupBy) ? $groupBy : func_get_args();
return $this->add('groupBy', $groupBy, \false);
}
public function addGroupBy($groupBy)
{
if (empty($groupBy)) {
return $this;
}
if (is_array($groupBy)) {
Deprecation::trigger('doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::addGroupBy is deprecated, ' . 'pass each value as an individual variadic argument instead.');
}
$groupBy = is_array($groupBy) ? $groupBy : func_get_args();
return $this->add('groupBy', $groupBy, \true);
}
public function setValue($column, $value)
{
$this->sqlParts['values'][$column] = $value;
return $this;
}
public function values(array $values)
{
return $this->add('values', $values);
}
public function having($having)
{
if (!(func_num_args() === 1 && $having instanceof CompositeExpression)) {
$having = CompositeExpression::and(...func_get_args());
}
return $this->add('having', $having);
}
public function andHaving($having)
{
$args = func_get_args();
$args = array_filter($args);
// https://github.com/doctrine/dbal/issues/4282
$having = $this->getQueryPart('having');
if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) {
$having = $having->with(...$args);
} else {
array_unshift($args, $having);
$having = CompositeExpression::and(...$args);
}
return $this->add('having', $having);
}
public function orHaving($having)
{
$args = func_get_args();
$args = array_filter($args);
// https://github.com/doctrine/dbal/issues/4282
$having = $this->getQueryPart('having');
if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) {
$having = $having->with(...$args);
} else {
array_unshift($args, $having);
$having = CompositeExpression::or(...$args);
}
return $this->add('having', $having);
}
public function orderBy($sort, $order = null)
{
return $this->add('orderBy', $sort . ' ' . (!$order ? 'ASC' : $order), \false);
}
public function addOrderBy($sort, $order = null)
{
return $this->add('orderBy', $sort . ' ' . (!$order ? 'ASC' : $order), \true);
}
public function getQueryPart($queryPartName)
{
return $this->sqlParts[$queryPartName];
}
public function getQueryParts()
{
return $this->sqlParts;
}
public function resetQueryParts($queryPartNames = null)
{
if ($queryPartNames === null) {
$queryPartNames = array_keys($this->sqlParts);
}
foreach ($queryPartNames as $queryPartName) {
$this->resetQueryPart($queryPartName);
}
return $this;
}
public function resetQueryPart($queryPartName)
{
$this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName];
$this->state = self::STATE_DIRTY;
return $this;
}
private function getSQLForSelect()
{
$query = 'SELECT ' . ($this->sqlParts['distinct'] ? 'DISTINCT ' : '') . implode(', ', $this->sqlParts['select']);
$query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '') . ($this->sqlParts['where'] !== null ? ' WHERE ' . (string) $this->sqlParts['where'] : '') . ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '') . ($this->sqlParts['having'] !== null ? ' HAVING ' . (string) $this->sqlParts['having'] : '') . ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : '');
if ($this->isLimitQuery()) {
return $this->connection->getDatabasePlatform()->modifyLimitQuery($query, $this->maxResults, $this->firstResult);
}
return $query;
}
private function getFromClauses()
{
$fromClauses = [];
$knownAliases = [];
// Loop through all FROM clauses
foreach ($this->sqlParts['from'] as $from) {
if ($from['alias'] === null) {
$tableSql = $from['table'];
$tableReference = $from['table'];
} else {
$tableSql = $from['table'] . ' ' . $from['alias'];
$tableReference = $from['alias'];
}
$knownAliases[$tableReference] = \true;
$fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
}
$this->verifyAllAliasesAreKnown($knownAliases);
return $fromClauses;
}
private function verifyAllAliasesAreKnown(array $knownAliases) : void
{
foreach ($this->sqlParts['join'] as $fromAlias => $joins) {
if (!isset($knownAliases[$fromAlias])) {
throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases));
}
}
}
private function isLimitQuery()
{
return $this->maxResults !== null || $this->firstResult !== 0;
}
private function getSQLForInsert()
{
return 'INSERT INTO ' . $this->sqlParts['from']['table'] . ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' . ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')';
}
private function getSQLForUpdate()
{
$table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
return 'UPDATE ' . $table . ' SET ' . implode(', ', $this->sqlParts['set']) . ($this->sqlParts['where'] !== null ? ' WHERE ' . (string) $this->sqlParts['where'] : '');
}
private function getSQLForDelete()
{
$table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
return 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . (string) $this->sqlParts['where'] : '');
}
public function __toString()
{
return $this->getSQL();
}
public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null)
{
if ($placeHolder === null) {
$this->boundCounter++;
$placeHolder = ':dcValue' . $this->boundCounter;
}
$this->setParameter(substr($placeHolder, 1), $value, $type);
return $placeHolder;
}
public function createPositionalParameter($value, $type = ParameterType::STRING)
{
$this->boundCounter++;
$this->setParameter($this->boundCounter, $value, $type);
return '?';
}
private function getSQLForJoins($fromAlias, array &$knownAliases)
{
$sql = '';
if (isset($this->sqlParts['join'][$fromAlias])) {
foreach ($this->sqlParts['join'][$fromAlias] as $join) {
if (array_key_exists($join['joinAlias'], $knownAliases)) {
throw QueryException::nonUniqueAlias($join['joinAlias'], array_keys($knownAliases));
}
$sql .= ' ' . strtoupper($join['joinType']) . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias'];
if ($join['joinCondition'] !== null) {
$sql .= ' ON ' . $join['joinCondition'];
}
$knownAliases[$join['joinAlias']] = \true;
}
foreach ($this->sqlParts['join'][$fromAlias] as $join) {
$sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases);
}
}
return $sql;
}
public function __clone()
{
foreach ($this->sqlParts as $part => $elements) {
if (is_array($this->sqlParts[$part])) {
foreach ($this->sqlParts[$part] as $idx => $element) {
if (!is_object($element)) {
continue;
}
$this->sqlParts[$part][$idx] = clone $element;
}
} elseif (is_object($elements)) {
$this->sqlParts[$part] = clone $elements;
}
}
foreach ($this->params as $name => $param) {
if (!is_object($param)) {
continue;
}
$this->params[$name] = clone $param;
}
}
}