Skip to content

Commit 53e81d2

Browse files
authored
Refactor array, structured and JSON expressions. (#929)
1 parent 318ec5c commit 53e81d2

62 files changed

Lines changed: 2222 additions & 966 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/db-oracle.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,11 @@ jobs:
6060
--health-retries 10
6161
6262
steps:
63+
- name: Configure Database.
64+
run: docker exec -i oci bash -c "sqlplus -s system/root@XE <<< 'ALTER USER system DEFAULT TABLESPACE USERS;'"
65+
6366
- name: Checkout.
64-
uses: actions/checkout@v3
67+
uses: actions/checkout@v4
6568

6669
- name: Install PHP with extensions.
6770
uses: shivammathur/setup-php@v2
@@ -70,7 +73,6 @@ jobs:
7073
extensions: ${{ env.EXTENSIONS }}
7174
ini-values: date.timezone='UTC'
7275
coverage: pcov
73-
tools: composer:v2, pecl
7476

7577
- name: Update composer.
7678
run: composer self-update
@@ -92,7 +94,7 @@ jobs:
9294

9395
- name: Upload coverage to Codecov.
9496
if: matrix.php == '8.3'
95-
uses: codecov/codecov-action@v3
97+
uses: codecov/codecov-action@v5
9698
with:
9799
token: ${{ secrets.CODECOV_TOKEN }}
98100
files: ./coverage.xml

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
- Enh #926: Refactor `DbArrayHelper` (@Tigrov)
5959
- Enh #920: Move index constants to the appropriate DBMS driver's `IndexType` and `IndexMethod` classes (@Tigrov)
6060
- New #928: Add `ReferentialAction` class with constants of possible values of referential actions (@Tigrov)
61+
- Enh #929: Refactor array, structured and JSON column type expressions and expression builders (@Tigrov)
62+
- Enh #929: Implement lazy arrays for array, structured and JSON column types (@Tigrov)
6163
- Bug #933: Explicitly mark nullable parameters (@vjik)
6264

6365
## 1.3.0 March 21, 2024

docs/guide/pt-BR/schema/usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,4 @@ echo 'id (' . $column->getDbType() . ')';
9696
```
9797

9898
Em ambos os casos, você obtém a instância ou instâncias
99-
ou a `ColumnSchemaInterface` que você pode usar para obter todas as informações sobre a coluna.
99+
ou a `ColumnInterface` que você pode usar para obter todas as informações sobre a coluna.

phpunit.xml.dist

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4-
backupGlobals="false"
54
colors="true"
65
bootstrap="vendor/autoload.php"
7-
executionOrder="default"
86
failOnRisky="true"
97
failOnWarning="true"
10-
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/10.2/phpunit.xsd"
8+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/schema/10.4.xsd"
119
>
1210
<source>
1311
<include>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Expression;
6+
7+
use Yiisoft\Db\Query\QueryInterface;
8+
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
9+
use Yiisoft\Db\Schema\Data\LazyArrayInterface;
10+
11+
use function is_string;
12+
13+
/**
14+
* Abstract expression builder for {@see ArrayExpression}.
15+
*/
16+
abstract class AbstractArrayExpressionBuilder implements ExpressionBuilderInterface
17+
{
18+
/**
19+
* Builds a SQL expression for a string value.
20+
*
21+
* @param string $value The valid SQL string representation of the array value.
22+
* @param ArrayExpression $expression The array expression.
23+
* @param array $params The binding parameters.
24+
*
25+
* @return string The SQL expression representing the array value.
26+
*/
27+
abstract protected function buildStringValue(string $value, ArrayExpression $expression, array &$params): string;
28+
29+
/**
30+
* Build an array expression from a sub-query object.
31+
*
32+
* @param QueryInterface $query The sub-query object.
33+
* @param ArrayExpression $expression The array expression.
34+
* @param array $params The binding parameters.
35+
*
36+
* @return string The sub-query SQL expression representing an array.
37+
*/
38+
abstract protected function buildSubquery(
39+
QueryInterface $query,
40+
ArrayExpression $expression,
41+
array &$params
42+
): string;
43+
44+
/**
45+
* Builds a SQL expression for an array value.
46+
*
47+
* @param iterable $value The array value.
48+
* @param ArrayExpression $expression The array expression.
49+
* @param array $params The binding parameters.
50+
*
51+
* @return string The SQL expression representing the array value.
52+
*/
53+
abstract protected function buildValue(iterable $value, ArrayExpression $expression, array &$params): string;
54+
55+
/**
56+
* Returns the value of the lazy array as an array or a raw string depending on the implementation.
57+
*
58+
* @param LazyArrayInterface $value The lazy array value.
59+
*
60+
* @return array|string The value of the lazy array.
61+
*/
62+
abstract protected function getLazyArrayValue(LazyArrayInterface $value): array|string;
63+
64+
public function __construct(protected readonly QueryBuilderInterface $queryBuilder)
65+
{
66+
}
67+
68+
/**
69+
* The Method builds the raw SQL from the `$expression` that won't be additionally escaped or quoted.
70+
*
71+
* @param ArrayExpression $expression The expression to build.
72+
* @param array $params The binding parameters.
73+
*
74+
* @return string The raw SQL that won't be additionally escaped or quoted.
75+
*/
76+
public function build(ExpressionInterface $expression, array &$params = []): string
77+
{
78+
$value = $expression->getValue();
79+
80+
if ($value === null) {
81+
return 'NULL';
82+
}
83+
84+
if ($value instanceof LazyArrayInterface) {
85+
$value = $this->getLazyArrayValue($value);
86+
}
87+
88+
if (is_string($value)) {
89+
return $this->buildStringValue($value, $expression, $params);
90+
}
91+
92+
if ($value instanceof QueryInterface) {
93+
return $this->buildSubquery($value, $expression, $params);
94+
}
95+
96+
return $this->buildValue($value, $expression, $params);
97+
}
98+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Expression;
6+
7+
use Yiisoft\Db\Exception\Exception;
8+
use Yiisoft\Db\Exception\InvalidArgumentException;
9+
use Yiisoft\Db\Exception\InvalidConfigException;
10+
use Yiisoft\Db\Exception\NotSupportedException;
11+
use Yiisoft\Db\Helper\DbArrayHelper;
12+
use Yiisoft\Db\Query\QueryInterface;
13+
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
14+
use Yiisoft\Db\Schema\Column\AbstractStructuredColumn;
15+
use Yiisoft\Db\Schema\Data\LazyArrayInterface;
16+
17+
use function array_key_exists;
18+
use function array_keys;
19+
use function is_string;
20+
21+
/**
22+
* Abstract expression builder for {@see StructuredExpression}.
23+
*/
24+
abstract class AbstractStructuredExpressionBuilder implements ExpressionBuilderInterface
25+
{
26+
/**
27+
* Builds a SQL expression for a string value.
28+
*
29+
* @param string $value The valid SQL string representation of the structured value.
30+
* @param StructuredExpression $expression The structured expression.
31+
* @param array $params The binding parameters.
32+
*
33+
* @return string The SQL expression representing the structured value.
34+
*/
35+
abstract protected function buildStringValue(
36+
string $value,
37+
StructuredExpression $expression,
38+
array &$params
39+
): string;
40+
41+
/**
42+
* Build a structured expression from a sub-query object.
43+
*
44+
* @param QueryInterface $query The sub-query object.
45+
* @param StructuredExpression $expression The structured expression.
46+
* @param array $params The binding parameters.
47+
*
48+
* @return string The sub-query SQL expression representing a structured value.
49+
*/
50+
abstract protected function buildSubquery(
51+
QueryInterface $query,
52+
StructuredExpression $expression,
53+
array &$params
54+
): string;
55+
56+
/**
57+
* Builds a SQL expression for a structured value.
58+
*
59+
* @param array|object $value The structured value.
60+
* @param StructuredExpression $expression The structured expression.
61+
* @param array $params The binding parameters.
62+
*
63+
* @return string The SQL expression representing the structured value.
64+
*/
65+
abstract protected function buildValue(
66+
array|object $value,
67+
StructuredExpression $expression,
68+
array &$params
69+
): string;
70+
71+
/**
72+
* Returns the value of the lazy array as an array or a raw string depending on the implementation.
73+
*
74+
* @param LazyArrayInterface $value The lazy array value.
75+
*
76+
* @return array|string The value of the lazy array.
77+
*/
78+
abstract protected function getLazyArrayValue(LazyArrayInterface $value): array|string;
79+
80+
public function __construct(protected readonly QueryBuilderInterface $queryBuilder)
81+
{
82+
}
83+
84+
/**
85+
* The method builds the raw SQL from the `$expression` that won't be additionally escaped or quoted.
86+
*
87+
* @param StructuredExpression $expression The expression to build.
88+
* @param array $params The binding parameters.
89+
*
90+
* @throws Exception
91+
* @throws InvalidArgumentException
92+
* @throws InvalidConfigException
93+
* @throws NotSupportedException
94+
*
95+
* @return string The raw SQL that won't be additionally escaped or quoted.
96+
*/
97+
public function build(ExpressionInterface $expression, array &$params = []): string
98+
{
99+
$value = $expression->getValue();
100+
101+
if ($value === null) {
102+
return 'NULL';
103+
}
104+
105+
if ($value instanceof LazyArrayInterface) {
106+
$value = $this->getLazyArrayValue($value);
107+
}
108+
109+
if (is_string($value)) {
110+
return $this->buildStringValue($value, $expression, $params);
111+
}
112+
113+
if ($value instanceof QueryInterface) {
114+
return $this->buildSubquery($value, $expression, $params);
115+
}
116+
117+
return $this->buildValue($value, $expression, $params);
118+
}
119+
120+
/**
121+
* Returns the prepared value of the structured type, where:
122+
* - object are converted to an array;
123+
* - array elements are sorted according to the order of structured type columns;
124+
* - indexed keys are replaced with column names;
125+
* - missing elements are filled in with default values;
126+
* - excessive elements are ignored.
127+
*
128+
* If the structured type columns are not specified it will only convert the object to an array.
129+
*
130+
* @param array|object $value The structured type value.
131+
* @param StructuredExpression $expression The structured expression.
132+
*/
133+
protected function prepareValues(array|object $value, StructuredExpression $expression): array
134+
{
135+
$value = DbArrayHelper::toArray($value);
136+
137+
$type = $expression->getType();
138+
$columns = $type instanceof AbstractStructuredColumn ? $type->getColumns() : [];
139+
140+
if (empty($columns)) {
141+
return $value;
142+
}
143+
144+
$prepared = [];
145+
$columnNames = array_keys($columns);
146+
147+
foreach ($columnNames as $i => $columnName) {
148+
$prepared[$columnName] = match (true) {
149+
array_key_exists($columnName, $value) => $value[$columnName],
150+
array_key_exists($i, $value) => $value[$i],
151+
default => $columns[$columnName]->getDefaultValue(),
152+
};
153+
}
154+
155+
return $prepared;
156+
}
157+
}

0 commit comments

Comments
 (0)