Skip to content

Commit 33a9413

Browse files
[9.x] Add "incrementColumns" to Query\Builder (#45577)
* Add incrementColumns to QueryBuilder * formatting Co-authored-by: Taylor Otwell <[email protected]>
1 parent 955db51 commit 33a9413

File tree

3 files changed

+175
-6
lines changed

3 files changed

+175
-6
lines changed

src/Illuminate/Database/Query/Builder.php

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3410,11 +3410,31 @@ public function increment($column, $amount = 1, array $extra = [])
34103410
throw new InvalidArgumentException('Non-numeric value passed to increment method.');
34113411
}
34123412

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

3415-
$columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra);
3416+
/**
3417+
* Increment the given column's values by the given amounts.
3418+
*
3419+
* @param array<string, float|int|numeric-string> $columns
3420+
* @param array<string, mixed> $extra
3421+
* @return int
3422+
*
3423+
* @throws \InvalidArgumentException
3424+
*/
3425+
public function incrementEach(array $columns, array $extra = [])
3426+
{
3427+
foreach ($columns as $column => $amount) {
3428+
if (! is_numeric($amount)) {
3429+
throw new InvalidArgumentException("Non-numeric value passed as increment amount for column: '$column'.");
3430+
} elseif (! is_string($column)) {
3431+
throw new InvalidArgumentException('Non-associative array passed to incrementEach method.');
3432+
}
3433+
3434+
$columns[$column] = $this->raw("{$this->grammar->wrap($column)} + $amount");
3435+
}
34163436

3417-
return $this->update($columns);
3437+
return $this->update(array_merge($columns, $extra));
34183438
}
34193439

34203440
/**
@@ -3433,11 +3453,31 @@ public function decrement($column, $amount = 1, array $extra = [])
34333453
throw new InvalidArgumentException('Non-numeric value passed to decrement method.');
34343454
}
34353455

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

3438-
$columns = array_merge([$column => $this->raw("$wrapped - $amount")], $extra);
3459+
/**
3460+
* Decrement the given column's values by the given amounts.
3461+
*
3462+
* @param array<string, float|int|numeric-string> $columns
3463+
* @param array<string, mixed> $extra
3464+
* @return int
3465+
*
3466+
* @throws \InvalidArgumentException
3467+
*/
3468+
public function decrementEach(array $columns, array $extra = [])
3469+
{
3470+
foreach ($columns as $column => $amount) {
3471+
if (! is_numeric($amount)) {
3472+
throw new InvalidArgumentException("Non-numeric value passed as decrement amount for column: '$column'.");
3473+
} elseif (! is_string($column)) {
3474+
throw new InvalidArgumentException('Non-associative array passed to decrementEach method.');
3475+
}
3476+
3477+
$columns[$column] = $this->raw("{$this->grammar->wrap($column)} - $amount");
3478+
}
34393479

3440-
return $this->update($columns);
3480+
return $this->update(array_merge($columns, $extra));
34413481
}
34423482

