Skip to content

Commit 5e4cf15

Browse files
committed
Add incrementColumns to QueryBuilder
1 parent cb6d257 commit 5e4cf15

File tree

3 files changed

+156
-5
lines changed

3 files changed

+156
-5
lines changed

src/Illuminate/Database/Query/Builder.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3392,6 +3392,32 @@ public function upsert(array $values, $uniqueBy, $update = null)
33923392
);
33933393
}
33943394

3395+
/**
3396+
* Atomically increments columns values by the given amounts.
3397+
*
3398+
* @param array<string, float|int|numeric-string> $columns
3399+
* @param array<string, mixed> $extra
3400+
* @return int
3401+
*
3402+
* @throws \InvalidArgumentException
3403+
*/
3404+
public function incrementColumns(array $columns, array $extra = [])
3405+
{
3406+
foreach ($columns as $column => $amount) {
3407+
if (! is_numeric($amount)) {
3408+
throw new InvalidArgumentException("Non-numeric value passed as increment amount for column: '$column'.");
3409+
}
3410+
3411+
if (! is_string($column)) {
3412+
throw new InvalidArgumentException('Non-associative array passed to incrementMany method.');
3413+
}
3414+
3415+
$columns[$column] = $this->raw("{$this->grammar->wrap($column)} + $amount");
3416+
}
3417+
3418+
return $this->update(array_merge($columns, $extra));
3419+
}
3420+
33953421
/**
33963422
* Increment a column's value by a given amount.
33973423
*
@@ -3408,11 +3434,7 @@ public function increment($column, $amount = 1, array $extra = [])
34083434
throw new InvalidArgumentException('Non-numeric value passed to increment method.');
34093435
}
34103436

3411-
$wrapped = $this->grammar->wrap($column);
3412-
3413-
$columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra);
3414-
3415-
return $this->update($columns);
3437+
return $this->incrementColumns([$column => $amount], $extra);
34163438
}
34173439

34183440
/**

tests/Database/DatabaseQueryBuilderTest.php

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

1981+
public function testIncrementManyArgumentValidation1()
1982+
{
1983+
$this->expectException(InvalidArgumentException::class);
1984+
$this->expectErrorMessage('Non-numeric value passed as increment amount for column: \'col\'.');
1985+
$builder = $this->getBuilder();
1986+
$builder->from('users')->incrementColumns(['col' => 'a']);
1987+
}
1988+
1989+
public function testIncrementManyArgumentValidation2()
1990+
{
1991+
$this->expectException(InvalidArgumentException::class);
1992+
$this->expectErrorMessage('Non-associative array passed to incrementMany method.');
1993+
$builder = $this->getBuilder();
1994+
$builder->from('users')->incrementColumns([11 => 11]);
1995+
}
1996+
19811997
public function testWhereNotWithArrayConditions()
19821998
{
19831999
$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)->incrementColumns([
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)->incrementColumns([
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')->incrementColumns([
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')->incrementColumns([
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)