Skip to content

Commit

Permalink
fix SQLite Update
Browse files Browse the repository at this point in the history
  • Loading branch information
abdumu committed Dec 8, 2017
1 parent d146acd commit ada99e5
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 2 deletions.
49 changes: 49 additions & 0 deletions src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand Down
9 changes: 7 additions & 2 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
128 changes: 128 additions & 0 deletions tests/Integration/Database/EloquentUpdateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

namespace Illuminate\Tests\Integration\Database;

use Orchestra\Testbench\TestCase;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
* @group integration
*/
class EloquentUpdateTest extends TestCase
{
protected function getEnvironmentSetUp($app)
{
$app['config']->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'];
}

0 comments on commit ada99e5

Please sign in to comment.