Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Add "incrementEach" to the Query\Builder #45577

Merged
merged 2 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 46 additions & 6 deletions src/Illuminate/Database/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3408,11 +3408,31 @@ public function increment($column, $amount = 1, array $extra = [])
throw new InvalidArgumentException('Non-numeric value passed to increment method.');
}

$wrapped = $this->grammar->wrap($column);
return $this->incrementEach([$column => $amount], $extra);
}

$columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra);
/**
* Increment the given column's values by the given amounts.
*
* @param array<string, float|int|numeric-string> $columns
* @param array<string, mixed> $extra
* @return int
*
* @throws \InvalidArgumentException
*/
public function incrementEach(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'.");
} elseif (! is_string($column)) {
throw new InvalidArgumentException('Non-associative array passed to incrementEach method.');
}

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

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

/**
Expand All @@ -3431,11 +3451,31 @@ public function decrement($column, $amount = 1, array $extra = [])
throw new InvalidArgumentException('Non-numeric value passed to decrement method.');
}

$wrapped = $this->grammar->wrap($column);
return $this->decrementEach([$column => $amount], $extra);
}

$columns = array_merge([$column => $this->raw("$wrapped - $amount")], $extra);
/**
* Decrement the given column's values by the given amounts.
*
* @param array<string, float|int|numeric-string> $columns
* @param array<string, mixed> $extra
* @return int
*
* @throws \InvalidArgumentException
*/
public function decrementEach(array $columns, array $extra = [])
{
foreach ($columns as $column => $amount) {
if (! is_numeric($amount)) {
throw new InvalidArgumentException("Non-numeric value passed as decrement amount for column: '$column'.");
} elseif (! is_string($column)) {
throw new InvalidArgumentException('Non-associative array passed to decrementEach method.');
}

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

return $this->update($columns);
return $this->update(array_merge($columns, $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')->incrementEach(['col' => 'a']);
}

public function testIncrementManyArgumentValidation2()
{
$this->expectException(InvalidArgumentException::class);
$this->expectErrorMessage('Non-associative array passed to incrementEach method.');
$builder = $this->getBuilder();
$builder->from('users')->incrementEach([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)->incrementEach([
'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)->incrementEach([
'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')->incrementEach([
'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')->incrementEach([
'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