From 5e4cf151d338b2ff6d8e1cc80e67ec164bd0a8cc Mon Sep 17 00:00:00 2001 From: Iman Ghafoori Date: Tue, 10 Jan 2023 01:18:47 +0330 Subject: [PATCH 1/2] Add incrementColumns to QueryBuilder --- src/Illuminate/Database/Query/Builder.php | 32 ++++- tests/Database/DatabaseQueryBuilderTest.php | 16 +++ .../Integration/Database/QueryBuilderTest.php | 113 ++++++++++++++++++ 3 files changed, 156 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index b1bde865d84e..4ccea411033f 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -3392,6 +3392,32 @@ public function upsert(array $values, $uniqueBy, $update = null) ); } + /** + * Atomically increments columns values by the given amounts. + * + * @param array $columns + * @param array $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. * @@ -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); } /** diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 2d0359ee1fc4..e9a467f7ea4a 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -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(); diff --git a/tests/Integration/Database/QueryBuilderTest.php b/tests/Integration/Database/QueryBuilderTest.php index 2c76a3108cb6..0570eeb1c0e7 100644 --- a/tests/Integration/Database/QueryBuilderTest.php +++ b/tests/Integration/Database/QueryBuilderTest.php @@ -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']; From 59580e9bf1b984168611d46f63677de7615ee99e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 16 Jan 2023 14:27:13 -0600 Subject: [PATCH 2/2] formatting --- src/Illuminate/Database/Query/Builder.php | 62 ++++++++++++------- tests/Database/DatabaseQueryBuilderTest.php | 6 +- .../Integration/Database/QueryBuilderTest.php | 8 +-- 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 4ccea411033f..e4f705265202 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -3393,7 +3393,26 @@ public function upsert(array $values, $uniqueBy, $update = null) } /** - * Atomically increments columns values by the given amounts. + * Increment a column's value by a given amount. + * + * @param string $column + * @param float|int $amount + * @param array $extra + * @return int + * + * @throws \InvalidArgumentException + */ + public function increment($column, $amount = 1, array $extra = []) + { + if (! is_numeric($amount)) { + throw new InvalidArgumentException('Non-numeric value passed to increment method.'); + } + + return $this->incrementEach([$column => $amount], $extra); + } + + /** + * Increment the given column's values by the given amounts. * * @param array $columns * @param array $extra @@ -3401,15 +3420,13 @@ public function upsert(array $values, $uniqueBy, $update = null) * * @throws \InvalidArgumentException */ - public function incrementColumns(array $columns, array $extra = []) + 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'."); - } - - if (! is_string($column)) { - throw new InvalidArgumentException('Non-associative array passed to incrementMany method.'); + } elseif (! is_string($column)) { + throw new InvalidArgumentException('Non-associative array passed to incrementEach method.'); } $columns[$column] = $this->raw("{$this->grammar->wrap($column)} + $amount"); @@ -3419,7 +3436,7 @@ public function incrementColumns(array $columns, array $extra = []) } /** - * Increment a column's value by a given amount. + * Decrement a column's value by a given amount. * * @param string $column * @param float|int $amount @@ -3428,36 +3445,37 @@ public function incrementColumns(array $columns, array $extra = []) * * @throws \InvalidArgumentException */ - public function increment($column, $amount = 1, array $extra = []) + public function decrement($column, $amount = 1, array $extra = []) { if (! is_numeric($amount)) { - throw new InvalidArgumentException('Non-numeric value passed to increment method.'); + throw new InvalidArgumentException('Non-numeric value passed to decrement method.'); } - return $this->incrementColumns([$column => $amount], $extra); + return $this->decrementEach([$column => $amount], $extra); } /** - * Decrement a column's value by a given amount. + * Decrement the given column's values by the given amounts. * - * @param string $column - * @param float|int $amount - * @param array $extra + * @param array $columns + * @param array $extra * @return int * * @throws \InvalidArgumentException */ - public function decrement($column, $amount = 1, array $extra = []) + public function decrementEach(array $columns, array $extra = []) { - if (! is_numeric($amount)) { - throw new InvalidArgumentException('Non-numeric value passed to decrement method.'); - } - - $wrapped = $this->grammar->wrap($column); + 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 = array_merge([$column => $this->raw("$wrapped - $amount")], $extra); + $columns[$column] = $this->raw("{$this->grammar->wrap($column)} - $amount"); + } - return $this->update($columns); + return $this->update(array_merge($columns, $extra)); } /** diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index e9a467f7ea4a..3cb1bb1c9801 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -1983,15 +1983,15 @@ 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']); + $builder->from('users')->incrementEach(['col' => 'a']); } public function testIncrementManyArgumentValidation2() { $this->expectException(InvalidArgumentException::class); - $this->expectErrorMessage('Non-associative array passed to incrementMany method.'); + $this->expectErrorMessage('Non-associative array passed to incrementEach method.'); $builder = $this->getBuilder(); - $builder->from('users')->incrementColumns([11 => 11]); + $builder->from('users')->incrementEach([11 => 11]); } public function testWhereNotWithArrayConditions() diff --git a/tests/Integration/Database/QueryBuilderTest.php b/tests/Integration/Database/QueryBuilderTest.php index 0570eeb1c0e7..d27072747d49 100644 --- a/tests/Integration/Database/QueryBuilderTest.php +++ b/tests/Integration/Database/QueryBuilderTest.php @@ -54,7 +54,7 @@ public function testIncrement() $connection = DB::table('accounting')->getConnection(); $connection->enableQueryLog(); - DB::table('accounting')->where('user_id', 2)->incrementColumns([ + DB::table('accounting')->where('user_id', 2)->incrementEach([ 'wallet_1' => 10, 'wallet_2' => -20, ], ['name' => 'foo']); @@ -83,7 +83,7 @@ public function testIncrement() ], (array) $rows[1]); // without the second argument. - $affectedRowsCount = DB::table('accounting')->where('user_id', 2)->incrementColumns([ + $affectedRowsCount = DB::table('accounting')->where('user_id', 2)->incrementEach([ 'wallet_1' => 20, 'wallet_2' => 20, ]); @@ -101,7 +101,7 @@ public function testIncrement() ], (array) $rows[1]); // Test Can affect multiple rows at once. - $affectedRowsCount = DB::table('accounting')->incrementColumns([ + $affectedRowsCount = DB::table('accounting')->incrementEach([ 'wallet_1' => 31.5, 'wallet_2' => '-32.5', ]); @@ -126,7 +126,7 @@ public function testIncrement() ], (array) $rows[1]); // In case of a conflict, the second argument wins and sets a fixed value: - $affectedRowsCount = DB::table('accounting')->incrementColumns([ + $affectedRowsCount = DB::table('accounting')->incrementEach([ 'wallet_1' => 3000, ], ['wallet_1' => 1.5]);