Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Traversable values with empty columns for batchInsert() #820

Merged
merged 15 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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 @@
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>>
*/
protected function prepareTraversable(Traversable $rows): Iterator|array
Tigrov marked this conversation as resolved.
Show resolved Hide resolved
{
while ($rows instanceof IteratorAggregate) {
$rows = $rows->getIterator();

Check warning on line 147 in src/QueryBuilder/AbstractDMLQueryBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/QueryBuilder/AbstractDMLQueryBuilder.php#L147

Added line #L147 was not covered by tests
}

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

return $rows;

Check warning on line 155 in src/QueryBuilder/AbstractDMLQueryBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/QueryBuilder/AbstractDMLQueryBuilder.php#L155

Added line #L155 was not covered by tests
}

/**
* Prepare values for batch insert.
*
Expand Down Expand Up @@ -180,21 +205,27 @@
/**
* 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();

Check warning on line 224 in src/QueryBuilder/AbstractDMLQueryBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/QueryBuilder/AbstractDMLQueryBuilder.php#L224

Added line #L224 was not covered by tests
} 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
Loading