diff --git a/CHANGELOG.md b/CHANGELOG.md index 129ca5f1b..88591b25c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ - New #397: Realize `Schema::loadResultColumn()` method (@Tigrov) - New #407: Use `DateTimeColumn` class for datetime column types (@Tigrov) - New #408, #410: Implement `DMLQueryBuilder::upsertReturning()` method (@Tigrov) +- Enh #412: Reduce binding parameters (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/src/Builder/ArrayExpressionBuilder.php b/src/Builder/ArrayExpressionBuilder.php index 85f62b991..dd1542104 100644 --- a/src/Builder/ArrayExpressionBuilder.php +++ b/src/Builder/ArrayExpressionBuilder.php @@ -84,6 +84,8 @@ private function buildNestedSubquery(QueryInterface $query, string $dbType, int private function buildNestedValue(iterable $value, string $dbType, ColumnInterface|null $column, int $dimension, array &$params): string { $placeholders = []; + $queryBuilder = $this->queryBuilder; + $isTypecastingEnabled = $column !== null && $queryBuilder->isTypecastingEnabled(); if ($dimension > 1) { /** @var iterable|null $item */ @@ -93,20 +95,18 @@ private function buildNestedValue(iterable $value, string $dbType, ColumnInterfa } elseif ($item instanceof ExpressionInterface) { $placeholders[] = $item instanceof QueryInterface ? $this->buildNestedSubquery($item, $dbType, $dimension - 1, $params) - : $this->queryBuilder->buildExpression($item, $params); + : $queryBuilder->buildExpression($item, $params); } else { $placeholders[] = $this->buildNestedValue($item, $dbType, $column, $dimension - 1, $params); } } } else { - $value = $this->dbTypecast($value, $column); + if ($isTypecastingEnabled) { + $value = $this->dbTypecast($value, $column); + } foreach ($value as $item) { - if ($item instanceof ExpressionInterface) { - $placeholders[] = $this->queryBuilder->buildExpression($item, $params); - } else { - $placeholders[] = $this->queryBuilder->bindParam($item, $params); - } + $placeholders[] = $queryBuilder->buildValue($item, $params); } } @@ -170,16 +170,12 @@ private function getTypeHint(string $dbType, int $dimension): string * Converts array values for use in a db query. * * @param iterable $value The array or iterable object. - * @param ColumnInterface|null $column The column instance to typecast values. + * @param ColumnInterface $column The column instance to typecast values. * * @return iterable Converted values. */ - private function dbTypecast(iterable $value, ColumnInterface|null $column): iterable + private function dbTypecast(iterable $value, ColumnInterface $column): iterable { - if ($column === null) { - return $value; - } - if (!is_array($value)) { $value = iterator_to_array($value, false); } diff --git a/src/Builder/StructuredExpressionBuilder.php b/src/Builder/StructuredExpressionBuilder.php index 6872a502a..62fef8e4b 100644 --- a/src/Builder/StructuredExpressionBuilder.php +++ b/src/Builder/StructuredExpressionBuilder.php @@ -11,7 +11,6 @@ use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\AbstractStructuredExpressionBuilder; -use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Expression\StructuredExpression; use Yiisoft\Db\Pgsql\Data\StructuredLazyArray; use Yiisoft\Db\Query\QueryInterface; @@ -72,7 +71,10 @@ protected function getLazyArrayValue(LazyArrayInterface $value): array|string private function buildPlaceholders(array $value, StructuredExpression $expression, array &$params): array { $type = $expression->getType(); - $columns = $type instanceof AbstractStructuredColumn ? $type->getColumns() : []; + $queryBuilder = $this->queryBuilder; + $columns = $type instanceof AbstractStructuredColumn && $queryBuilder->isTypecastingEnabled() + ? $type->getColumns() + : []; $placeholders = []; @@ -82,11 +84,7 @@ private function buildPlaceholders(array $value, StructuredExpression $expressio $item = $columns[$columnName]->dbTypecast($item); } - if ($item instanceof ExpressionInterface) { - $placeholders[] = $this->queryBuilder->buildExpression($item, $params); - } else { - $placeholders[] = $this->queryBuilder->bindParam($item, $params); - } + $placeholders[] = $queryBuilder->buildValue($item, $params); } return $placeholders; diff --git a/tests/ArrayExpressionBuilderTest.php b/tests/ArrayExpressionBuilderTest.php index f0654a374..42be324c0 100644 --- a/tests/ArrayExpressionBuilderTest.php +++ b/tests/ArrayExpressionBuilderTest.php @@ -23,6 +23,7 @@ use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Schema\Data\JsonLazyArray; use Yiisoft\Db\Schema\Data\LazyArrayInterface; +use Yiisoft\Db\Tests\Support\Assert; /** * @group pgsql @@ -34,41 +35,45 @@ final class ArrayExpressionBuilderTest extends TestCase public static function buildProvider(): array { return [ - [null, null, 'NULL', []], - [[], null, 'ARRAY[]', []], - [[1, 2, 3], null, 'ARRAY[:qp0,:qp1,:qp2]', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3]], - [ + 'null' => [null, null, 'NULL', []], + 'empty' => [[], null, 'ARRAY[]', []], + 'list' => [[1, 2, 3], null, 'ARRAY[1,2,3]', []], + 'ArrayIterator' => [ new ArrayIterator(['a', 'b', 'c']), 'varchar', 'ARRAY[:qp0,:qp1,:qp2]::varchar[]', - [':qp0' => 'a', ':qp1' => 'b', ':qp2' => 'c'], + [ + ':qp0' => new Param('a', DataType::STRING), + ':qp1' => new Param('b', DataType::STRING), + ':qp2' => new Param('c', DataType::STRING), + ], ], - [ + 'LazyArray' => [ new LazyArray('{1,2,3}'), 'int[]', ':qp0::int[]', [':qp0' => new Param('{1,2,3}', DataType::STRING)], ], - [ + 'LazyArray external' => [ new \Yiisoft\Db\Schema\Data\LazyArray('[1,2,3]'), ColumnBuilder::integer(), - 'ARRAY[:qp0,:qp1,:qp2]::integer[]', - [':qp0' => 1, ':qp1' => 2, ':qp2' => 3], + 'ARRAY[1,2,3]::integer[]', + [], ], - [ + 'StructuredLazyArray' => [ new StructuredLazyArray('(1,2,3)'), 'int', - 'ARRAY[:qp0,:qp1,:qp2]::int[]', - [':qp0' => 1, ':qp1' => 2, ':qp2' => 3], + 'ARRAY[1,2,3]::int[]', + [], ], - [ + 'JsonLazyArray' => [ new JsonLazyArray('[1,2,3]'), ColumnBuilder::array(ColumnBuilder::integer()), - 'ARRAY[:qp0,:qp1,:qp2]::integer[]', - [':qp0' => 1, ':qp1' => 2, ':qp2' => 3], + 'ARRAY[1,2,3]::integer[]', + [], ], - [[new Expression('now()')], null, 'ARRAY[now()]', []], - [ + 'Expression' => [[new Expression('now()')], null, 'ARRAY[now()]', []], + 'JsonExpression w/o type' => [ [new JsonExpression(['a' => null, 'b' => 123, 'c' => [4, 5]]), new JsonExpression([true])], null, 'ARRAY[:qp0,:qp1]', @@ -77,7 +82,7 @@ public static function buildProvider(): array ':qp1' => new Param('[true]', DataType::STRING), ], ], - [ + 'JsonExpression' => [ [new JsonExpression(['a' => null, 'b' => 123, 'c' => [4, 5]]), new JsonExpression([true])], 'jsonb', 'ARRAY[:qp0,:qp1]::jsonb[]', @@ -86,57 +91,51 @@ public static function buildProvider(): array ':qp1' => new Param('[true]', DataType::STRING), ], ], - [ + 'StructuredExpression' => [ [ null, new StructuredExpression(['value' => 11.11, 'currency_code' => 'USD']), new StructuredExpression(['value' => null, 'currency_code' => null]), ], null, - 'ARRAY[:qp0,ROW(:qp1,:qp2),ROW(:qp3,:qp4)]', - [':qp0' => null, ':qp1' => 11.11, ':qp2' => 'USD', ':qp3' => null, ':qp4' => null], + 'ARRAY[NULL,ROW(11.11,:qp0),ROW(NULL,NULL)]', + [':qp0' => new Param('USD', DataType::STRING)], ], - [ + 'Query w/o type' => [ (new Query(self::getDb()))->select('id')->from('users')->where(['active' => 1]), null, 'ARRAY(SELECT "id" FROM "users" WHERE "active"=:qp0)', [':qp0' => 1], ], - [ + 'Query' => [ [(new Query(self::getDb()))->select('id')->from('users')->where(['active' => 1])], 'integer[][]', 'ARRAY[ARRAY(SELECT "id" FROM "users" WHERE "active"=:qp0)::integer[]]::integer[][]', [':qp0' => 1], ], - [ + 'bool' => [ [[[true], [false, null]], [['t', 'f'], null], null], 'bool[][][]', - 'ARRAY[ARRAY[ARRAY[:qp0]::bool[],ARRAY[:qp1,:qp2]::bool[]]::bool[][],ARRAY[ARRAY[:qp3,:qp4]::bool[],NULL]::bool[][],NULL]::bool[][][]', - [ - ':qp0' => true, - ':qp1' => false, - ':qp2' => null, - ':qp3' => 't', - ':qp4' => 'f', - ], + 'ARRAY[ARRAY[ARRAY[TRUE]::bool[],ARRAY[FALSE,NULL]::bool[]]::bool[][],ARRAY[ARRAY[TRUE,TRUE]::bool[],NULL]::bool[][],NULL]::bool[][][]', + [], ], - [ + 'associative' => [ ['a' => '1', 'b' => null], ColumnType::STRING, - 'ARRAY[:qp0,:qp1]::varchar(255)[]', - [':qp0' => '1', ':qp1' => null], + 'ARRAY[:qp0,NULL]::varchar(255)[]', + [':qp0' => new Param('1', DataType::STRING)], ], - [ + 'string' => [ '{1,2,3}', 'string[]', ':qp0::varchar(255)[]', [':qp0' => new Param('{1,2,3}', DataType::STRING)], ], - [ + 'null multi-level' => [ [[1, null], null], 'int[][]', - 'ARRAY[ARRAY[:qp0,:qp1]::int[],NULL]::int[][]', - [':qp0' => '1', ':qp1' => null], + 'ARRAY[ARRAY[1,NULL]::int[],NULL]::int[][]', + [], ], ]; } @@ -156,6 +155,6 @@ public function testBuild( $expression = new ArrayExpression($value, $type); $this->assertSame($expected, $builder->build($expression, $params)); - $this->assertEquals($expectedParams, $params); + Assert::arraysEquals($expectedParams, $params); } } diff --git a/tests/ColumnTest.php b/tests/ColumnTest.php index 81ff75b0d..753dec1ee 100644 --- a/tests/ColumnTest.php +++ b/tests/ColumnTest.php @@ -7,10 +7,7 @@ use DateTimeImmutable; use DateTimeZone; use PHPUnit\Framework\Attributes\DataProviderExternal; -use Throwable; use Yiisoft\Db\Constant\ColumnType; -use Yiisoft\Db\Exception\Exception; -use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Expression\ArrayExpression; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\JsonExpression; @@ -38,8 +35,6 @@ /** * @group pgsql - * - * @psalm-suppress PropertyNotSetInConstructor */ final class ColumnTest extends CommonColumnTest { @@ -91,7 +86,7 @@ private function assertTypecastedValues(array $result): void $this->assertSame(['', 'some text', '""', '\\\\', '[",","null",true,"false","f"]', null], $result['varchararray_col']); $this->assertNull($result['textarray2_col']); $this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $result['json_col']); - $this->assertSame(['1', '2', '3'], $result['jsonb_col']); + $this->assertSame([1, 2, 3], $result['jsonb_col']); $this->assertSame([[[',', 'null', true, 'false', 'f']]], $result['jsonarray_col']); } @@ -193,11 +188,6 @@ public function testSelectWithPhpTypecasting(): void $db->close(); } - /** - * @throws Exception - * @throws InvalidConfigException - * @throws Throwable - */ public function testPhpTypeCast(): void { $db = $this->getConnection(true); diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 84beb70fa..cbf75a12d 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -5,9 +5,6 @@ namespace Yiisoft\Db\Pgsql\Tests; use PHPUnit\Framework\Attributes\DataProviderExternal; -use Throwable; -use Yiisoft\Db\Exception\Exception; -use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Pgsql\Tests\Provider\CommandProvider; use Yiisoft\Db\Pgsql\Tests\Support\TestTrait; @@ -17,8 +14,6 @@ /** * @group pgsql - * - * @psalm-suppress PropertyNotSetInConstructor */ final class CommandTest extends CommonCommandTest { @@ -26,10 +21,6 @@ final class CommandTest extends CommonCommandTest protected string $upsertTestCharCast = 'CAST([[address]] AS VARCHAR(255))'; - /** - * @throws Exception - * @throws InvalidConfigException - */ public function testAddDefaultValue(): void { $db = $this->getConnection(); @@ -46,11 +37,7 @@ public function testAddDefaultValue(): void $db->close(); } - /** - * @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\CommandProvider::batchInsert - * - * @throws Throwable - */ + #[DataProviderExternal(CommandProvider::class, 'batchInsert')] public function testBatchInsert( string $table, iterable $values, @@ -62,11 +49,6 @@ public function testBatchInsert( parent::testBatchInsert($table, $values, $columns, $expected, $expectedParams, $insertedRow); } - /** - * @throws Exception - * @throws InvalidConfigException - * @throws Throwable - */ public function testBooleanValuesInsert(): void { $db = $this->getConnection(true); @@ -100,11 +82,6 @@ public function testBooleanValuesInsert(): void $db->close(); } - /** - * @throws Exception - * @throws InvalidConfigException - * @throws Throwable - */ public function testBooleanValuesBatchInsert(): void { $db = $this->getConnection(true); @@ -133,11 +110,6 @@ public function testBooleanValuesBatchInsert(): void $db->close(); } - /** - * @throws Exception - * @throws InvalidConfigException - * @throws Throwable - */ public function testDelete(): void { $db = $this->getConnection(true); @@ -159,10 +131,6 @@ public function testDelete(): void $db->close(); } - /** - * @throws Exception - * @throws InvalidConfigException - */ public function testDropDefaultValue(): void { $db = $this->getConnection(); @@ -179,23 +147,13 @@ public function testDropDefaultValue(): void $db->close(); } - /** - * @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\CommandProvider::rawSql - * - * @throws Exception - * @throws InvalidConfigException - * @throws NotSupportedException - */ + #[DataProviderExternal(CommandProvider::class, 'rawSql')] public function testGetRawSql(string $sql, array $params, string $expectedRawSql): void { parent::testGetRawSql($sql, $params, $expectedRawSql); } /** - * @throws Exception - * @throws InvalidConfigException - * @throws Throwable - * * {@link https://github.com/yiisoft/yii2/issues/11498} */ public function testSaveSerializedObject(): void @@ -223,12 +181,7 @@ public function testSaveSerializedObject(): void $db->close(); } - /** - * @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\CommandProvider::update - * - * @throws Exception - * @throws Throwable - */ + #[DataProviderExternal(CommandProvider::class, 'update')] public function testUpdate( string $table, array $columns, @@ -240,12 +193,7 @@ public function testUpdate( parent::testUpdate($table, $columns, $conditions, $params, $expectedValues, $expectedCount); } - /** - * @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\CommandProvider::upsert - * - * @throws Exception - * @throws Throwable - */ + #[DataProviderExternal(CommandProvider::class, 'upsert')] public function testUpsert(array $firstData, array $secondData): void { parent::testUpsert($firstData, $secondData); diff --git a/tests/JsonExpressionBuilderTest.php b/tests/JsonExpressionBuilderTest.php index ade92dca3..c83dcbcef 100644 --- a/tests/JsonExpressionBuilderTest.php +++ b/tests/JsonExpressionBuilderTest.php @@ -70,14 +70,14 @@ public function testBuildArrayExpression(): void $builder = new JsonExpressionBuilder($qb); $expression = new JsonExpression(new ArrayExpression([1,2,3])); - $this->assertSame('array_to_json(ARRAY[:qp0,:qp1,:qp2])', $builder->build($expression, $params)); - $this->assertSame([':qp0' => 1, ':qp1' => 2, ':qp2' => 3], $params); + $this->assertSame('array_to_json(ARRAY[1,2,3])', $builder->build($expression, $params)); + $this->assertSame([], $params); $params = []; $expression = new JsonExpression(new ArrayExpression([1,2,3]), 'jsonb'); - $this->assertSame('array_to_json(ARRAY[:qp0,:qp1,:qp2])::jsonb', $builder->build($expression, $params)); - $this->assertSame([':qp0' => 1, ':qp1' => 2, ':qp2' => 3], $params); + $this->assertSame('array_to_json(ARRAY[1,2,3])::jsonb', $builder->build($expression, $params)); + $this->assertSame([], $params); } public function testBuildNull(): void diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php index 27ef57188..6a295f50b 100644 --- a/tests/Provider/CommandProvider.php +++ b/tests/Provider/CommandProvider.php @@ -22,7 +22,7 @@ public static function batchInsert(): array $batchInsert['binds json params']['expected'] = 'INSERT INTO "type" ("int_col", "char_col", "float_col", "bool_col", "json_col")' - . ' VALUES (:qp0, :qp1, :qp2, :qp3, :qp4::json), (:qp5, :qp6, :qp7, :qp8, :qp9)'; + . ' VALUES (1, :qp0, 0, TRUE, :qp1::json), (2, :qp2, -1, FALSE, :qp3)'; $batchInsert['binds params from jsonExpression'] = [ '{{%type}}', @@ -39,14 +39,11 @@ public static function batchInsert(): array ], ['json_col', 'int_col', 'float_col', 'char_col', 'bool_col'], 'expected' => << [ ':qp0' => '{"username":"silverfire","is_active":true,"langs":["Ukrainian","Russian","English"]}', - ':qp1' => 1, - ':qp2' => 1.0, - ':qp3' => '', - ':qp4' => false, + ':qp1' => '', ], ]; @@ -55,9 +52,9 @@ public static function batchInsert(): array [[new ArrayExpression([1, null, 3], 'int'), 1, 1.0, '', false]], ['intarray_col', 'int_col', 'float_col', 'char_col', 'bool_col'], 'expected' => << [':qp0' => 1, ':qp1' => null, ':qp2' => 3, ':qp3' => 1, ':qp4' => 1.0, ':qp5' => '', ':qp6' => false], + 'expectedParams' => [':qp0' => ''], ]; $batchInsert['casts string to int according to the table schema'] = [ @@ -65,9 +62,9 @@ public static function batchInsert(): array [['3', '1.1', '', false]], ['int_col', 'float_col', 'char_col', 'bool_col'], 'expected' => << [':qp0' => 3, ':qp1' => 1.1, ':qp2' => '', ':qp3' => false], + 'expectedParams' => [':qp0' => ''], ]; $batchInsert['binds params from jsonbExpression'] = [ @@ -75,9 +72,9 @@ public static function batchInsert(): array [[new JsonExpression(['a' => true]), 1, 1.1, '', false]], ['jsonb_col', 'int_col', 'float_col', 'char_col', 'bool_col'], 'expected' => << [':qp0' => '{"a":true}', ':qp1' => 1, ':qp2' => 1.1, ':qp3' => '', ':qp4' => false], + 'expectedParams' => [':qp0' => '{"a":true}', ':qp1' => ''], ]; diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 53d804baa..5c6cf9abb 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -96,15 +96,15 @@ public static function buildCondition(): array ], /* Checks to verity that operators work correctly */ - [['@>', 'id', new ArrayExpression([1])], '"id" @> ARRAY[:qp0]', [':qp0' => 1]], - [['<@', 'id', new ArrayExpression([1])], '"id" <@ ARRAY[:qp0]', [':qp0' => 1]], - [['=', 'id', new ArrayExpression([1])], '"id" = ARRAY[:qp0]', [':qp0' => 1]], - [['<>', 'id', new ArrayExpression([1])], '"id" <> ARRAY[:qp0]', [':qp0' => 1]], - [['>', 'id', new ArrayExpression([1])], '"id" > ARRAY[:qp0]', [':qp0' => 1]], - [['<', 'id', new ArrayExpression([1])], '"id" < ARRAY[:qp0]', [':qp0' => 1]], - [['>=', 'id', new ArrayExpression([1])], '"id" >= ARRAY[:qp0]', [':qp0' => 1]], - [['<=', 'id', new ArrayExpression([1])], '"id" <= ARRAY[:qp0]', [':qp0' => 1]], - [['&&', 'id', new ArrayExpression([1])], '"id" && ARRAY[:qp0]', [':qp0' => 1]], + [['@>', 'id', new ArrayExpression([1])], '"id" @> ARRAY[1]', []], + [['<@', 'id', new ArrayExpression([1])], '"id" <@ ARRAY[1]', []], + [['=', 'id', new ArrayExpression([1])], '"id" = ARRAY[1]', []], + [['<>', 'id', new ArrayExpression([1])], '"id" <> ARRAY[1]', []], + [['>', 'id', new ArrayExpression([1])], '"id" > ARRAY[1]', []], + [['<', 'id', new ArrayExpression([1])], '"id" < ARRAY[1]', []], + [['>=', 'id', new ArrayExpression([1])], '"id" >= ARRAY[1]', []], + [['<=', 'id', new ArrayExpression([1])], '"id" <= ARRAY[1]', []], + [['&&', 'id', new ArrayExpression([1])], '"id" && ARRAY[1]', []], ]; } @@ -469,6 +469,26 @@ public static function buildColumnDefinition(): array ]; } + public static function buildValue(): array + { + $values = parent::buildValue(); + + $values['array'][1] = 'ARRAY[:qp0,:qp1,:qp2]'; + $values['array'][2] = [ + ':qp0' => new Param('a', DataType::STRING), + ':qp1' => new Param('b', DataType::STRING), + ':qp2' => new Param('c', DataType::STRING), + ]; + $values['Iterator'][1] = 'ARRAY[:qp0,:qp1,:qp2]'; + $values['Iterator'][2] = [ + ':qp0' => new Param('a', DataType::STRING), + ':qp1' => new Param('b', DataType::STRING), + ':qp2' => new Param('c', DataType::STRING), + ]; + + return $values; + } + public static function prepareParam(): array { $values = parent::prepareParam(); @@ -484,6 +504,8 @@ public static function prepareValue(): array $values['binary'][0] = "'\\x737472696e67'::bytea"; $values['paramBinary'][0] = "'\\x737472696e67'::bytea"; + $values['array'][0] = "ARRAY['a','b','c']"; + $values['Iterator'][0] = "ARRAY['a','b','c']"; return $values; } diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 35256b04d..dc6b30cb4 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -463,15 +463,15 @@ public function testArrayOverlapsConditionBuilder(): void $params = []; $sql = $qb->buildExpression(new ArrayOverlapsCondition('column', [1, 2, 3]), $params); - $this->assertSame('"column"::text[] && ARRAY[:qp0,:qp1,:qp2]::text[]', $sql); - $this->assertSame([':qp0' => 1, ':qp1' => 2, ':qp2' => 3], $params); + $this->assertSame('"column"::text[] && ARRAY[1,2,3]::text[]', $sql); + $this->assertSame([], $params); // Test column as Expression $params = []; $sql = $qb->buildExpression(new ArrayOverlapsCondition(new Expression('column'), [1, 2, 3]), $params); - $this->assertSame('column::text[] && ARRAY[:qp0,:qp1,:qp2]::text[]', $sql); - $this->assertSame([':qp0' => 1, ':qp1' => 2, ':qp2' => 3], $params); + $this->assertSame('column::text[] && ARRAY[1,2,3]::text[]', $sql); + $this->assertSame([], $params); $db->close(); } @@ -485,10 +485,10 @@ public function testJsonOverlapsConditionBuilder(): void $sql = $qb->buildExpression(new JsonOverlapsCondition('column', [1, 2, 3]), $params); $this->assertSame( - 'ARRAY(SELECT jsonb_array_elements_text("column"::jsonb)) && ARRAY[:qp0,:qp1,:qp2]::text[]', + 'ARRAY(SELECT jsonb_array_elements_text("column"::jsonb)) && ARRAY[1,2,3]::text[]', $sql ); - $this->assertSame([':qp0' => 1, ':qp1' => 2, ':qp2' => 3], $params); + $this->assertSame([], $params); $db->close(); } @@ -559,6 +559,12 @@ public function testBuildColumnDefinition(string $expected, ColumnInterface|stri parent::testBuildColumnDefinition($expected, $column); } + #[DataProviderExternal(QueryBuilderProvider::class, 'buildValue')] + public function testBuildValue(mixed $value, string $expected, array $expectedParams): void + { + parent::testBuildValue($value, $expected, $expectedParams); + } + #[DataProviderExternal(QueryBuilderProvider::class, 'prepareParam')] public function testPrepareParam(string $expected, mixed $value, int $type): void { diff --git a/tests/StructuredExpressionBuilderTest.php b/tests/StructuredExpressionBuilderTest.php index c7737295d..c0bb0d5eb 100644 --- a/tests/StructuredExpressionBuilderTest.php +++ b/tests/StructuredExpressionBuilderTest.php @@ -18,6 +18,7 @@ use Yiisoft\Db\Query\Query; use Yiisoft\Db\Schema\Column\AbstractStructuredColumn; use Yiisoft\Db\Schema\Data\JsonLazyArray; +use Yiisoft\Db\Tests\Support\Assert; /** * @group pgsql @@ -34,77 +35,98 @@ public static function buildProvider(): array ]); return [ - [null, null, 'NULL', []], - [[null, null], null, 'ROW(:qp0,:qp1)', [':qp0' => null, ':qp1' => null]], - [['5', 'USD'], null, 'ROW(:qp0,:qp1)', [':qp0' => '5', ':qp1' => 'USD']], - [ + 'null' => [null, null, 'NULL', []], + 'nulls' => [[null, null], null, 'ROW(NULL,NULL)', []], + 'array w/o type' => [ + ['5', 'USD'], + null, + 'ROW(:qp0,:qp1)', + [ + ':qp0' => new Param('5', DataType::STRING), + ':qp1' => new Param('USD', DataType::STRING), + ], + ], + 'ArrayIterator' => [ new ArrayIterator(['5', 'USD']), $column, - 'ROW(:qp0,:qp1)::currency_money', - [':qp0' => 5, ':qp1' => 'USD'], + 'ROW(5,:qp0)::currency_money', + [':qp0' => new Param('USD', DataType::STRING)], ], - [ + 'StructuredLazyArray' => [ new StructuredLazyArray('(5,USD)'), $column, ':qp0::currency_money', [':qp0' => new Param('(5,USD)', DataType::STRING)], ], - [ + 'StructuredLazyArray external' => [ new \Yiisoft\Db\Schema\Data\StructuredLazyArray('["5","USD"]'), $column, - 'ROW(:qp0,:qp1)::currency_money', - [':qp0' => 5, ':qp1' => 'USD'], + 'ROW(5,:qp0)::currency_money', + [':qp0' => new Param('USD', DataType::STRING)], ], - [ + 'LazyArray' => [ new LazyArray('{5,USD}'), $column, - 'ROW(:qp0,:qp1)::currency_money', - [':qp0' => 5, ':qp1' => 'USD'], + 'ROW(5,:qp0)::currency_money', + [':qp0' => new Param('USD', DataType::STRING)], ], - [ + 'JsonLazyArray' => [ new JsonLazyArray('["5","USD"]'), $column, - 'ROW(:qp0,:qp1)::currency_money', - [':qp0' => 5, ':qp1' => 'USD'], + 'ROW(5,:qp0)::currency_money', + [':qp0' => new Param('USD', DataType::STRING)], ], - [ + 'Query w/o type' => [ (new Query(self::getDb()))->select('price')->from('product')->where(['id' => 1]), null, '(SELECT "price" FROM "product" WHERE "id"=:qp0)', [':qp0' => 1], ], - [ + 'Query' => [ (new Query(self::getDb()))->select('price')->from('product')->where(['id' => 1]), 'currency_money', '(SELECT "price" FROM "product" WHERE "id"=:qp0)::currency_money', [':qp0' => 1], ], - [ + 'ordered array' => [ ['value' => '5', 'currency_code' => 'USD'], $column, - 'ROW(:qp0,:qp1)::currency_money', - [':qp0' => 5, ':qp1' => 'USD'], + 'ROW(5,:qp0)::currency_money', + [':qp0' => new Param('USD', DataType::STRING)], ], - [ + 'unordered array' => [ ['currency_code' => 'USD', 'value' => '5'], $column, - 'ROW(:qp0,:qp1)::currency_money', - [':qp0' => 5, ':qp1' => 'USD'], + 'ROW(5,:qp0)::currency_money', + [':qp0' => new Param('USD', DataType::STRING)], + ], + 'missing items' => [ + ['value' => '5'], + $column, + 'ROW(5,:qp0)::currency_money', + [':qp0' => new Param('USD', DataType::STRING)], ], - [['value' => '5'], $column, 'ROW(:qp0,:qp1)::currency_money', [':qp0' => 5, ':qp1' => 'USD']], - [['value' => '5'], null, 'ROW(:qp0)', [':qp0' => '5']], - [ + 'missing items w/o type' => [ + ['value' => '5'], + null, + 'ROW(:qp0)', + [':qp0' => new Param('5', DataType::STRING)], + ], + 'extra items' => [ ['value' => '5', 'currency_code' => 'USD', 'extra' => 'value'], $column, - 'ROW(:qp0,:qp1)::currency_money', - [':qp0' => 5, ':qp1' => 'USD'], + 'ROW(5,:qp0)::currency_money', + [':qp0' => new Param('USD', DataType::STRING)], ], - [(object) ['value' => '5', 'currency_code' => 'USD'], + 'object' => [(object) ['value' => '5', 'currency_code' => 'USD'], 'currency_money', 'ROW(:qp0,:qp1)::currency_money', - [':qp0' => 5, ':qp1' => 'USD'], + [ + ':qp0' => new Param('5', DataType::STRING), + ':qp1' => new Param('USD', DataType::STRING), + ], ], - ['(5,USD)', null, ':qp0', [':qp0' => new Param('(5,USD)', DataType::STRING)]], + 'string' => ['(5,USD)', null, ':qp0', [':qp0' => new Param('(5,USD)', DataType::STRING)]], ]; } @@ -123,6 +145,6 @@ public function testBuild( $expression = new StructuredExpression($value, $type); $this->assertSame($expected, $builder->build($expression, $params)); - $this->assertEquals($expectedParams, $params); + Assert::arraysEquals($expectedParams, $params); } }