Skip to content

Commit

Permalink
Fix batchInsert() with associative arrays (#769)
Browse files Browse the repository at this point in the history
* Refactor DMLQueryBuilder

* Get uniques using `getTableIndexes()` and `getTableUniques()`

* Fix @psalm-var

* Fix #61 (point 2)

* Fix #61 (point 2) add test

* Improve test

* Remove methods with `NotSupportedException`

* Fix test issues

* Fix test issues

* Revert "Remove methods with `NotSupportedException`"

* Add line to CHANGELOG.md

* Change order of checks

* Improve performance of quoting column names up to 10% using `array_map()`

* Fix `batchInsert()` with associative arrays

* Update after merge

* Remove old comments

* Fix psalm

* Add line to CHANGELOG.md

---------

Co-authored-by: Sergei Predvoditelev <[email protected]>
  • Loading branch information
Tigrov and vjik authored Nov 12, 2023
1 parent 826d6ea commit 63f68c0
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- Bug #761: Quote aliases of CTE in `WITH` queries (@Tigrov)
- Chg #765: Deprecate `SchemaInterface::TYPE_JSONB` (@Tigrov)
- Enh #770: Move methods from concrete `Command` class to `AbstractPdoCommand` class (@Tigrov)
- Bug #769, #61: Fix `AbstractDMLQueryBuilder::batchInsert()` for values as associative arrays (@Tigrov)

## 1.1.1 August 16, 2023

Expand Down
37 changes: 21 additions & 16 deletions src/QueryBuilder/AbstractDMLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

use function array_combine;
use function array_diff;
use function array_fill_keys;
use function array_filter;
use function array_keys;
use function array_map;
Expand Down Expand Up @@ -57,39 +58,45 @@ public function batchInsert(string $table, array $columns, iterable $rows, array

$values = [];
$columns = $this->getNormalizeColumnNames('', $columns);
$columnNames = array_values($columns);
$columnKeys = array_fill_keys($columnNames, false);
$columnSchemas = $this->schema->getTableSchema($table)?->getColumns() ?? [];

foreach ($rows as $row) {
$i = 0;
$placeholders = [];

foreach ($row as $value) {
if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
$placeholders = $columnKeys;

foreach ($row as $key => $value) {
/** @psalm-suppress MixedArrayTypeCoercion */
$columnName = $columns[$key] ?? (isset($columnKeys[$key]) ? $key : $columnNames[$i] ?? $i);
/** @psalm-suppress MixedArrayTypeCoercion */
if (isset($columnSchemas[$columnName])) {
$value = $columnSchemas[$columnName]->dbTypecast($value);
}

if ($value instanceof ExpressionInterface) {
$placeholders[] = $this->queryBuilder->buildExpression($value, $params);
$placeholders[$columnName] = $this->queryBuilder->buildExpression($value, $params);
} else {
$placeholders[] = $this->queryBuilder->bindParam($value, $params);
$placeholders[$columnName] = $this->queryBuilder->bindParam($value, $params);
}

++$i;
}

$values[] = '(' . implode(', ', $placeholders) . ')';
}

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

$columns = array_map(
$columnNames = array_map(
[$this->quoter, 'quoteColumnName'],
$columns,
$columnNames,
);

return 'INSERT INTO ' . $this->quoter->quoteTableName($table)
. ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
. ' (' . implode(', ', $columnNames) . ') VALUES ' . implode(', ', $values);
}

public function delete(string $table, array|string $condition, array &$params): string
Expand Down Expand Up @@ -430,13 +437,11 @@ protected function normalizeColumnNames(string $table, array $columns): array
*/
protected function getNormalizeColumnNames(string $table, array $columns): array
{
$normalizedNames = [];

foreach ($columns as $name) {
$normalizedName = $this->quoter->ensureColumnName($name);
$normalizedNames[] = $this->quoter->unquoteSimpleColumnName($normalizedName);
foreach ($columns as &$name) {
$name = $this->quoter->ensureColumnName($name);
$name = $this->quoter->unquoteSimpleColumnName($name);
}

return $normalizedNames;
return $columns;
}
}
70 changes: 69 additions & 1 deletion tests/Provider/CommandProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ public static function batchInsert(): array
':qp3' => false,
],
],
'with associative values' => [
'with associative values with different keys' => [
'type',
['int_col', 'float_col', 'char_col', 'bool_col'],
'values' => [['int' => '1.0', 'float' => '2', 'char' => 10, 'bool' => 1]],
Expand All @@ -356,6 +356,74 @@ public static function batchInsert(): array
':qp3' => true,
],
],
'with associative values with different keys and columns with keys' => [
'type',
['a' => 'int_col', 'b' => 'float_col', 'c' => 'char_col', 'd' => 'bool_col'],
'values' => [['int' => '1.0', 'float' => '2', 'char' => 10, 'bool' => 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,
],
],
'with associative values with keys of column names' => [
'type',
['int_col', 'float_col', 'char_col', 'bool_col'],
'values' => [['bool_col' => 1, 'char_col' => 10, 'int_col' => '1.0', 'float_col' => '2']],
'expected' => DbHelper::replaceQuotes(
<<<SQL
INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]]) VALUES (:qp2, :qp3, :qp1, :qp0)
SQL,
static::$driverName,
),
'expectedParams' => [
':qp0' => true,
':qp1' => '10',
':qp2' => 1,
':qp3' => 2.0,
],
],
'with associative values with keys of column keys' => [
'type',
['int' => 'int_col', 'float' => 'float_col', 'char' => 'char_col', 'bool' => 'bool_col'],
'values' => [['bool' => 1, 'char' => 10, 'int' => '1.0', 'float' => '2']],
'expected' => DbHelper::replaceQuotes(
<<<SQL
INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]]) VALUES (:qp2, :qp3, :qp1, :qp0)
SQL,
static::$driverName,
),
'expectedParams' => [
':qp0' => true,
':qp1' => '10',
':qp2' => 1,
':qp3' => 2.0,
],
],
'with shuffled indexes of values' => [
'type',
['int_col', 'float_col', 'char_col', 'bool_col'],
'values' => [[3 => 1, 2 => 10, 0 => '1.0', 1 => '2']],
'expected' => DbHelper::replaceQuotes(
<<<SQL
INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]]) VALUES (:qp2, :qp3, :qp1, :qp0)
SQL,
static::$driverName,
),
'expectedParams' => [
':qp0' => true,
':qp1' => '10',
':qp2' => 1,
':qp3' => 2.0,
],
],
];
}

Expand Down

0 comments on commit 63f68c0

Please sign in to comment.