diff --git a/CHANGELOG-1.0.md b/CHANGELOG-1.0.md index 55d74184..f6731f3d 100644 --- a/CHANGELOG-1.0.md +++ b/CHANGELOG-1.0.md @@ -12,6 +12,8 @@ milestone. * [#117](https://github.com/alcaeus/mongo-php-adapter/pull/117) adds a missing flag to indexes when calling `MongoCollection::getIndexInfo`. + * [#120](https://github.com/alcaeus/mongo-php-adapter/pull/120) throws the proper + `MongoWriteConcernException` when encountering bulk write errors. 1.0.4 (2016-06-22) ------------------ diff --git a/lib/Mongo/MongoWriteBatch.php b/lib/Mongo/MongoWriteBatch.php index 48dd2e08..23dfb726 100644 --- a/lib/Mongo/MongoWriteBatch.php +++ b/lib/Mongo/MongoWriteBatch.php @@ -19,6 +19,9 @@ use Alcaeus\MongoDbAdapter\TypeConverter; use Alcaeus\MongoDbAdapter\Helper\WriteConcernConverter; +use MongoDB\Driver\Exception\BulkWriteException; +use MongoDB\Driver\WriteError; +use MongoDB\Driver\WriteResult; /** * MongoWriteBatch allows you to "batch up" multiple operations (of same type) @@ -115,55 +118,62 @@ final public function execute(array $writeOptions = []) $options['ordered'] = $writeOptions['ordered']; } - $collection = $this->collection->getCollection(); - try { - $result = $collection->BulkWrite($this->items, $options); + $writeResult = $this->collection->getCollection()->bulkWrite($this->items, $options); + $resultDocument = []; $ok = true; - } catch (\MongoDB\Driver\Exception\BulkWriteException $e) { - $result = $e->getWriteResult(); + } catch (BulkWriteException $e) { + $writeResult = $e->getWriteResult(); + $resultDocument = ['writeErrors' => $this->convertWriteErrors($writeResult)]; $ok = false; } - if ($ok === true) { - $this->items = []; - } + $this->items = []; switch ($this->batchType) { case self::COMMAND_UPDATE: $upsertedIds = []; - foreach ($result->getUpsertedIds() as $index => $id) { + foreach ($writeResult->getUpsertedIds() as $index => $id) { $upsertedIds[] = [ 'index' => $index, '_id' => TypeConverter::toLegacy($id) ]; } - $result = [ - 'nMatched' => $result->getMatchedCount(), - 'nModified' => $result->getModifiedCount(), - 'nUpserted' => $result->getUpsertedCount(), - 'ok' => $ok, + $resultDocument += [ + 'nMatched' => $writeResult->getMatchedCount(), + 'nModified' => $writeResult->getModifiedCount(), + 'nUpserted' => $writeResult->getUpsertedCount(), + 'ok' => true, ]; if (count($upsertedIds)) { - $result['upserted'] = $upsertedIds; + $resultDocument['upserted'] = $upsertedIds; } - - return $result; + break; case self::COMMAND_DELETE: - return [ - 'nRemoved' => $result->getDeletedCount(), - 'ok' => $ok, + $resultDocument += [ + 'nRemoved' => $writeResult->getDeletedCount(), + 'ok' => true, ]; + break; case self::COMMAND_INSERT: - return [ - 'nInserted' => $result->getInsertedCount(), - 'ok' => $ok, + $resultDocument += [ + 'nInserted' => $writeResult->getInsertedCount(), + 'ok' => true, ]; + break; + } + + if (! $ok) { + // Exception code is hardcoded to the value in ext-mongo, see + // https://github.com/mongodb/mongo-php-driver-legacy/blob/ab4bc0d90e93b3f247f6bcb386d0abc8d2fa7d74/batch/write.c#L428 + throw new \MongoWriteConcernException('Failed write', 911, null, $resultDocument); } + + return $resultDocument; } private function validate(array $item) @@ -214,4 +224,22 @@ private function addItem(array $item) break; } } + + /** + * @param WriteResult $result + * @return array + */ + private function convertWriteErrors(WriteResult $result) + { + $writeErrors = []; + /** @var WriteError $writeError */ + foreach ($result->getWriteErrors() as $writeError) { + $writeErrors[] = [ + 'index' => $writeError->getIndex(), + 'code' => $writeError->getCode(), + 'errmsg' => $writeError->getMessage(), + ]; + } + return $writeErrors; + } } diff --git a/lib/Mongo/MongoWriteConcernException.php b/lib/Mongo/MongoWriteConcernException.php index 4647184a..f8db1858 100644 --- a/lib/Mongo/MongoWriteConcernException.php +++ b/lib/Mongo/MongoWriteConcernException.php @@ -21,11 +21,34 @@ *

(PECL mongo >= 1.5.0)

* @link http://php.net/manual/en/class.mongowriteconcernexception.php#class.mongowriteconcernexception */ -class MongoWriteConcernException extends MongoCursorException { +class MongoWriteConcernException extends MongoCursorException +{ + private $document; + + /** + * MongoWriteConcernException constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + * @param null $document + * + * @internal The $document parameter is not part of the ext-mongo API + */ + public function __construct($message = '', $code = 0, Exception $previous = null, $document = null) + { + parent::__construct($message, $code, $previous); + + $this->document = $document; + } + /** * Get the error document * @link http://php.net/manual/en/mongowriteconcernexception.getdocument.php * @return array

A MongoDB document, if available, as an array.

*/ - public function getDocument() {} + public function getDocument() + { + return $this->document; + } } diff --git a/tests/Alcaeus/MongoDbAdapter/Mongo/MongoDeleteBatchTest.php b/tests/Alcaeus/MongoDbAdapter/Mongo/MongoDeleteBatchTest.php index 3e25d359..875a5886 100644 --- a/tests/Alcaeus/MongoDbAdapter/Mongo/MongoDeleteBatchTest.php +++ b/tests/Alcaeus/MongoDbAdapter/Mongo/MongoDeleteBatchTest.php @@ -58,7 +58,6 @@ public function testDeleteMany() $this->assertSame(0, $newCollection->count()); } - public function testValidateItem() { $collection = $this->getCollection(); diff --git a/tests/Alcaeus/MongoDbAdapter/Mongo/MongoInsertBatchTest.php b/tests/Alcaeus/MongoDbAdapter/Mongo/MongoInsertBatchTest.php index 0ffbdae9..06a47839 100644 --- a/tests/Alcaeus/MongoDbAdapter/Mongo/MongoInsertBatchTest.php +++ b/tests/Alcaeus/MongoDbAdapter/Mongo/MongoInsertBatchTest.php @@ -56,8 +56,10 @@ public function testInsertBatchError() try { $batch->execute(); + $this->fail('Expected MongoWriteConcernException'); } catch (\MongoWriteConcernException $e) { $this->assertSame('Failed write', $e->getMessage()); + $this->assertSame(911, $e->getCode()); $this->assertArraySubset($expected, $e->getDocument()); } } diff --git a/tests/Alcaeus/MongoDbAdapter/Mongo/MongoUpdateBatchTest.php b/tests/Alcaeus/MongoDbAdapter/Mongo/MongoUpdateBatchTest.php index 87fc70c0..afcbe3d6 100644 --- a/tests/Alcaeus/MongoDbAdapter/Mongo/MongoUpdateBatchTest.php +++ b/tests/Alcaeus/MongoDbAdapter/Mongo/MongoUpdateBatchTest.php @@ -39,6 +39,42 @@ public function testUpdateOne() $this->assertAttributeSame('foo', 'foo', $record); } + public function testUpdateOneException() + { + $collection = $this->getCollection(); + $batch = new \MongoUpdateBatch($collection); + + $document = ['foo' => 'bar']; + $collection->insert($document); + $document = ['foo' => 'foo']; + $collection->insert($document); + $collection->createIndex(['foo' => 1], ['unique' => true]); + + $this->assertTrue($batch->add(['q' => ['foo' => 'bar'], 'u' => ['$set' => ['foo' => 'foo']]])); + + $expected = [ + 'writeErrors' => [ + [ + 'index' => 0, + 'code' => 11000, + ] + ], + 'nMatched' => 0, + 'nModified' => 0, + 'nUpserted' => 0, + 'ok' => true, + ]; + + try { + $batch->execute(); + $this->fail('Expected MongoWriteConcernException'); + } catch (\MongoWriteConcernException $e) { + $this->assertSame('Failed write', $e->getMessage()); + $this->assertSame(911, $e->getCode()); + $this->assertArraySubset($expected, $e->getDocument()); + } + } + public function testUpdateMany() { $collection = $this->getCollection(); @@ -49,7 +85,6 @@ public function testUpdateMany() unset($document['_id']); $collection->insert($document); - $this->assertTrue($batch->add(['q' => ['foo' => 'bar'], 'u' => ['$set' => ['foo' => 'foo']], 'multi' => true])); $expected = [ @@ -69,6 +104,42 @@ public function testUpdateMany() $this->assertAttributeSame('foo', 'foo', $record); } + public function testUpdateManyException() + { + $collection = $this->getCollection(); + $batch = new \MongoUpdateBatch($collection); + + $document = ['foo' => 'bar', 'bar' => 'bar']; + $collection->insert($document); + $document = ['foo' => 'foobar', 'bar' => 'bar']; + $collection->insert($document); + $collection->createIndex(['foo' => 1], ['unique' => true]); + + $batch->add(['q' => ['bar' => 'bar'], 'u' => ['$set' => ['foo' => 'foo']], 'multi' => true]); + + $expected = [ + 'writeErrors' => [ + [ + 'index' => 0, + 'code' => 11000, + ] + ], + 'nMatched' => 0, + 'nModified' => 0, + 'nUpserted' => 0, + 'ok' => true, + ]; + + try { + $batch->execute(); + $this->fail('Expected MongoWriteConcernException'); + } catch (\MongoWriteConcernException $e) { + $this->assertSame('Failed write', $e->getMessage()); + $this->assertSame(911, $e->getCode()); + $this->assertArraySubset($expected, $e->getDocument()); + } + } + public function testUpsert() { $document = ['foo' => 'foo'];