Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Collection modify() and remove() via findAndModify #45

Merged
merged 7 commits into from
Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}
}
Loading