Skip to content

Commit 5fb08d3

Browse files
authored
Reduce binding parameters (#412)
1 parent 158e8ac commit 5fb08d3

11 files changed

+170
-191
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
- New #397: Realize `Schema::loadResultColumn()` method (@Tigrov)
4242
- New #407: Use `DateTimeColumn` class for datetime column types (@Tigrov)
4343
- New #408, #410: Implement `DMLQueryBuilder::upsertReturning()` method (@Tigrov)
44+
- Enh #412: Reduce binding parameters (@Tigrov)
4445

4546
## 1.3.0 March 21, 2024
4647

src/Builder/ArrayExpressionBuilder.php

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ private function buildNestedSubquery(QueryInterface $query, string $dbType, int
8484
private function buildNestedValue(iterable $value, string $dbType, ColumnInterface|null $column, int $dimension, array &$params): string
8585
{
8686
$placeholders = [];
87+
$queryBuilder = $this->queryBuilder;
88+
$isTypecastingEnabled = $column !== null && $queryBuilder->isTypecastingEnabled();
8789

8890
if ($dimension > 1) {
8991
/** @var iterable|null $item */
@@ -93,20 +95,18 @@ private function buildNestedValue(iterable $value, string $dbType, ColumnInterfa
9395
} elseif ($item instanceof ExpressionInterface) {
9496
$placeholders[] = $item instanceof QueryInterface
9597
? $this->buildNestedSubquery($item, $dbType, $dimension - 1, $params)
96-
: $this->queryBuilder->buildExpression($item, $params);
98+
: $queryBuilder->buildExpression($item, $params);
9799
} else {
98100
$placeholders[] = $this->buildNestedValue($item, $dbType, $column, $dimension - 1, $params);
99101
}
100102
}
101103
} else {
102-
$value = $this->dbTypecast($value, $column);
104+
if ($isTypecastingEnabled) {
105+
$value = $this->dbTypecast($value, $column);
106+
}
103107

104108
foreach ($value as $item) {
105-
if ($item instanceof ExpressionInterface) {
106-
$placeholders[] = $this->queryBuilder->buildExpression($item, $params);
107-
} else {
108-
$placeholders[] = $this->queryBuilder->bindParam($item, $params);
109-
}
109+
$placeholders[] = $queryBuilder->buildValue($item, $params);
110110
}
111111
}
112112

@@ -170,16 +170,12 @@ private function getTypeHint(string $dbType, int $dimension): string
170170
* Converts array values for use in a db query.
171171
*
172172
* @param iterable $value The array or iterable object.
173-
* @param ColumnInterface|null $column The column instance to typecast values.
173+
* @param ColumnInterface $column The column instance to typecast values.
174174
*
175175
* @return iterable Converted values.
176176
*/
177-
private function dbTypecast(iterable $value, ColumnInterface|null $column): iterable
177+
private function dbTypecast(iterable $value, ColumnInterface $column): iterable
178178
{
179-
if ($column === null) {
180-
return $value;
181-
}
182-
183179
if (!is_array($value)) {
184180
$value = iterator_to_array($value, false);
185181
}

src/Builder/StructuredExpressionBuilder.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use Yiisoft\Db\Exception\InvalidConfigException;
1212
use Yiisoft\Db\Exception\NotSupportedException;
1313
use Yiisoft\Db\Expression\AbstractStructuredExpressionBuilder;
14-
use Yiisoft\Db\Expression\ExpressionInterface;
1514
use Yiisoft\Db\Expression\StructuredExpression;
1615
use Yiisoft\Db\Pgsql\Data\StructuredLazyArray;
1716
use Yiisoft\Db\Query\QueryInterface;
@@ -72,7 +71,10 @@ protected function getLazyArrayValue(LazyArrayInterface $value): array|string
7271
private function buildPlaceholders(array $value, StructuredExpression $expression, array &$params): array
7372
{
7473
$type = $expression->getType();
75-
$columns = $type instanceof AbstractStructuredColumn ? $type->getColumns() : [];
74+
$queryBuilder = $this->queryBuilder;
75+
$columns = $type instanceof AbstractStructuredColumn && $queryBuilder->isTypecastingEnabled()
76+
? $type->getColumns()
77+
: [];
7678

7779
$placeholders = [];
7880

@@ -82,11 +84,7 @@ private function buildPlaceholders(array $value, StructuredExpression $expressio
8284
$item = $columns[$columnName]->dbTypecast($item);
8385
}
8486

85-
if ($item instanceof ExpressionInterface) {
86-
$placeholders[] = $this->queryBuilder->buildExpression($item, $params);
87-
} else {
88-
$placeholders[] = $this->queryBuilder->bindParam($item, $params);
89-
}
87+
$placeholders[] = $queryBuilder->buildValue($item, $params);
9088
}
9189

9290
return $placeholders;

tests/ArrayExpressionBuilderTest.php

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Yiisoft\Db\Schema\Column\ColumnInterface;
2424
use Yiisoft\Db\Schema\Data\JsonLazyArray;
2525
use Yiisoft\Db\Schema\Data\LazyArrayInterface;
26+
use Yiisoft\Db\Tests\Support\Assert;
2627

2728
/**
2829
* @group pgsql
@@ -34,41 +35,45 @@ final class ArrayExpressionBuilderTest extends TestCase
3435
public static function buildProvider(): array
3536
{
3637
return [
37-
[null, null, 'NULL', []],
38-
[[], null, 'ARRAY[]', []],
39-
[[1, 2, 3], null, 'ARRAY[:qp0,:qp1,:qp2]', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3]],
40-
[
38+
'null' => [null, null, 'NULL', []],
39+
'empty' => [[], null, 'ARRAY[]', []],
40+
'list' => [[1, 2, 3], null, 'ARRAY[1,2,3]', []],
41+
'ArrayIterator' => [
4142
new ArrayIterator(['a', 'b', 'c']),
4243
'varchar',
4344
'ARRAY[:qp0,:qp1,:qp2]::varchar[]',
44-
[':qp0' => 'a', ':qp1' => 'b', ':qp2' => 'c'],
45+
[
46+
':qp0' => new Param('a', DataType::STRING),
47+
':qp1' => new Param('b', DataType::STRING),
48+
':qp2' => new Param('c', DataType::STRING),
49+
],
4550
],
46-
[
51+
'LazyArray' => [
4752
new LazyArray('{1,2,3}'),
4853
'int[]',
4954
':qp0::int[]',
5055
[':qp0' => new Param('{1,2,3}', DataType::STRING)],
5156
],
52-
[
57+
'LazyArray external' => [
5358
new \Yiisoft\Db\Schema\Data\LazyArray('[1,2,3]'),
5459
ColumnBuilder::integer(),
55-
'ARRAY[:qp0,:qp1,:qp2]::integer[]',
56-
[':qp0' => 1, ':qp1' => 2, ':qp2' => 3],
60+
'ARRAY[1,2,3]::integer[]',
61+
[],
5762
],
58-
[
63+
'StructuredLazyArray' => [
5964
new StructuredLazyArray('(1,2,3)'),
6065
'int',
61-
'ARRAY[:qp0,:qp1,:qp2]::int[]',
62-
[':qp0' => 1, ':qp1' => 2, ':qp2' => 3],
66+
'ARRAY[1,2,3]::int[]',
67+
[],
6368
],
64-
[
69+
'JsonLazyArray' => [
6570
new JsonLazyArray('[1,2,3]'),
6671
ColumnBuilder::array(ColumnBuilder::integer()),
67-
'ARRAY[:qp0,:qp1,:qp2]::integer[]',
68-
[':qp0' => 1, ':qp1' => 2, ':qp2' => 3],
72+
'ARRAY[1,2,3]::integer[]',
73+
[],
6974
],
70-
[[new Expression('now()')], null, 'ARRAY[now()]', []],
71-
[
75+
'Expression' => [[new Expression('now()')], null, 'ARRAY[now()]', []],
76+
'JsonExpression w/o type' => [
7277
[new JsonExpression(['a' => null, 'b' => 123, 'c' => [4, 5]]), new JsonExpression([true])],
7378
null,
7479
'ARRAY[:qp0,:qp1]',
@@ -77,7 +82,7 @@ public static function buildProvider(): array
7782
':qp1' => new Param('[true]', DataType::STRING),
7883
],
7984
],
80-
[
85+
'JsonExpression' => [
8186
[new JsonExpression(['a' => null, 'b' => 123, 'c' => [4, 5]]), new JsonExpression([true])],
8287
'jsonb',
8388
'ARRAY[:qp0,:qp1]::jsonb[]',
@@ -86,57 +91,51 @@ public static function buildProvider(): array
8691
':qp1' => new Param('[true]', DataType::STRING),
8792
],
8893
],
89-
[
94+
'StructuredExpression' => [
9095
[
9196
null,
9297
new StructuredExpression(['value' => 11.11, 'currency_code' => 'USD']),
9398
new StructuredExpression(['value' => null, 'currency_code' => null]),
9499
],
95100
null,
96-
'ARRAY[:qp0,ROW(:qp1,:qp2),ROW(:qp3,:qp4)]',
97-
[':qp0' => null, ':qp1' => 11.11, ':qp2' => 'USD', ':qp3' => null, ':qp4' => null],
101+
'ARRAY[NULL,ROW(11.11,:qp0),ROW(NULL,NULL)]',
102+
[':qp0' => new Param('USD', DataType::STRING)],
98103
],
99-
[
104+
'Query w/o type' => [
100105
(new Query(self::getDb()))->select('id')->from('users')->where(['active' => 1]),
101106
null,
102107
'ARRAY(SELECT "id" FROM "users" WHERE "active"=:qp0)',
103108
[':qp0' => 1],
104109
],
105-
[
110+
'Query' => [
106111
[(new Query(self::getDb()))->select('id')->from('users')->where(['active' => 1])],
107112
'integer[][]',
108113
'ARRAY[ARRAY(SELECT "id" FROM "users" WHERE "active"=:qp0)::integer[]]::integer[][]',
109114
[':qp0' => 1],
110115
],
111-
[
116+
'bool' => [
112117
[[[true], [false, null]], [['t', 'f'], null], null],
113118
'bool[][][]',
114-
'ARRAY[ARRAY[ARRAY[:qp0]::bool[],ARRAY[:qp1,:qp2]::bool[]]::bool[][],ARRAY[ARRAY[:qp3,:qp4]::bool[],NULL]::bool[][],NULL]::bool[][][]',
115-
[
116-
':qp0' => true,
117-
':qp1' => false,
118-
':qp2' => null,
119-
':qp3' => 't',
120-
':qp4' => 'f',
121-
],
119+
'ARRAY[ARRAY[ARRAY[TRUE]::bool[],ARRAY[FALSE,NULL]::bool[]]::bool[][],ARRAY[ARRAY[TRUE,TRUE]::bool[],NULL]::bool[][],NULL]::bool[][][]',
120+
[],
122121
],
123-
[
122+
'associative' => [
124123
['a' => '1', 'b' => null],
125124
ColumnType::STRING,
126-
'ARRAY[:qp0,:qp1]::varchar(255)[]',
127-
[':qp0' => '1', ':qp1' => null],
125+
'ARRAY[:qp0,NULL]::varchar(255)[]',
126+
[':qp0' => new Param('1', DataType::STRING)],
128127
],
129-
[
128+
'string' => [
130129
'{1,2,3}',
131130
'string[]',
132131
':qp0::varchar(255)[]',
133132
[':qp0' => new Param('{1,2,3}', DataType::STRING)],
134133
],
135-
[
134+
'null multi-level' => [
136135
[[1, null], null],
137136
'int[][]',
138-
'ARRAY[ARRAY[:qp0,:qp1]::int[],NULL]::int[][]',
139-
[':qp0' => '1', ':qp1' => null],
137+
'ARRAY[ARRAY[1,NULL]::int[],NULL]::int[][]',
138+
[],
140139
],
141140
];
142141
}
@@ -156,6 +155,6 @@ public function testBuild(
156155
$expression = new ArrayExpression($value, $type);
157156

158157
$this->assertSame($expected, $builder->build($expression, $params));
159-
$this->assertEquals($expectedParams, $params);
158+
Assert::arraysEquals($expectedParams, $params);
160159
}
161160
}

tests/ColumnTest.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@
77
use DateTimeImmutable;
88
use DateTimeZone;
99
use PHPUnit\Framework\Attributes\DataProviderExternal;
10-
use Throwable;
1110
use Yiisoft\Db\Constant\ColumnType;
12-
use Yiisoft\Db\Exception\Exception;
13-
use Yiisoft\Db\Exception\InvalidConfigException;
1411
use Yiisoft\Db\Expression\ArrayExpression;
1512
use Yiisoft\Db\Expression\Expression;
1613
use Yiisoft\Db\Expression\JsonExpression;
@@ -38,8 +35,6 @@
3835

3936
/**
4037
* @group pgsql
41-
*
42-
* @psalm-suppress PropertyNotSetInConstructor
4338
*/
4439
final class ColumnTest extends CommonColumnTest
4540
{
@@ -91,7 +86,7 @@ private function assertTypecastedValues(array $result): void
9186
$this->assertSame(['', 'some text', '""', '\\\\', '[",","null",true,"false","f"]', null], $result['varchararray_col']);
9287
$this->assertNull($result['textarray2_col']);
9388
$this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $result['json_col']);
94-
$this->assertSame(['1', '2', '3'], $result['jsonb_col']);
89+
$this->assertSame([1, 2, 3], $result['jsonb_col']);
9590
$this->assertSame([[[',', 'null', true, 'false', 'f']]], $result['jsonarray_col']);
9691
}
9792

@@ -193,11 +188,6 @@ public function testSelectWithPhpTypecasting(): void
193188
$db->close();
194189
}
195190

196-
/**
197-
* @throws Exception
198-
* @throws InvalidConfigException
199-
* @throws Throwable
200-
*/
201191
public function testPhpTypeCast(): void
202192
{
203193
$db = $this->getConnection(true);

0 commit comments

Comments
 (0)