Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/Query/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ class Query implements QueryInterface
/** @psalm-var ResultCallback|null $resultCallback */
protected Closure|null $resultCallback = null;
protected array $union = [];
/** @var WithQuery[] */
protected array $withQueries = [];
/** @psalm-var IndexBy|null $indexBy */
protected Closure|string|null $indexBy = null;
protected ExpressionInterface|int|null $limit = null;
protected ExpressionInterface|int|null $offset = null;
protected array|string|ExpressionInterface|null $where = null;
protected array $with = [];

/**
* @psalm-var list<string>
Expand Down Expand Up @@ -782,13 +782,22 @@ public function withQuery(
ExpressionInterface|string $alias,
bool $recursive = false
): static {
$this->withQueries[] = ['query' => $query, 'alias' => $alias, 'recursive' => $recursive];
$this->withQueries = [new WithQuery($query, $alias, $recursive)];
return $this;
}

public function withQueries(array $withQueries): static
public function addWithQuery(
QueryInterface|string $query,
ExpressionInterface|string $alias,
bool $recursive = false
): static {
$this->withQueries[] = new WithQuery($query, $alias, $recursive);
return $this;
}

public function withQueries(WithQuery ...$queries): static
{
$this->withQueries = $withQueries;
$this->withQueries = $queries;
return $this;
}

Expand All @@ -814,7 +823,6 @@ protected function queryScalar(string|ExpressionInterface $selectExpression): bo
&& empty($this->groupBy)
&& empty($this->having)
&& empty($this->union)
&& empty($this->with)
) {
$select = $this->select;
$order = $this->orderBy;
Expand Down
2 changes: 1 addition & 1 deletion src/Query/QueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public function getUnions(): array;
public function getWhere(): array|string|ExpressionInterface|null;

/**
* @return array The withQueries value.
* @return WithQuery[] The "withQueries" value.
*/
public function getWithQueries(): array;

