Skip to content

Commit acaba1a

Browse files
authored
PHPORM-114 Implement Builder::upsert (#3053)
1 parent a62d4b9 commit acaba1a

File tree

4 files changed

+112
-2
lines changed

4 files changed

+112
-2
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ All notable changes to this project will be documented in this file.
33

44
## [4.7.0] - coming soon
55

6+
* Add `Query\Builder::upsert()` method by @GromNaN in [#3052](https://github.com/mongodb/laravel-mongodb/pull/3052)
67
* Add `Connection::getServerVersion()` by @GromNaN in [#3043](https://github.com/mongodb/laravel-mongodb/pull/3043)
78
* Add `Schema\Builder::getTables()` and `getTableListing()` by @GromNaN in [#3044](https://github.com/mongodb/laravel-mongodb/pull/3044)
89
* Add `Schema\Builder::getColumns()` and `getIndexes()` by @GromNaN in [#3045](https://github.com/mongodb/laravel-mongodb/pull/3045)
9-
* Add `Schema\Builder::hasColumn` and `hasColumns` method by @Alex-Belyi in [#3002](https://github.com/mongodb/laravel-mongodb/pull/3001)
10+
* Add `Schema\Builder::hasColumn` and `hasColumns` method by @Alex-Belyi in [#3001](https://github.com/mongodb/laravel-mongodb/pull/3001)
1011

1112
## [4.6.0] - 2024-07-09
1213

src/Query/Builder.php

+41
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,47 @@ public function update(array $values, array $options = [])
725725
return $this->performUpdate($values, $options);
726726
}
727727

728+
/** @inheritdoc */
729+
public function upsert(array $values, $uniqueBy, $update = null): int
730+
{
731+
if ($values === []) {
732+
return 0;
733+
}
734+
735+
$this->applyBeforeQueryCallbacks();
736+
737+
$options = $this->inheritConnectionOptions();
738+
$uniqueBy = array_fill_keys((array) $uniqueBy, 1);
739+
740+
// If no update fields are specified, all fields are updated
741+
if ($update !== null) {
742+
$update = array_fill_keys((array) $update, 1);
743+
}
744+
745+
$bulk = [];
746+
747+
foreach ($values as $value) {
748+
$filter = $operation = [];
749+
foreach ($value as $key => $val) {
750+
if (isset($uniqueBy[$key])) {
751+
$filter[$key] = $val;
752+
}
753+
754+
if ($update === null || array_key_exists($key, $update)) {
755+
$operation['$set'][$key] = $val;
756+
} else {
757+
$operation['$setOnInsert'][$key] = $val;
758+
}
759+
}
760+
761+
$bulk[] = ['updateOne' => [$filter, $operation, ['upsert' => true]]];
762+
}
763+
764+
$result = $this->collection->bulkWrite($bulk, $options);
765+
766+
return $result->getInsertedCount() + $result->getUpsertedCount() + $result->getModifiedCount();
767+
}
768+
728769
/** @inheritdoc */
729770
public function increment($column, $amount = 1, array $extra = [], array $options = [])
730771
{

tests/ModelTest.php

+34
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,40 @@ public function testUpdate(): void
143143
$this->assertEquals('Hans Thomas', $check->fullname);
144144
}
145145

146+
public function testUpsert()
147+
{
148+
$result = User::upsert([
149+
['email' => 'foo', 'name' => 'bar'],
150+
['name' => 'bar2', 'email' => 'foo2'],
151+
], ['email']);
152+
153+
$this->assertSame(2, $result);
154+
155+
$this->assertSame(2, $result);
156+
$this->assertSame(2, User::count());
157+
$this->assertSame('bar', User::where('email', 'foo')->first()->name);
158+
159+
// Update 1 document
160+
$result = User::upsert([
161+
['email' => 'foo', 'name' => 'bar2'],
162+
['name' => 'bar2', 'email' => 'foo2'],
163+
], 'email', ['name']);
164+
165+
// Even if the same value is set for the 2nd document, the "updated_at" field is updated
166+
$this->assertSame(2, $result);
167+
$this->assertSame(2, User::count());
168+
$this->assertSame('bar2', User::where('email', 'foo')->first()->name);
169+
170+
// If no update fields are specified, all fields are updated
171+
$result = User::upsert([
172+
['email' => 'foo', 'name' => 'bar3'],
173+
], 'email');
174+
175+
$this->assertSame(1, $result);
176+
$this->assertSame(2, User::count());
177+
$this->assertSame('bar3', User::where('email', 'foo')->first()->name);
178+
}
179+
146180
public function testManualStringId(): void
147181
{
148182
$user = new User();

tests/QueryBuilderTest.php

+35-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Illuminate\Support\LazyCollection;
1212
use Illuminate\Support\Str;
1313
use Illuminate\Testing\Assert;
14+
use Illuminate\Tests\Database\DatabaseQueryBuilderTest;
1415
use InvalidArgumentException;
1516
use MongoDB\BSON\ObjectId;
1617
use MongoDB\BSON\Regex;
@@ -588,7 +589,7 @@ public function testSubdocumentArrayAggregate()
588589
$this->assertEquals(12, DB::collection('items')->avg('amount.*.hidden'));
589590
}
590591

591-
public function testUpsert()
592+
public function testUpdateWithUpsert()
592593
{
593594
DB::collection('items')->where('name', 'knife')
594595
->update(
@@ -607,6 +608,39 @@ public function testUpsert()
607608
$this->assertEquals(2, DB::collection('items')->count());
608609
}
609610

611+
public function testUpsert()
612+
{
613+
/** @see DatabaseQueryBuilderTest::testUpsertMethod() */
614+
// Insert 2 documents
615+
$result = DB::collection('users')->upsert([
616+
['email' => 'foo', 'name' => 'bar'],
617+
['name' => 'bar2', 'email' => 'foo2'],
618+
], 'email', 'name');
619+
620+
$this->assertSame(2, $result);
621+
$this->assertSame(2, DB::collection('users')->count());
622+
$this->assertSame('bar', DB::collection('users')->where('email', 'foo')->first()['name']);
623+
624+
// Update 1 document
625+
$result = DB::collection('users')->upsert([
626+
['email' => 'foo', 'name' => 'bar2'],
627+
['name' => 'bar2', 'email' => 'foo2'],
628+
], 'email', 'name');
629+
630+
$this->assertSame(1, $result);
631+
$this->assertSame(2, DB::collection('users')->count());
632+
$this->assertSame('bar2', DB::collection('users')->where('email', 'foo')->first()['name']);
633+
634+
// If no update fields are specified, all fields are updated
635+
$result = DB::collection('users')->upsert([
636+
['email' => 'foo', 'name' => 'bar3'],
637+
], 'email');
638+
639+
$this->assertSame(1, $result);
640+
$this->assertSame(2, DB::collection('users')->count());
641+
$this->assertSame('bar3', DB::collection('users')->where('email', 'foo')->first()['name']);
642+
}
643+
610644
public function testUnset()
611645
{
612646
$id1 = DB::collection('users')->insertGetId(['name' => 'John Doe', 'note1' => 'ABC', 'note2' => 'DEF']);

0 commit comments

Comments
 (0)