Skip to content

Commit

Permalink
Support Traversable values with empty columns for batchInsert() (#…
Browse files Browse the repository at this point in the history
…820)

* Refactor `AbstractDMLQueryBuilder::batchInsert()`

* Allow skipped columns and empty columns for iterators

* Improve

* Apply fixes from StyleCI

* Fix psalm issues

* Remove redundant check

* Add line to CHANGELOG.md [skip ci]

* Remove redundant check

* Mark `AbstractDMLQueryBuilder::prepareTraversable()` as final by @vjik

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

* Fix tests

---------

Co-authored-by: Sergei Predvoditelev <[email protected]>
  • Loading branch information
Tigrov and vjik authored Apr 27, 2024
1 parent 903e7e2 commit e28a16c
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 13 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 #820: Support `Traversable` values for `AbstractDMLQueryBuilder::batchInsert()` method with empty columns (@Tigrov)
- Enh #815: Refactor `Query::column()` method (@Tigrov)
- 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)
Expand Down
49 changes: 40 additions & 9 deletions src/QueryBuilder/AbstractDMLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Yiisoft\Db\QueryBuilder;

use Iterator;
use IteratorAggregate;
use JsonException;
use Traversable;
use Yiisoft\Db\Connection\ConnectionInterface;
Expand Down Expand Up @@ -62,17 +64,17 @@ public function __construct(

public function batchInsert(string $table, array $columns, iterable $rows, array &$params = []): string
{
if (!is_array($rows)) {
$rows = $this->prepareTraversable($rows);
}

if (empty($rows)) {
return '';
}

$columns = $this->extractColumnNames($rows, $columns);
$values = $this->prepareBatchInsertValues($table, $rows, $columns, $params);

if (empty($values)) {
return '';
}

$query = 'INSERT INTO ' . $this->quoter->quoteTableName($table);

if (count($columns) > 0) {
Expand Down Expand Up @@ -130,6 +132,29 @@ public function upsert(
throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.');
}

/**
* Prepare traversable for batch insert.
*
* @param Traversable $rows The rows to be batch inserted into the table.
*
* @return array|Iterator The prepared rows.
*
* @psalm-return Iterator|array<iterable<array-key, mixed>>
*/
final protected function prepareTraversable(Traversable $rows): Iterator|array
{
while ($rows instanceof IteratorAggregate) {
$rows = $rows->getIterator();
}

/** @var Iterator $rows */
if (!$rows->valid()) {
return [];
}

return $rows;
}

/**
* Prepare values for batch insert.
*
Expand Down Expand Up @@ -180,21 +205,27 @@ protected function prepareBatchInsertValues(string $table, iterable $rows, array
/**
* Extract column names from columns and rows.
*
* @param string $table The column schemas.
* @param iterable $rows The rows to be batch inserted into the table.
* @param array[]|Iterator $rows The rows to be batch inserted into the table.
* @param string[] $columns The column names.
*
* @return string[] The column names.
*
* @psalm-param Iterator|non-empty-array<iterable<array-key, mixed>> $rows
*/
protected function extractColumnNames(iterable $rows, array $columns): array
protected function extractColumnNames(array|Iterator $rows, array $columns): array
{
$columns = $this->getNormalizeColumnNames('', $columns);

if ($columns !== [] || !is_array($rows)) {
if (!empty($columns)) {
return $columns;
}

$row = reset($rows);
if ($rows instanceof Iterator) {
$row = $rows->current();
} else {
$row = reset($rows);
}

$row = match (true) {
is_array($row) => $row,
$row instanceof Traversable => iterator_to_array($row),
Expand Down
2 changes: 1 addition & 1 deletion src/QueryBuilder/DMLQueryBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ interface DMLQueryBuilderInterface
* @return string The batch INSERT SQL statement.
*
* @psalm-param string[] $columns
* @psalm-param iterable<array-key, array<array-key, mixed>> $rows
* @psalm-param iterable<iterable<array-key, mixed>> $rows
* @psalm-param ParamsType $params
*
* Note:
Expand Down
2 changes: 1 addition & 1 deletion tests/Common/CommonCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ public function testAddUnique(string $name, string $tableName, array|string $col
public function testBatchInsert(
string $table,
array $columns,
array $values,
iterable $values,
string $expected,
array $expectedParams = [],
int $insertedRow = 1
Expand Down
26 changes: 25 additions & 1 deletion tests/Provider/CommandProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Yiisoft\Db\Tests\Provider;

use ArrayIterator;
use IteratorAggregate;
use Traversable;
use Yiisoft\Db\Command\DataType;
use Yiisoft\Db\Command\Param;
use Yiisoft\Db\Expression\Expression;
Expand Down Expand Up @@ -463,7 +465,7 @@ public static function batchInsert(): array
':qp3' => true,
],
],
'empty columns and Traversable' => [
'empty columns and a Traversable value' => [
'type',
[],
'values' => [new ArrayIterator(['int_col' => '1.0', 'float_col' => '2', 'char_col' => 10, 'bool_col' => 1])],
Expand All @@ -480,6 +482,28 @@ public static function batchInsert(): array
':qp3' => true,
],
],
'empty columns and Traversable values' => [
'type',
[],
'values' => new class () implements IteratorAggregate {
public function getIterator(): Traversable
{
yield ['int_col' => '1.0', 'float_col' => '2', 'char_col' => 10, 'bool_col' => 1];
}
},
'expected' => DbHelper::replaceQuotes(
<<<SQL
INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]]) VALUES (:qp0, :qp1, :qp2, :qp3)
SQL,
static::$driverName,
),
'expectedParams' => [
':qp0' => 1,
':qp1' => 2.0,
':qp2' => '10',
':qp3' => true,
],
],
];
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Provider/QueryBuilderProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ public static function batchInsert(): array
'empty columns and non-exists table' => [
'non_exists_table',
[],
'values' => [['1.0', '2', 10, 1]],
[['1.0', '2', 10, 1]],
'expected' => DbHelper::replaceQuotes(
<<<SQL
INSERT INTO [[non_exists_table]] VALUES (:qp0, :qp1, :qp2, :qp3)
Expand Down

0 comments on commit e28a16c

Please sign in to comment.