34433483
/**

tests/Database/DatabaseQueryBuilderTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,6 +2021,22 @@ public function testWhereNot()
20212021
$this->assertEquals([0 => 'bar', 1 => 'foo'], $builder->getBindings());
20222022
}
20232023

2024+
public function testIncrementManyArgumentValidation1()
2025+
{
2026+
$this->expectException(InvalidArgumentException::class);
2027+
$this->expectErrorMessage('Non-numeric value passed as increment amount for column: \'col\'.');
2028+
$builder = $this->getBuilder();
2029+
$builder->from('users')->incrementEach(['col' => 'a']);
2030+
}
2031+
2032+
public function testIncrementManyArgumentValidation2()
2033+
{
2034+
$this->expectException(InvalidArgumentException::class);
2035+
$this->expectErrorMessage('Non-associative array passed to incrementEach method.');
2036+
$builder = $this->getBuilder();
2037+
$builder->from('users')->incrementEach([11 => 11]);
2038+
}
2039+
20242040
public function testWhereNotWithArrayConditions()
20252041
{
20262042
$builder = $this->getBuilder();

tests/Integration/Database/QueryBuilderTest.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,119 @@ protected function defineDatabaseMigrationsAfterDatabaseRefreshed()
2727
]);
2828
}
2929

30+
public function testIncrement()
31+
{
32+
Schema::create('accounting', function (Blueprint $table) {
33+
$table->increments('id');
34+
$table->float('wallet_1');
35+
$table->float('wallet_2');
36+
$table->integer('user_id');
37+
$table->string('name', 20);
38+
});
39+
40+
DB::table('accounting')->insert([
41+
[
42+
'wallet_1' => 100,
43+
'wallet_2' => 200,
44+
'user_id' => 1,
45+
'name' => 'Taylor',
46+
],
47+
[
48+
'wallet_1' => 15,
49+
'wallet_2' => 300,
50+
'user_id' => 2,
51+
'name' => 'Otwell',
52+
],
53+
]);
54+
$connection = DB::table('accounting')->getConnection();
55+
$connection->enableQueryLog();
56+
57+
DB::table('accounting')->where('user_id', 2)->incrementEach([
58+
'wallet_1' => 10,
59+
'wallet_2' => -20,
60+
], ['name' => 'foo']);
61+
62+
$queryLogs = $connection->getQueryLog();
63+
$this->assertCount(1, $queryLogs);
64+
65+
$rows = DB::table('accounting')->get();
66+
67+
$this->assertCount(2, $rows);
68+
// other rows are not affected.
69+
$this->assertEquals([
70+
'id' => 1,
71+
'wallet_1' => 100,
72+
'wallet_2' => 200,
73+
'user_id' => 1,
74+
'name' => 'Taylor',
75+
], (array) $rows[0]);
76+
77+
$this->assertEquals([
78+
'id' => 2,
79+
'wallet_1' => 15 + 10,
80+
'wallet_2' => 300 - 20,
81+
'user_id' => 2,
82+
'name' => 'foo',
83+
], (array) $rows[1]);
84+
85+
// without the second argument.
86+
$affectedRowsCount = DB::table('accounting')->where('user_id', 2)->incrementEach([
87+
'wallet_1' => 20,
88+
'wallet_2' => 20,
89+
]);
90+
91+
$this->assertEquals(1, $affectedRowsCount);
92+
93+
$rows = DB::table('accounting')->get();
94+
95+
$this->assertEquals([
96+
'id' => 2,
97+
'wallet_1' => 15 + (10 + 20),
98+
'wallet_2' => 300 + (-20 + 20),
99+
'user_id' => 2,
100+
'name' => 'foo',
101+
], (array) $rows[1]);
102+
103+
// Test Can affect multiple rows at once.
104+
$affectedRowsCount = DB::table('accounting')->incrementEach([
105+
'wallet_1' => 31.5,
106+
'wallet_2' => '-32.5',
107+
]);
108+
109+
$this->assertEquals(2, $affectedRowsCount);
110+
111+
$rows = DB::table('accounting')->get();
112+
$this->assertEquals([
113+
'id' => 1,
114+
'wallet_1' => 100 + 31.5,
115+
'wallet_2' => 200 - 32.5,
116+
'user_id' => 1,
117+
'name' => 'Taylor',
118+
], (array) $rows[0]);
119+
120+
$this->assertEquals([
121+
'id' => 2,
122+
'wallet_1' => 15 + (10 + 20 + 31.5),
123+
'wallet_2' => 300 + (-20 + 20 - 32.5),
124+
'user_id' => 2,
125+
'name' => 'foo',
126+
], (array) $rows[1]);
127+
128+
// In case of a conflict, the second argument wins and sets a fixed value:
129+
$affectedRowsCount = DB::table('accounting')->incrementEach([
130+
'wallet_1' => 3000,
131+
], ['wallet_1' => 1.5]);
132+
133+
$this->assertEquals(2, $affectedRowsCount);
134+
135+
$rows = DB::table('accounting')->get();
136+
137+
$this->assertEquals(1.5, $rows[0]->wallet_1);
138+
$this->assertEquals(1.5, $rows[1]->wallet_1);
139+
140+
Schema::drop('accounting');
141+
}
142+
30143
public function testSole()
31144
{
32145
$expected = ['id' => '1', 'title' => 'Foo Post'];

0 commit comments

Comments
 (0)