From 7d0a3d397028499c342d0fb618c2fbd69fdc3c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Al=C3=AD=20Padr=C3=B3n?= <34191509+alipadron@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:34:06 -0400 Subject: [PATCH] Fix: Handle mixed-type values in compileInsert (#53948) * Fix: Prevent incorrect handling of single-value inserts by checking if $values itself is a list using `array_is_list` instead of checking if the first element (`reset($values)`) is an array. * Add tests for `compileInsert` with different value structures * Refactor: Improve insert value handling for non-list arrays * formatting * formatting * formatting --------- Co-authored-by: Taylor Otwell --- .../Database/Query/Grammars/Grammar.php | 7 +- tests/Database/DatabaseQueryGrammarTest.php | 77 +++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index c64aa49a783f..64e8f916d45c 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -1168,8 +1168,11 @@ public function compileInsert(Builder $query, array $values) return "insert into {$table} default values"; } - if (! is_array(reset($values))) { - $values = [$values]; + if (! array_is_list($values)) { + $values = (new Collection(array_keys($values))) + ->some(fn ($key) => ! is_numeric($key)) + ? [$values] + : array_values($values); } $columns = $this->columnize(array_keys(reset($values))); diff --git a/tests/Database/DatabaseQueryGrammarTest.php b/tests/Database/DatabaseQueryGrammarTest.php index aee7f822caba..2af4cca76e13 100644 --- a/tests/Database/DatabaseQueryGrammarTest.php +++ b/tests/Database/DatabaseQueryGrammarTest.php @@ -2,9 +2,11 @@ namespace Illuminate\Tests\Database; +use Illuminate\Database\ConnectionInterface; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\Grammars\Grammar; +use Illuminate\Database\Query\Processors\Processor; use Mockery as m; use PHPUnit\Framework\TestCase; use ReflectionClass; @@ -41,4 +43,79 @@ public function testWhereRawReturnsStringWhenStringPassed() $this->assertSame('select * from "users"', $rawQuery); } + + public function testCompileInsertSingleValue() + { + $builder = $this->getBuilder(); + $grammar = $builder->getGrammar(); + + $sql = $grammar->compileInsert($builder, ['name' => 'John Doe', 'email' => 'johndoe@laravel.com']); + $this->assertSame('insert into "users" ("name", "email") values (?, ?)', $sql); + } + + public function testCompileInsertMultipleValues() + { + $builder = $this->getBuilder(); + $grammar = $builder->getGrammar(); + $values = [ + ['name' => 'John Doe', 'email' => 'john@doe.com'], + ['name' => 'Alice Wong', 'email' => 'alice@wong.com'], + ]; + + $sql = $grammar->compileInsert($builder, $values); + $this->assertSame('insert into "users" ("name", "email") values (?, ?), (?, ?)', $sql); + } + + public function testCompileInsertSingleValueWhereFirstKeyIsArray() + { + $builder = $this->getBuilder(); + $grammar = $builder->getGrammar(); + $value = [ + 'configuration' => [ + 'dark_mode' => false, + 'language' => 'en', + ], + 'name' => 'John Doe', + 'email' => 'john@doe.com', + ]; + + $sql = $grammar->compileInsert($builder, $value); + + $this->assertSame('insert into "users" ("configuration", "name", "email") values (?, ?, ?)', $sql); + } + + public function testCompileInsertSingleValueWhereFirstKeyIsNotArray() + { + $builder = $this->getBuilder(); + $grammar = $builder->getGrammar(); + + $value = [ + 'name' => 'John Doe', + 'configuration' => [ + 'dark_mode' => false, + 'language' => 'en', + ], + 'email' => 'john@doe.com', + ]; + + $sql = $grammar->compileInsert($builder, $value); + + $this->assertSame('insert into "users" ("name", "configuration", "email") values (?, ?, ?)', $sql); + } + + protected function getConnection() + { + return m::mock(ConnectionInterface::class); + } + + protected function getBuilder($tableName = 'users') + { + $grammar = new Grammar; + $processor = m::mock(Processor::class); + + $builder = new Builder($this->getConnection(), $grammar, $processor); + $builder->from = $tableName; + + return $builder; + } }