Skip to content

Commit

Permalink
Merge pull request #45 from xp-forge/feature/findAndModify
Browse files Browse the repository at this point in the history
Implement Collection `modify()` and `remove()`  via findAndModify
  • Loading branch information
thekid authored Dec 30, 2023
2 parents 261421e + 45a1a23 commit 5707dc0
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 2 deletions.
48 changes: 46 additions & 2 deletions src/main/php/com/mongodb/Collection.class.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php namespace com\mongodb;

use com\mongodb\io\{Commands, Protocol};
use com\mongodb\result\{Insert, Update, Delete, Cursor, Run, ChangeStream};
use com\mongodb\result\{Insert, Update, Delete, Modification, Cursor, Run, ChangeStream};
use lang\Value;
use util\Objects;

Expand Down Expand Up @@ -97,7 +97,7 @@ public function upsert($query, $arg, Options... $options): Update {
}

/**
* Updates collection with given modifications.
* Updates collection with given statements.
*
* @param string|com.mongodb.ObjectId|[:var] $query
* @param [:var] $statements Update operator expressions
Expand All @@ -118,6 +118,29 @@ public function update($query, $statements, Options... $options): Update {
return new Update($result['body']);
}

/**
* Modifies collection and returns a `Modification` instance with the modified
* document.
*
* @param string|com.mongodb.ObjectId|[:var] $query
* @param [:var]|com.mongodb.Document $arg Update operator expressions or document
* @param bool $upsert
* @param com.mongodb.Options... $options
* @return com.mongodb.result.Modification
* @throws com.mongodb.Error
*/
public function modify($query, $arg, $upsert= false, Options... $options): Modification {
$result= $this->proto->write($options, [
'findAndModify' => $this->name,
'query' => is_array($query) ? $query : ['_id' => $query],
'update' => $arg,
'new' => true,
'upsert' => $upsert,
'$db' => $this->database,
]);
return new Modification($result['body']);
}

/**
* Delete documents
*
Expand All @@ -139,6 +162,27 @@ public function delete($query, Options... $options): Delete {
return new Delete($result['body']);
}

/**
* Modifies collection and returns a `Modification` instance with the removed
* document.
*
* @param string|com.mongodb.ObjectId|[:var] $query
* @param [:var]|com.mongodb.Document $arg Update operator expressions or document
* @param bool $upsert
* @param com.mongodb.Options... $options
* @return com.mongodb.result.Modification
* @throws com.mongodb.Error
*/
public function remove($query, Options... $options): Modification {
$result= $this->proto->write($options, [
'findAndModify' => $this->name,
'query' => is_array($query) ? $query : ['_id' => $query],
'remove' => true,
'$db' => $this->database,
]);
return new Modification($result['body']);
}

/**
* Find documents in this collection
*
Expand Down
49 changes: 49 additions & 0 deletions src/main/php/com/mongodb/result/Modification.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php namespace com\mongodb\result;

use com\mongodb\Document;

/**
* The result of a `findAndModify` operation
*
* @see com.mongodb.Collection::modify
* @see com.mongodb.Collection::remove
* @see https://www.mongodb.com/docs/manual/reference/command/findAndModify/
* @test com.mongodb.unittest.result.ModificationTest
*/
class Modification extends Result {
const REMOVED= 'removed';
const CREATED= 'created';
const UPDATED= 'updated';

/** Returns number of modified documents */
public function modified(): int { return $this->result['lastErrorObject']['n']; }

/** Returns kind of modification: created, updated or removed. */
public function kind(): string {
if (isset($this->result['lastErrorObject']['upserted'])) {
return self::CREATED;
} else if (isset($this->result['lastErrorObject']['updatedExisting'])) {
return self::UPDATED;
} else {
return self::REMOVED;
}
}

/**
* Returns the upserted ID, if any
*
* @return ?(string|com.mongodb.ObjectId)
*/
public function upserted() {
return $this->result['lastErrorObject']['upserted'] ?? null;
}

/**
* Returns the document
*
* @return ?com.mongodb.Document
*/
public function document() {
return isset($this->result['value']) ? new Document($this->result['value']) : null;
}
}
69 changes: 69 additions & 0 deletions src/test/php/com/mongodb/unittest/CollectionTest.class.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace com\mongodb\unittest;

use com\mongodb\result\Modification;
use com\mongodb\{Collection, Document, Int64, ObjectId, Options, Session, Error};
use test\{Assert, Before, Expect, Test, Values};
use util\UUID;
Expand Down Expand Up @@ -139,6 +140,47 @@ public function update_many() {
Assert::equals([2, 1], [$result->matched(), $result->modified()]);
}

#[Test]
public function modify_none() {
$body= $this->ok([
'lastErrorObject' => ['n' => 0, 'updatedExisting' => false],
'value' => null,
]);
$result= $this->newFixture($body)->modify(ObjectId::create(), ['$set' => ['test' => true]]);

Assert::equals([Modification::UPDATED, 0], [$result->kind(), $result->modified()]);
Assert::null($result->upserted());
Assert::null($result->document());
}

