diff --git a/CHANGELOG.md b/CHANGELOG.md index e2d08b973..e8142b534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -167,6 +167,9 @@ - Chg #1078: Change type of `$tables` to `array` in `DQLQueryBuilderInterface::buildFrom()` (@vjik) - Chg #1083: Add `pdoStatementExecute()` protected method to `AbstractPdoCommand` class and cleanup `internalExecute()` method (@vjik) +- New #1082: Add `WithQuery` class that holds the information for a single "WITH" query clause (@vjik) +- New #1082: Add `QueryPartsInterface::addWithQuery()` method (@vjik) +- Chg #1082: `QueryPartsInterface::withQuery()` method replace "WITH" clause instead of adding before (@vjik) ## 1.3.0 March 21, 2024 diff --git a/UPGRADE.md b/UPGRADE.md index d32f17244..9d805c0a9 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -142,6 +142,7 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace - `QueryPartsInterface::setFor()` - overwrites the `FOR` part of the query; - `QueryPartsInterface::setWhere()` - overwrites the `WHERE` part of the query; - `QueryPartsInterface::setHaving()` - overwrites the `HAVING` part of the query; +- `QueryPartsInterface::addWithQuery()` - prepends an SQL statement using `WITH` syntax; - `ConnectionInterface::getColumnBuilderClass()` - returns the column builder class name for concrete DBMS; - `ConnectionInterface::getColumnFactory()` - returns the column factory object for concrete DBMS; - `ConnectionInterface::getServerInfo()` - returns `ServerInfoInterface` instance which provides server information; @@ -291,3 +292,7 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace - Remove `Expression::getParams()` method, use `$params` property instead; - Allow `ExpressionInterface` for `$table` and `$on` parameters of `QueryPartsInterface` methods: `join()`, `innerJoin()`, `leftJoin()`, `rightJoin()`; +- `QueryInterface::getWithQueries()` method returns array of `WithQuery` instances; +- `QueryPartsInterface::withQuery()` method replace "WITH" clause instead of adding before; +- Change `QueryPartsInterface::withQueries()` parameter to variadic with type `WithQuery`; +- In `DQLQueryBuilderInterface::buildWithQueries()` method change first parameter type form `array[]` to `WithQuery[]`; diff --git a/src/Query/Query.php b/src/Query/Query.php index 5cd76def8..541044901 100644 --- a/src/Query/Query.php +++ b/src/Query/Query.php @@ -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 @@ -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; } @@ -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; diff --git a/src/Query/QueryInterface.php b/src/Query/QueryInterface.php index 9ea546b91..62b2fb8dc 100644 --- a/src/Query/QueryInterface.php +++ b/src/Query/QueryInterface.php @@ -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; diff --git a/src/Query/QueryPartsInterface.php b/src/Query/QueryPartsInterface.php index 7d0954c29..b571289c9 100644 --- a/src/Query/QueryPartsInterface.php +++ b/src/Query/QueryPartsInterface.php @@ -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. * @@ -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 @@ -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; } diff --git a/src/Query/WithQuery.php b/src/Query/WithQuery.php new file mode 100644 index 000000000..23a96c828 --- /dev/null +++ b/src/Query/WithQuery.php @@ -0,0 +1,30 @@ +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); diff --git a/src/QueryBuilder/AbstractQueryBuilder.php b/src/QueryBuilder/AbstractQueryBuilder.php index 42e8fb7b1..c0f1c4a13 100644 --- a/src/QueryBuilder/AbstractQueryBuilder.php +++ b/src/QueryBuilder/AbstractQueryBuilder.php @@ -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 diff --git a/src/QueryBuilder/DQLQueryBuilderInterface.php b/src/QueryBuilder/DQLQueryBuilderInterface.php index 161d139ec..f4a5f9322 100644 --- a/src/QueryBuilder/DQLQueryBuilderInterface.php +++ b/src/QueryBuilder/DQLQueryBuilderInterface.php @@ -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; @@ -288,7 +289,7 @@ 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 @@ -296,11 +297,11 @@ public function buildWhere( * @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 diff --git a/tests/AbstractQueryBuilderTest.php b/tests/AbstractQueryBuilderTest.php index 9580bd83f..965a66bfa 100644 --- a/tests/AbstractQueryBuilderTest.php +++ b/tests/AbstractQueryBuilderTest.php @@ -814,12 +814,6 @@ public function testBuildUnion(): void ); } - /** - * @throws Exception - * @throws InvalidConfigException - * @throws InvalidArgumentException - * @throws NotSupportedException - */ public function testBuildWithQueries(): void { $db = $this->getConnection(); @@ -838,12 +832,6 @@ public function testBuildWithQueries(): void ); } - /** - * @throws Exception - * @throws InvalidConfigException - * @throws InvalidArgumentException - * @throws NotSupportedException - */ public function testBuildWithComplexSelect(): void { $db = $this->getConnection(); @@ -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(); @@ -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); @@ -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(); @@ -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(); @@ -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(); diff --git a/tests/AbstractQueryTest.php b/tests/AbstractQueryTest.php index 1b16c2a6e..554d86d1c 100644 --- a/tests/AbstractQueryTest.php +++ b/tests/AbstractQueryTest.php @@ -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; @@ -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 diff --git a/tests/Common/CommonQueryTest.php b/tests/Common/CommonQueryTest.php index 3727c5adf..9d995e76a 100644 --- a/tests/Common/CommonQueryTest.php +++ b/tests/Common/CommonQueryTest.php @@ -58,7 +58,7 @@ public function testColumnIndexByWithClosure() $db->close(); } - public function testWithQuery() + public function testWithQuery(): void { $db = $this->getConnection(true); @@ -76,7 +76,7 @@ public function testWithQuery() $db->close(); } - public function testWithQueryRecursive() + public function testWithQueryRecursive(): void { $db = $this->getConnection(); $quoter = $db->getQuoter();