Expand Down
20 changes: 17 additions & 3 deletions src/Query/QueryPartsInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,20 @@ public function where(array|string|ExpressionInterface|null $condition, array $p
*/
public function setWhere(array|string|ExpressionInterface|null $condition, array $params = []): static;

/**
* Set an SQL statement using `WITH` syntax.
*
* @param QueryInterface|string $query The SQL statement.
* @param ExpressionInterface|string $alias The query alias in `WITH` construction.
* To specify the alias in plain SQL, you may pass an instance of {@see ExpressionInterface}.
* @param bool $recursive Its `true` if using `WITH RECURSIVE` and `false` if using `WITH`.
*/
public function withQuery(
QueryInterface|string $query,
ExpressionInterface|string $alias,
bool $recursive = false
): static;

/**
* Prepends an SQL statement using `WITH` syntax.
*
Expand All @@ -785,7 +799,7 @@ public function setWhere(array|string|ExpressionInterface|null $condition, array
* To specify the alias in plain SQL, you may pass an instance of {@see ExpressionInterface}.
* @param bool $recursive Its `true` if using `WITH RECURSIVE` and `false` if using `WITH`.
*/
public function withQuery(
public function addWithQuery(
QueryInterface|string $query,
ExpressionInterface|string $alias,
bool $recursive = false
Expand All @@ -794,7 +808,7 @@ public function withQuery(
/**
* Specifies the `WITH` query clause for the query.
*
* @param array $withQueries The `WITH` queries to append to the query.
* @param WithQuery ...$queries The `WITH` queries to append to the query.
*/
public function withQueries(array $withQueries): static;
public function withQueries(WithQuery ...$queries): static;
}
30 changes: 30 additions & 0 deletions src/Query/WithQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Query;

use Yiisoft\Db\Expression\ExpressionInterface;

/**
* Represents a common table expression (CTE) for SQL WITH queries.
*
* A data structure holds the information for a single "WITH" query clause.
*
* @see QueryPartsInterface::addWithQuery()
* @see QueryPartsInterface::withQueries()
*/
final class WithQuery
{
/**
* @param QueryInterface|string $query The query to be used as a CTE. It can be a Query object or a raw SQL string.
* @param ExpressionInterface|string $alias The name/alias for the CTE that can be referenced in the main query.
* @param bool $recursive Whether this is a recursive CTE. Default is `false`.
*/
public function __construct(
public readonly QueryInterface|string $query,
public readonly ExpressionInterface|string $alias,
public readonly bool $recursive = false
) {
}
}
19 changes: 10 additions & 9 deletions src/QueryBuilder/AbstractDQLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -422,28 +422,29 @@ public function buildWhere(
return ($where === '') ? '' : ('WHERE ' . $where);
}

public function buildWithQueries(array $withs, array &$params): string
public function buildWithQueries(array $withQueries, array &$params): string
{
if (empty($withs)) {
if (empty($withQueries)) {
return '';
}

$recursive = false;
$result = [];

/** @psalm-var array{query:string|Query, alias:ExpressionInterface|string, recursive:bool}[] $withs */
foreach ($withs as $with) {
if ($with['recursive']) {
foreach ($withQueries as $withQuery) {
if ($withQuery->recursive) {
$recursive = true;
}

if ($with['query'] instanceof QueryInterface) {
[$with['query'], $params] = $this->build($with['query'], $params);
if ($withQuery->query instanceof QueryInterface) {
[$querySql, $params] = $this->build($withQuery->query, $params);
} else {
$querySql = $withQuery->query;
}

$quotedAlias = $this->quoteCteAlias($with['alias']);
$quotedAlias = $this->quoteCteAlias($withQuery->alias);

$result[] = $quotedAlias . ' AS (' . $with['query'] . ')';
$result[] = $quotedAlias . ' AS (' . $querySql . ')';
}

return 'WITH ' . ($recursive ? 'RECURSIVE ' : '') . implode(', ', $result);
Expand Down
4 changes: 2 additions & 2 deletions src/QueryBuilder/AbstractQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,9 @@ public function buildWhere(
return $this->dqlBuilder->buildWhere($condition, $params);
}

public function buildWithQueries(array $withs, array &$params): string
public function buildWithQueries(array $withQueries, array &$params): string
{
return $this->dqlBuilder->buildWithQueries($withs, $params);
return $this->dqlBuilder->buildWithQueries($withQueries, $params);
}

public function checkIntegrity(string $schema = '', string $table = '', bool $check = true): string
Expand Down
7 changes: 4 additions & 3 deletions src/QueryBuilder/DQLQueryBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Query\QueryPartsInterface;
use Yiisoft\Db\Query\WithQuery;
use Yiisoft\Db\QueryBuilder\Condition\ConditionInterface;
use Yiisoft\Db\Query\QueryInterface;

Expand Down Expand Up @@ -288,19 +289,19 @@ public function buildWhere(
): string;

/**
* @param array $withs The `WITH` queries to process.
* @param WithQuery[] $withQueries The `WITH` queries to process.
* @param array $params The binding parameters to populate.
*
* @throws Exception
* @throws InvalidArgumentException
* @throws InvalidConfigException
* @throws NotSupportedException
*
* @return string The `WITH` clause built from {@see \Yiisoft\Db\Query\Query::with}.
* @return string The `WITH` clause built from {@see \Yiisoft\Db\Query\Query::withQuery}.
*
* @psalm-param ParamsType $params
*/
public function buildWithQueries(array $withs, array &$params): string;
public function buildWithQueries(array $withQueries, array &$params): string;

/**
* Transforms one condition defined in array format (as described in {@see \Yiisoft\Db\Query\Query::where()} to
Expand Down
36 changes: 3 additions & 33 deletions tests/AbstractQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -814,12 +814,6 @@ public function testBuildUnion(): void
);
}

/**
* @throws Exception
* @throws InvalidConfigException
* @throws InvalidArgumentException
* @throws NotSupportedException
*/
public function testBuildWithQueries(): void
{
$db = $this->getConnection();
Expand All @@ -838,12 +832,6 @@ public function testBuildWithQueries(): void
);
}

/**
* @throws Exception
* @throws InvalidConfigException
* @throws InvalidArgumentException
* @throws NotSupportedException
*/
public function testBuildWithComplexSelect(): void
{
$db = $this->getConnection();
Expand Down Expand Up @@ -1253,12 +1241,6 @@ public function testBuildWithOrderBy(): void
$this->assertSame([':to' => 4], $params);
}

/**
* @throws Exception
* @throws InvalidConfigException
* @throws InvalidArgumentException
* @throws NotSupportedException
*/
public function testBuildWithQuery(): void
{
$db = $this->getConnection();
Expand All @@ -1269,7 +1251,7 @@ public function testBuildWithQuery(): void
$with3Query = (new Query($db))->select('id')->from('t3')->where('expr = 3');
$query = (new Query($db))
->withQuery($with1Query, 'a1')
->withQuery($with2Query->union($with3Query), 'a2')
->addWithQuery($with2Query->union($with3Query), 'a2')
->from('a2');

[$sql, $params] = $qb->build($query);
Expand All @@ -1286,12 +1268,6 @@ public function testBuildWithQuery(): void
$this->assertSame([], $params);
}

/**
* @throws Exception
* @throws InvalidConfigException
* @throws InvalidArgumentException
* @throws NotSupportedException
*/
public function testBuildWithQueryRecursive(): void
{
$db = $this->getConnection();
Expand All @@ -1316,8 +1292,8 @@ public function testBuildWithQueryRecursive(): void
$this->assertSame([], $params);
}

/** @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::cteAliases */
public function testBuildWithQueryAlias($alias, $expected)
#[DataProviderExternal(QueryBuilderProvider::class, 'cteAliases')]
public function testBuildWithQueryAlias($alias, $expected): void
{
$db = $this->getConnection();
$qb = $db->getQueryBuilder();
Expand All @@ -1337,12 +1313,6 @@ public function testBuildWithQueryAlias($alias, $expected)
$this->assertSame([], $params);
}

/**
* @throws Exception
* @throws InvalidConfigException
* @throws InvalidArgumentException
* @throws NotSupportedException
*/
public function testBuildWithSelectExpression(): void
{
$db = $this->getConnection();
Expand Down
9 changes: 7 additions & 2 deletions tests/AbstractQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\Query\WithQuery;
use Yiisoft\Db\QueryBuilder\Condition\LikeConjunction;
use Yiisoft\Db\Tests\Support\TestTrait;

Expand Down Expand Up @@ -745,10 +746,14 @@ public function testWithQueries(): void
{
$db = $this->getConnection();

$withQueries = [
new WithQuery('query1', 'q1'),
new WithQuery('query2', 'q2'),
];
$query = new Query($db);
$query->withQueries(['query1', 'query2']);
$query->withQueries(...$withQueries);

$this->assertSame(['query1', 'query2'], $query->getWithQueries());
$this->assertSame($withQueries, $query->getWithQueries());
}

public function testColumnWithIndexBy(): void
Expand Down
4 changes: 2 additions & 2 deletions tests/Common/CommonQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function testColumnIndexByWithClosure()
$db->close();
}

public function testWithQuery()
public function testWithQuery(): void
{
$db = $this->getConnection(true);

Expand All @@ -76,7 +76,7 @@ public function testWithQuery()
$db->close();
}

public function testWithQueryRecursive()
public function testWithQueryRecursive(): void
{
$db = $this->getConnection();
$quoter = $db->getQuoter();
Expand Down
Loading