Skip to content

Commit

Permalink
PHPORM-114 Implement Builder::upsert (#3053)
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN authored Jul 19, 2024
1 parent a62d4b9 commit acaba1a
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 2 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ All notable changes to this project will be documented in this file.

## [4.7.0] - coming soon

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

## [4.6.0] - 2024-07-09

Expand Down
41 changes: 41 additions & 0 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,47 @@ public function update(array $values, array $options = [])
return $this->performUpdate($values, $options);
}

/** @inheritdoc */
public function upsert(array $values, $uniqueBy, $update = null): int
{
if ($values === []) {
return 0;
}

$this->applyBeforeQueryCallbacks();

$options = $this->inheritConnectionOptions();
$uniqueBy = array_fill_keys((array) $uniqueBy, 1);

// If no update fields are specified, all fields are updated
if ($update !== null) {
$update = array_fill_keys((array) $update, 1);
}

$bulk = [];

foreach ($values as $value) {
$filter = $operation = [];
foreach ($value as $key => $val) {
if (isset($uniqueBy[$key])) {
$filter[$key] = $val;
}

if ($update === null || array_key_exists($key, $update)) {
$operation['$set'][$key] = $val;
} else {
$operation['$setOnInsert'][$key] = $val;
}
}

$bulk[] = ['updateOne' => [$filter, $operation, ['upsert' => true]]];
}

$result = $this->collection->bulkWrite($bulk, $options);

return $result->getInsertedCount() + $result->getUpsertedCount() + $result->getModifiedCount();
}

/** @inheritdoc */
public function increment($column, $amount = 1, array $extra = [], array $options = [])
{
Expand Down
34 changes: 34 additions & 0 deletions tests/ModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,40 @@ public function testUpdate(): void
$this->assertEquals('Hans Thomas', $check->fullname);
}

public function testUpsert()
{
$result = User::upsert([
['email' => 'foo', 'name' => 'bar'],
['name' => 'bar2', 'email' => 'foo2'],
], ['email']);

$this->assertSame(2, $result);

$this->assertSame(2, $result);
$this->assertSame(2, User::count());
$this->assertSame('bar', User::where('email', 'foo')->first()->name);

// Update 1 document
$result = User::upsert([
['email' => 'foo', 'name' => 'bar2'],
['name' => 'bar2', 'email' => 'foo2'],
], 'email', ['name']);

// Even if the same value is set for the 2nd document, the "updated_at" field is updated
$this->assertSame(2, $result);
$this->assertSame(2, User::count());
$this->assertSame('bar2', User::where('email', 'foo')->first()->name);

// If no update fields are specified, all fields are updated
$result = User::upsert([
['email' => 'foo', 'name' => 'bar3'],
], 'email');

$this->assertSame(1, $result);
$this->assertSame(2, User::count());
$this->assertSame('bar3', User::where('email', 'foo')->first()->name);
}

public function testManualStringId(): void
{
$user = new User();
Expand Down
36 changes: 35 additions & 1 deletion tests/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Illuminate\Support\LazyCollection;
use Illuminate\Support\Str;
use Illuminate\Testing\Assert;
use Illuminate\Tests\Database\DatabaseQueryBuilderTest;
use InvalidArgumentException;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\Regex;
Expand Down Expand Up @@ -588,7 +589,7 @@ public function testSubdocumentArrayAggregate()
$this->assertEquals(12, DB::collection('items')->avg('amount.*.hidden'));
}

public function testUpsert()
public function testUpdateWithUpsert()
{
DB::collection('items')->where('name', 'knife')
->update(
Expand All @@ -607,6 +608,39 @@ public function testUpsert()
$this->assertEquals(2, DB::collection('items')->count());
}

public function testUpsert()
{
/** @see DatabaseQueryBuilderTest::testUpsertMethod() */
// Insert 2 documents
$result = DB::collection('users')->upsert([
['email' => 'foo', 'name' => 'bar'],
['name' => 'bar2', 'email' => 'foo2'],
], 'email', 'name');

$this->assertSame(2, $result);
$this->assertSame(2, DB::collection('users')->count());
$this->assertSame('bar', DB::collection('users')->where('email', 'foo')->first()['name']);

// Update 1 document
$result = DB::collection('users')->upsert([
['email' => 'foo', 'name' => 'bar2'],
['name' => 'bar2', 'email' => 'foo2'],
], 'email', 'name');

$this->assertSame(1, $result);
$this->assertSame(2, DB::collection('users')->count());
$this->assertSame('bar2', DB::collection('users')->where('email', 'foo')->first()['name']);

// If no update fields are specified, all fields are updated
$result = DB::collection('users')->upsert([
['email' => 'foo', 'name' => 'bar3'],
], 'email');

$this->assertSame(1, $result);
$this->assertSame(2, DB::collection('users')->count());
$this->assertSame('bar3', DB::collection('users')->where('email', 'foo')->first()['name']);
}

public function testUnset()
{
$id1 = DB::collection('users')->insertGetId(['name' => 'John Doe', 'note1' => 'ABC', 'note2' => 'DEF']);
Expand Down

0 comments on commit acaba1a

Please sign in to comment.