Skip to content

Commit

Permalink
Allow scalar values for Query::select() (#816)
Browse files Browse the repository at this point in the history
* Allow scalar values for `Query::select()`

* Apply fixes from StyleCI

* Apply Rector changes (CI)

* Apply fixes from StyleCI

* Update CHANGELOG.md and UPGRADE.md [skip ci]

* Update CHANGELOG.md and UPGRADE.md [skip ci]

* Update UPGRADE.md [skip ci]

* Update UPGRADE.md [skip ci]

* fix UPGRADE.md

* improve `UPGRADE.md`

* Improve psalm types for scalar values in select (#818)

* Improve psalm types for scalar values in select

* Remove unnecessary psalm annotations

---------

Co-authored-by: Tigrov <[email protected]>

* Improve phpdoc [skip ci]

* Update tests/Provider/QueryBuilderProvider.php

Co-authored-by: Sergei Predvoditelev <[email protected]>

* Fix test

* Add type string to test

---------

Co-authored-by: StyleCI Bot <[email protected]>
Co-authored-by: Tigrov <[email protected]>
Co-authored-by: Sergei Predvoditelev <[email protected]>
  • Loading branch information
4 people authored Apr 13, 2024
1 parent 789523f commit 3f977a5
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 2.0.0 under development

- Enh #816: Allow scalar values for `$columns` parameter of `Query::select()` and `Query::addSelect()` methods (@Tigrov)
- Enh #806: Non-unique placeholder names inside `Expression::$params` will be replaced with unique names (@Tigrov)
- Enh #806: Build `Expression` instances inside `Expression::$params` when build a query using `QueryBuilder` (@Tigrov)
- Enh #766: Allow `ColumnInterface` as column type. (@Tigrov)
Expand Down
8 changes: 8 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ in `addColumn()` method of your classes that implement the following interfaces:
- `Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder`;
- `Yiisoft\Db\QueryBuilder\AbstractQueryBuilder`.

### Scalar values for columns in `Query`

Change `$columns` parameter type from `array|string|ExpressionInterface` to `array|bool|float|int|string|ExpressionInterface`
in methods `select()` and `addSelect()` of your classes that implement `Yiisoft\Db\Query\QueryPartsInterface`.

Add support any scalar values for `$columns` parameter of these methods in your classes that implement
`Yiisoft\Db\Query\QueryPartsInterface` or inherit `Yiisoft\Db\Query\Query`.

### Build `Expression` instances inside `Expression::$params`

`ExpressionBuilder` is replaced by an abstract class `AbstractExpressionBuilder` with an instance of the
Expand Down
27 changes: 16 additions & 11 deletions src/Query/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use function array_shift;
use function array_unshift;
use function count;
use function gettype;
use function is_array;
use function is_int;
use function is_numeric;
Expand Down Expand Up @@ -66,9 +67,12 @@
* ```
*
* Query internally uses the {@see \Yiisoft\Db\QueryBuilder\AbstractQueryBuilder} class to generate the SQL statement.
*
* @psalm-import-type SelectValue from QueryPartsInterface
*/
class Query implements QueryInterface
{
/** @psalm-var SelectValue $select */
protected array $select = [];
protected string|null $selectOption = null;
protected bool|null $distinct = null;
Expand Down Expand Up @@ -178,7 +182,7 @@ public function andHaving(array|string|ExpressionInterface $condition, array $pa
return $this;
}

public function addSelect(array|string|ExpressionInterface $columns): static
public function addSelect(array|bool|float|int|string|ExpressionInterface $columns): static
{
if ($this->select === []) {
return $this->select($columns);
Expand Down Expand Up @@ -612,7 +616,7 @@ public function scalar(): bool|int|null|string|float
};
}

public function select(array|string|ExpressionInterface $columns, string $option = null): static
public function select(array|bool|float|int|string|ExpressionInterface $columns, string $option = null): static
{
$this->select = $this->normalizeSelect($columns);
$this->selectOption = $option;
Expand Down Expand Up @@ -854,18 +858,20 @@ private function normalizeOrderBy(array|string|ExpressionInterface $columns): ar

/**
* Normalizes the `SELECT` columns passed to {@see select()} or {@see addSelect()}.
*
* @psalm-param SelectValue|scalar|ExpressionInterface $columns
* @psalm-return SelectValue
*/
private function normalizeSelect(array|ExpressionInterface|string $columns): array
private function normalizeSelect(array|bool|float|int|string|ExpressionInterface $columns): array
{
if ($columns instanceof ExpressionInterface) {
$columns = [$columns];
} elseif (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
$columns = match (gettype($columns)) {
'array' => $columns,
'string' => preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY),
default => [$columns],
};

$select = [];

/** @psalm-var array<array-key, ExpressionInterface|string> $columns */
foreach ($columns as $columnAlias => $columnDefinition) {
if (is_string($columnAlias)) {
// Already in the normalized format, good for them.
Expand All @@ -890,8 +896,7 @@ private function normalizeSelect(array|ExpressionInterface|string $columns): arr
}
}

// Either a string calling a function, DB expression, or sub-query
/** @psalm-var string */
// Either a string calling a function, instance of ExpressionInterface or a scalar value.
$select[] = $columnDefinition;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Query/QueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* Sorting is supported via {@see orderBy()} and items can be limited to match some conditions using {@see where()}.
*
* @psalm-import-type ParamsType from ConnectionInterface
* @psalm-import-type SelectValue from QueryPartsInterface
*/
interface QueryInterface extends ExpressionInterface, QueryPartsInterface, QueryFunctionsInterface
{
Expand Down Expand Up @@ -207,6 +208,7 @@ public function getParams(): array;

/**
* @return array The "select" value.
* @psalm-return SelectValue
*/
public function getSelect(): array;

Expand Down
20 changes: 15 additions & 5 deletions src/Query/QueryPartsInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*
* {@see Query} uses these methods to build and manipulate SQL statements.
*
* @psalm-type SelectValue = array<array-key, ExpressionInterface|scalar>
* @psalm-import-type ParamsType from ConnectionInterface
*/
interface QueryPartsInterface
Expand Down Expand Up @@ -64,11 +65,15 @@ public function addOrderBy(array|string|ExpressionInterface $columns): static;
* $query->addSelect(["*", "CONCAT(first_name, ' ', last_name) AS full_name"])->one();
* ```
*
* @param array|ExpressionInterface|string $columns The columns to add to the select.
* @param array|ExpressionInterface|scalar $columns The columns to add to the select.
*
* {@see select()} for more details about the format of this parameter.
* @see select() for more details about the format of this parameter.
*
* @since 2.0.0 `$columns` can be a scalar value or an array of scalar values.
*
* @psalm-param SelectValue|scalar|ExpressionInterface $columns
*/
public function addSelect(array|string|ExpressionInterface $columns): static;
public function addSelect(array|bool|float|int|string|ExpressionInterface $columns): static;

/**
* Adds a filtering condition for a specific column and allow the user to choose a filter operator.
Expand Down Expand Up @@ -514,7 +519,7 @@ public function rightJoin(array|string $table, array|string $on = '', array $par
/**
* Sets the `SELECT` part of the query.
*
* @param array|ExpressionInterface|string $columns The columns to be selected.
* @param array|ExpressionInterface|scalar $columns The columns to be selected.
* Columns can be specified in either a string (for example `id, name`) or an array (such as `['id', 'name']`).
* Columns can be prefixed with table names (such as `user.id`) and/or contain column aliases
* (for example `user.id AS user_id`).
Expand All @@ -527,8 +532,13 @@ public function rightJoin(array|string $table, array|string $on = '', array $par
* doesn't need alias, don't use a string key).
* @param string|null $option More option that should be appended to the 'SELECT' keyword. For example, in MySQL,
* the option 'SQL_CALC_FOUND_ROWS' can be used.
*
* @since 2.0.0 `$columns` can be a scalar value or an array of scalar values.
* For example, `$query->select(1)` will be converted to `SELECT 1`.
*
* @psalm-param SelectValue|scalar|ExpressionInterface $columns
*/
public function select(array|string|ExpressionInterface $columns, string $option = null): static;
public function select(array|bool|float|int|string|ExpressionInterface $columns, string $option = null): static;

/**
* It allows you to specify more options for the `SELECT` clause of an SQL statement.
Expand Down
13 changes: 12 additions & 1 deletion src/QueryBuilder/AbstractDQLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Helper\DbStringHelper;
use Yiisoft\Db\QueryBuilder\Condition\HashCondition;
use Yiisoft\Db\QueryBuilder\Condition\Interface\ConditionInterface;
use Yiisoft\Db\QueryBuilder\Condition\SimpleCondition;
Expand All @@ -25,6 +26,7 @@
use function array_merge;
use function array_shift;
use function ctype_digit;
use function gettype;
use function implode;
use function is_array;
use function is_int;
Expand Down Expand Up @@ -324,7 +326,6 @@ public function buildSelect(
return $select . ' *';
}

/** @psalm-var array<array-key, ExpressionInterface|string> $columns */
foreach ($columns as $i => $column) {
if ($column instanceof ExpressionInterface) {
if (is_int($i)) {
Expand All @@ -333,6 +334,16 @@ public function buildSelect(
$columns[$i] = $this->buildExpression($column, $params) . ' AS '
. $this->quoter->quoteColumnName($i);
}
} elseif (!is_string($column)) {
$columns[$i] = match (gettype($column)) {
'double' => DbStringHelper::normalizeFloat($column),
'boolean' => $column ? 'TRUE' : 'FALSE',
default => (string) $column,
};

if (is_string($i)) {
$columns[$i] .= ' AS ' . $this->quoter->quoteColumnName($i);
}
} elseif (is_string($i) && $i !== $column) {
if (!str_contains($column, '(')) {
$column = $this->quoter->quoteColumnName($column);
Expand Down
3 changes: 3 additions & 0 deletions src/QueryBuilder/DQLQueryBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Query\QueryPartsInterface;
use Yiisoft\Db\QueryBuilder\Condition\Interface\ConditionInterface;
use Yiisoft\Db\Query\QueryInterface;

Expand All @@ -20,6 +21,7 @@
* @link https://en.wikipedia.org/wiki/Data_query_language
*
* @psalm-import-type ParamsType from ConnectionInterface
* @psalm-import-type SelectValue from QueryPartsInterface
*/
interface DQLQueryBuilderInterface
{
Expand Down Expand Up @@ -224,6 +226,7 @@ public function buildOrderByAndLimit(
*
* @return string The `SELECT` clause built from {@see \Yiisoft\Db\Query\Query::select()}.
*
* @psalm-param SelectValue $columns
* @psalm-param ParamsType $params
*/
public function buildSelect(
Expand Down
18 changes: 18 additions & 0 deletions tests/AbstractQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2091,6 +2091,24 @@ public function testSelectSubquery(): void
$this->assertEmpty($params);
}

/** @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::selectScalar */
public function testSelectScalar(array|bool|float|int|string $columns, string $expected): void
{
$db = $this->getConnection();
$qb = $db->getQueryBuilder();

$query = (new Query($db))->select($columns);

[$sql, $params] = $qb->build($query);

if ($db->getDriverName() === 'oci') {
$expected .= ' FROM DUAL';
}

$this->assertSame($expected, $sql);
$this->assertEmpty($params);
}

public function testSetConditionClasses(): void
{
$db = $this->getConnection();
Expand Down
9 changes: 8 additions & 1 deletion tests/AbstractQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,13 @@ public function testSelect(): void
['DISTINCT ON(tour_dates.date_from) tour_dates.date_from', 'tour_dates.id' => 'tour_dates.id'],
$query->getSelect()
);

$query = new Query($db);
$query->select(1);
$query->addSelect(true);
$query->addSelect(['float' => 12.34]);

$this->assertSame([1, true, 'float' => 12.34], $query->getSelect());
}

public function testSetJoin(): void
Expand Down Expand Up @@ -779,7 +786,7 @@ public function testNormalizeOrderBy(array|string|Expression $columns, array|str
/**
* @dataProvider \Yiisoft\Db\Tests\Provider\QueryProvider::normalizeSelect
*/
public function testNormalizeSelect(array|string|Expression $columns, array|string $expected): void
public function testNormalizeSelect(array|bool|float|int|string|ExpressionInterface $columns, array|string $expected): void
{
$query = (new Query($this->getConnection()));
$this->assertEquals([], $query->getSelect());
Expand Down
16 changes: 16 additions & 0 deletions tests/Provider/QueryBuilderProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,22 @@ public static function selectExist(): array
];
}

public static function selectScalar(): array
{
return [
[1, 'SELECT 1'],
['custom_string', DbHelper::replaceQuotes('SELECT [[custom_string]]', static::$driverName)],
[true, 'SELECT TRUE'],
[false, 'SELECT FALSE'],
[12.34, 'SELECT 12.34'],
[[1, true, 12.34], 'SELECT 1, TRUE, 12.34'],
[
['a' => 1, 'b' => true, 12.34],
DbHelper::replaceQuotes('SELECT 1 AS [[a]], TRUE AS [[b]], 12.34', static::$driverName),
],
];
}

public static function update(): array
{
return [
Expand Down
4 changes: 4 additions & 0 deletions tests/Provider/QueryProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public static function normalizeSelect(): array
['email' => 'email', 'address' => 'address', 'status' => new Expression('1')],
],
[new Expression('1 as Ab'), [new Expression('1 as Ab')]],
[1, [1]],
[true, [true]],
[12.34, [12.34]],
[['a' => 1, 'b' => true, 12.34], ['a' => 1, 'b' => true, 12.34]],
];
}
}

0 comments on commit 3f977a5

Please sign in to comment.