diff --git a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php index da1cfb840516..e80edc47876d 100755 --- a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Query\Grammars; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Illuminate\Database\Query\Builder; class SQLiteGrammar extends Grammar @@ -175,6 +176,54 @@ public function compileInsert(Builder $query, array $values) return "insert into $table ($names) select ".implode(' union all select ', $columns); } + /** + * Compile an update statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdate(Builder $query, $values) + { + $table = $this->wrapTable($query->from); + + // SQLite doesn't support dots (table.column) in update columns. + // if there is any, We only remove the current table and leave other + // table names as they might be columns that based on a join statement. + $columns = collect($values)->map(function ($value, $key) use($query) { + return $this->wrap(Str::after($key, $query->from.'.')).' = '.$this->parameter($value); + })->implode(', '); + + + // SQLite doesn't support limits or orders by default in update/delete, + // so we use A workaround sub-query where. + if (isset($query->joins) || isset($query->limit)) { + $selectSql = parent::compileSelect($query->select("{$query->from}.rowid")); + + return "update {$table} set $columns where {$this->wrap('rowid')} in ({$selectSql})"; + } + + $wheres = $this->compileWheres($query); + + return trim("update {$table} set $columns $wheres"); + } + + /** + * Prepare the bindings for an update statement. + * + * @param array $bindings + * @param array $values + * @return array + */ + public function prepareBindingsForUpdate(array $bindings, array $values) + { + $cleanBindings = Arr::except($bindings, ['join', 'select']); + + return array_values( + array_merge($values, $bindings['join'], Arr::flatten($cleanBindings)) + ); + } + /** * Compile a delete statement into SQL. * diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 154585db15a9..0632710b4723 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -1485,12 +1485,17 @@ public function testUpdateMethodWithJoinsOnMySql() public function testUpdateMethodWithJoinsOnSQLite() { $builder = $this->getSQLiteBuilder(); - $builder->getConnection()->shouldReceive('update')->once()->with('update "users" inner join "orders" on "users"."id" = "orders"."user_id" set "email" = ?, "name" = ? where "users"."id" = ?', ['foo', 'bar', 1])->andReturn(1); + $builder->getConnection()->shouldReceive('update')->once()->with('update "users" set "email" = ?, "name" = ? where "rowid" in (select "users"."rowid" from "users" where "users"."id" > ? order by "id" asc limit 3)', ['foo', 'bar', 1])->andReturn(1); + $result = $builder->from('users')->where('users.id', '>', 1)->limit(3)->oldest('id')->update(['email' => 'foo', 'name' => 'bar']); + $this->assertEquals(1, $result); + + $builder = $this->getSQLiteBuilder(); + $builder->getConnection()->shouldReceive('update')->once()->with('update "users" set "email" = ?, "name" = ? where "rowid" in (select "users"."rowid" from "users" inner join "orders" on "users"."id" = "orders"."user_id" where "users"."id" = ?)', ['foo', 'bar', 1])->andReturn(1); $result = $builder->from('users')->join('orders', 'users.id', '=', 'orders.user_id')->where('users.id', '=', 1)->update(['email' => 'foo', 'name' => 'bar']); $this->assertEquals(1, $result); $builder = $this->getSQLiteBuilder(); - $builder->getConnection()->shouldReceive('update')->once()->with('update "users" inner join "orders" on "users"."id" = "orders"."user_id" and "users"."id" = ? set "email" = ?, "name" = ?', [1, 'foo', 'bar'])->andReturn(1); + $builder->getConnection()->shouldReceive('update')->once()->with('update "users" set "email" = ?, "name" = ? where "rowid" in (select "users"."rowid" from "users" inner join "orders" on "users"."id" = "orders"."user_id" and "users"."id" = ?)', ['foo', 'bar', 1])->andReturn(1); $result = $builder->from('users')->join('orders', function ($join) { $join->on('users.id', '=', 'orders.user_id') ->where('users.id', '=', 1); diff --git a/tests/Integration/Database/EloquentUpdateTest.php b/tests/Integration/Database/EloquentUpdateTest.php new file mode 100644 index 000000000000..94377e6e64f4 --- /dev/null +++ b/tests/Integration/Database/EloquentUpdateTest.php @@ -0,0 +1,128 @@ +set('app.debug', 'true'); + + $app['config']->set('database.default', 'testbench'); + + $app['config']->set('database.connections.testbench', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + } + + public function setUp() + { + parent::setUp(); + + Schema::create('test_model1', function ($table) { + $table->increments('id'); + $table->string('name')->nullable(); + $table->string('title')->nullable(); + }); + + Schema::create('test_model2', function ($table) { + $table->increments('id'); + $table->string('name'); + $table->string('job')->nullable(); + $table->softDeletes(); + $table->timestamps(); + }); + } + + public function testBasicUpdate() + { + TestUpdateModel1::create([ + 'name' => str_random(), + 'title' => 'Ms.', + ]); + + TestUpdateModel1::where('title', 'Ms.')->delete(); + + $this->assertEquals(0, TestUpdateModel1::all()->count()); + } + + public function testUpdateWithLimitsAndOrders() + { + for($i=1; $i <= 10; $i++){ + TestUpdateModel1::create(); + } + + TestUpdateModel1::latest('id')->limit(3)->update(['title'=>'Dr.']); + + $this->assertEquals('Dr.', TestUpdateModel1::find(8)->title); + $this->assertNotEquals('Dr.', TestUpdateModel1::find(7)->title); + } + + public function testUpdatedAtWithJoins() + { + TestUpdateModel1::create([ + 'name' => 'Abdul', + 'title' => 'Mr.', + ]); + + TestUpdateModel2::create([ + 'name' => str_random() + ]); + + TestUpdateModel2::join('test_model1', function($join) { + $join->on('test_model1.id', '=', 'test_model2.id') + ->where('test_model1.title', '=', 'Mr.'); + })->update(['test_model2.name' => 'Abdul', 'job'=>'Engineer']); + + $record = TestUpdateModel2::find(1); + + $this->assertEquals('Engineer: Abdul', $record->job.': '.$record->name); + } + + public function testSoftDeleteWithJoins() + { + TestUpdateModel1::create([ + 'name' => str_random(), + 'title' => 'Mr.', + ]); + + TestUpdateModel2::create([ + 'name' => str_random() + ]); + + + TestUpdateModel2::join('test_model1', function($join) { + $join->on('test_model1.id', '=', 'test_model2.id') + ->where('test_model1.title', '=', 'Mr.'); + })->delete(); + + $this->assertEquals(0, TestUpdateModel2::all()->count()); + } +} + + +class TestUpdateModel1 extends Model +{ + public $table = 'test_model1'; + public $timestamps = false; + protected $guarded = ['id']; +} + +class TestUpdateModel2 extends Model +{ + use SoftDeletes; + + public $table = 'test_model2'; + protected $fillable = ['name']; + protected $dates = ['deleted_at']; +}