Skip to content

Commit

Permalink
Add incrementColumns to QueryBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
imanghafoori1 committed Jan 13, 2023
1 parent cb6d257 commit 5e4cf15
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 5 deletions.
32 changes: 27 additions & 5 deletions src/Illuminate/Database/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3392,6 +3392,32 @@ public function upsert(array $values, $uniqueBy, $update = null)
);
}

/**
* Atomically increments columns values by the given amounts.
*
* @param array<string, float|int|numeric-string> $columns
* @param array<string, mixed> $extra
* @return int
*
* @throws \InvalidArgumentException
*/
public function incrementColumns(array $columns, array $extra = [])
{
foreach ($columns as $column => $amount) {
if (! is_numeric($amount)) {
throw new InvalidArgumentException("Non-numeric value passed as increment amount for column: '$column'.");
}

if (! is_string($column)) {
throw new InvalidArgumentException('Non-associative array passed to incrementMany method.');
}

$columns[$column] = $this->raw("{$this->grammar->wrap($column)} + $amount");
}

return $this->update(array_merge($columns, $extra));
}

/**
* Increment a column's value by a given amount.
*
Expand All @@ -3408,11 +3434,7 @@ public function increment($column, $amount = 1, array $extra = [])
throw new InvalidArgumentException('Non-numeric value passed to increment method.');
}

$wrapped = $this->grammar->wrap($column);

$columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra);

return $this->update($columns);
return $this->incrementColumns([$column => $amount], $extra);
}

/**
Expand Down
16 changes: 16 additions & 0 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1978,6 +1978,22 @@ public function testWhereNot()
$this->assertEquals([0 => 'bar', 1 => 'foo'], $builder->getBindings());
}

public function testIncrementManyArgumentValidation1()
{
$this->expectException(InvalidArgumentException::class);
$this->expectErrorMessage('Non-numeric value passed as increment amount for column: \'col\'.');
$builder = $this->getBuilder();
$builder->from('users')->incrementColumns(['col' => 'a']);
}

public function testIncrementManyArgumentValidation2()
{
$this->expectException(InvalidArgumentException::class);
$this->expectErrorMessage('Non-associative array passed to incrementMany method.');
$builder = $this->getBuilder();
$builder->from('users')->incrementColumns([11 => 11]);
}

public function testWhereNotWithArrayConditions()
{
$builder = $this->getBuilder();
Expand Down
113 changes: 113 additions & 0 deletions tests/Integration/Database/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,119 @@ protected function defineDatabaseMigrationsAfterDatabaseRefreshed()
]);
}

public function testIncrement()
{
Schema::create('accounting', function (Blueprint $table) {
$table->increments('id');
$table->float('wallet_1');
$table->float('wallet_2');
$table->integer('user_id');
$table->string('name', 20);
});

DB::table('accounting')->insert([
[
'wallet_1' => 100,
'wallet_2' => 200,
'user_id' => 1,
'name' => 'Taylor',
],
[
'wallet_1' => 15,
'wallet_2' => 300,
'user_id' => 2,
'name' => 'Otwell',
],
]);
$connection = DB::table('accounting')->getConnection();
$connection->enableQueryLog();

DB::table('accounting')->where('user_id', 2)->incrementColumns([
'wallet_1' => 10,
'wallet_2' => -20,
], ['name' => 'foo']);

$queryLogs = $connection->getQueryLog();
$this->assertCount(1, $queryLogs);

$rows = DB::table('accounting')->get();

$this->assertCount(2, $rows);
// other rows are not affected.
$this->assertEquals([
'id' => 1,
'wallet_1' => 100,
'wallet_2' => 200,
'user_id' => 1,
'name' => 'Taylor',
], (array) $rows[0]);

$this->assertEquals([
'id' => 2,
'wallet_1' => 15 + 10,
'wallet_2' => 300 - 20,
'user_id' => 2,
'name' => 'foo',
], (array) $rows[1]);

// without the second argument.
$affectedRowsCount = DB::table('accounting')->where('user_id', 2)->incrementColumns([
'wallet_1' => 20,
'wallet_2' => 20,
]);

$this->assertEquals(1, $affectedRowsCount);

$rows = DB::table('accounting')->get();

$this->assertEquals([
'id' => 2,
'wallet_1' => 15 + (10 + 20),
'wallet_2' => 300 + (-20 + 20),
'user_id' => 2,
'name' => 'foo',
], (array) $rows[1]);

// Test Can affect multiple rows at once.
$affectedRowsCount = DB::table('accounting')->incrementColumns([
'wallet_1' => 31.5,
'wallet_2' => '-32.5',
]);

$this->assertEquals(2, $affectedRowsCount);

$rows = DB::table('accounting')->get();
$this->assertEquals([
'id' => 1,
'wallet_1' => 100 + 31.5,
'wallet_2' => 200 - 32.5,
'user_id' => 1,
'name' => 'Taylor',
], (array) $rows[0]);

$this->assertEquals([
'id' => 2,
'wallet_1' => 15 + (10 + 20 + 31.5),
'wallet_2' => 300 + (-20 + 20 - 32.5),
'user_id' => 2,
'name' => 'foo',
], (array) $rows[1]);

// In case of a conflict, the second argument wins and sets a fixed value:
$affectedRowsCount = DB::table('accounting')->incrementColumns([
'wallet_1' => 3000,
], ['wallet_1' => 1.5]);

$this->assertEquals(2, $affectedRowsCount);

$rows = DB::table('accounting')->get();

$this->assertEquals(1.5, $rows[0]->wallet_1);
$this->assertEquals(1.5, $rows[1]->wallet_1);

Schema::drop('accounting');
}

public function testSole()
{
$expected = ['id' => '1', 'title' => 'Foo Post'];
Expand Down

0 comments on commit 5e4cf15

Please sign in to comment.