#[Test]
public function modify_existing() {
$doc= new Document(['_id' => ObjectId::create(), 'test' => true]);
$body= $this->ok([
'lastErrorObject' => ['n' => 1, 'updatedExisting' => true],
'value' => $doc->properties(),
]);
$result= $this->newFixture($body)->modify($doc->id(), ['$set' => ['test' => true]]);

Assert::equals([Modification::UPDATED, 1], [$result->kind(), $result->modified()]);
Assert::null($result->upserted());
Assert::equals($doc, $result->document());
}

#[Test]
public function create_new() {
$doc= new Document(['_id' => ObjectId::create(), 'test' => true]);
$body= $this->ok([
'lastErrorObject' => ['n' => 1, 'updatedExisting' => false, 'upserted' => $doc->id()],
'value' => $doc->properties(),
]);
$result= $this->newFixture($body)->modify($doc->id(), ['$set' => ['test' => true]]);

Assert::equals([Modification::CREATED, 1], [$result->kind(), $result->modified()]);
Assert::equals($doc->id(), $result->upserted());
Assert::equals($doc, $result->document());
}

#[Test]
public function delete_one() {
$result= $this->newFixture($this->ok(['n' => 1]))->delete('6100');
Expand All @@ -153,6 +195,33 @@ public function delete_many() {
Assert::equals(2, $result->deleted());
}

#[Test]
public function remove() {
$doc= new Document(['_id' => ObjectId::create(), 'test' => true]);
$body= $this->ok([
'lastErrorObject' => ['n' => 1],
'value' => $doc->properties(),
]);
$result= $this->newFixture($body)->remove($doc->id());

Assert::equals([Modification::REMOVED, 1], [$result->kind(), $result->modified()]);
Assert::null($result->upserted());
Assert::equals($doc, $result->document());
}

#[Test]
public function not_removed() {
$body= $this->ok([
'lastErrorObject' => ['n' => 0],
'value' => null,
]);
$result= $this->newFixture($body)->remove(ObjectId::create());

Assert::equals([Modification::REMOVED, 0], [$result->kind(), $result->modified()]);
Assert::null($result->upserted());
Assert::null($result->document());
}

#[Test]
public function count_empty() {
$collection= $this->newFixture($this->cursor([]));
Expand Down
108 changes: 108 additions & 0 deletions src/test/php/com/mongodb/unittest/result/ModificationTest.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php namespace com\mongodb\unittest\result;

use com\mongodb\result\Modification;
use com\mongodb\{Document, ObjectId};
use test\{Assert, Before, Test};

class ModificationTest {
private $objectId;

/** Creates a result from an update operation */
private function update(Document $document= null, $created= false) {
if (null === $document) {
$lastErrorObject= ['n' => 0, 'updatedExisting' => false];
$value= null;
} else if ($created) {
$lastErrorObject= ['n' => 1, 'updatedExisting' => false, 'upserted' => $document->id()];
$value= $document->properties();
} else {
$lastErrorObject= ['n' => 1, 'updatedExisting' => true];
$value= $document->properties();
}
return ['lastErrorObject' => $lastErrorObject, 'value' => $value, 'ok' => 1];
}

/** Creates a result from a remove operation */
private function remove(Document $document= null) {
if (null === $document) {
$lastErrorObject= ['n' => 0];
$value= null;
} else {
$lastErrorObject= ['n' => 1];
$value= $document->properties();
}
return ['lastErrorObject' => $lastErrorObject, 'value' => $value, 'ok' => 1];
}

#[Before]
public function objectId() {
$this->objectId= ObjectId::create();
}

#[Test]
public function can_create() {
new Modification($this->update());
}

#[Test]
public function none_modified() {
Assert::equals(0, (new Modification($this->update()))->modified());
}

#[Test]
public function modified() {
$doc= new Document(['test' => true]);
Assert::equals(1, (new Modification($this->update($doc)))->modified());
}

#[Test]
public function updated_existing() {
$doc= new Document(['_id' => $this->objectId, 'test' => true]);
Assert::equals(Modification::UPDATED, ((new Modification($this->update($doc)))->kind()));
}

#[Test]
public function created_new() {
$doc= new Document(['_id' => $this->objectId, 'test' => true]);
Assert::equals(Modification::CREATED, (new Modification($this->update($doc, true)))->kind());
}

#[Test]
public function not_upserted() {
$doc= new Document(['_id' => $this->objectId, 'test' => true]);
Assert::null((new Modification($this->update($doc)))->upserted());
}

#[Test]
public function upserted_id() {
$doc= new Document(['_id' => $this->objectId, 'test' => true]);
Assert::equals($this->objectId, (new Modification($this->update($doc, true)))->upserted());
}

#[Test]
public function document() {
$doc= new Document(['_id' => $this->objectId, 'test' => true]);
Assert::equals($doc, (new Modification($this->update($doc)))->document());
}

#[Test]
public function no_document() {
Assert::null((new Modification($this->update()))->document());
}

#[Test]
public function removed() {
$doc= new Document(['_id' => $this->objectId, 'test' => true]);
Assert::equals($doc, (new Modification($this->remove($doc)))->document());
}

#[Test]
public function not_removed() {
Assert::null((new Modification($this->remove()))->document());
}

#[Test]
public function removal_kind() {
Assert::equals(Modification::REMOVED, (new Modification($this->remove()))->kind());
}
}

0 comments on commit 5707dc0

Please sign in to comment.