Skip to content

Commit

Permalink
Fix batchInsert() with associative arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov committed Oct 14, 2023
1 parent b82cb14 commit 13c2b97
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 17 deletions.
68 changes: 52 additions & 16 deletions src/QueryBuilder/AbstractDMLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,22 @@

use function array_combine;
use function array_diff;
use function array_fill_keys;
use function array_filter;
use function array_key_first;
use function array_keys;
use function array_map;
use function array_merge;
use function array_slice;
use function array_unique;
use function array_values;
use function implode;
use function in_array;
use function is_array;
use function is_string;
use function json_encode;
use function preg_match;
use function reset;
use function sort;

/**
Expand Down Expand Up @@ -58,39 +63,72 @@ public function batchInsert(string $table, array $columns, iterable $rows, array
$columns = $this->getNormalizeColumnNames($columns);
$columnSchemas = $this->schema->getTableSchema($table)?->getColumns() ?? [];

if ($columns === []) {
$columnNames = [];
if (is_array($rows)) {
/** @psalm-var iterable $row */
$row = reset($rows);

if (is_array($row)) {
if (is_string(array_key_first($row))) {
$columnNames = array_keys($row);
} else {
$columnNames = array_slice(array_keys($columnSchemas), 0, count($row));
}

$columns = array_combine($columnNames, $columnNames);
}
}
} else {
$columnNames = array_values($columns);
}

$columnKeys = array_fill_keys($columnNames, false);

/** @psalm-var array[] $rows */
foreach ($rows as $row) {
$i = 0;
$placeholders = [];
/** @psalm-var mixed $value */
foreach ($row as $value) {
if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
$placeholders = $columnKeys;
/**
* @psalm-var string|int $key
* @psalm-var mixed $value
*/
foreach ($row as $key => $value) {
/** @psalm-var string|int $columnName */
$columnName = $columns[$key] ?? (
isset($columnKeys[$key])
? $key
: $columnNames[$i] ?? $i
);

if (isset($columnSchemas[$columnName])) {
/** @psalm-var mixed $value */
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
$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 @@ -424,13 +462,11 @@ protected function normalizeColumnNames(array $columns): array
*/
protected function getNormalizeColumnNames(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;
}
}
86 changes: 85 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,90 @@ 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,
],
],
'with empty columns' => [
'customer',
[],
'values' => [['10.0', '[email protected]']],
'expected' => DbHelper::replaceQuotes(
<<<SQL
INSERT INTO [[customer]] (`id`, `email`) VALUES (:qp0, :qp1)
SQL,
static::$driverName,
),
'expectedParams' => [
':qp0' => 10,
':qp1' => '[email protected]',
],
4,
],
];
}

Expand Down

0 comments on commit 13c2b97

Please sign in to comment.