Jelajahi Sumber

Merge pull request #120 from alcaeus/throw-writeconcern-exception

Throw MongoWriteConcernException on bulk write errors
Andreas 9 tahun lalu
induk
melakukan
4fd36a29e5

+ 2 - 0
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)
 ------------------

+ 51 - 23
lib/Mongo/MongoWriteBatch.php

@@ -19,6 +19,9 @@ if (class_exists('MongoWriteBatch', false)) {
 
 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 @@ class MongoWriteBatch
             $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 @@ class MongoWriteBatch
                 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;
+    }
 }

+ 25 - 2
lib/Mongo/MongoWriteConcernException.php

@@ -21,11 +21,34 @@ if (class_exists('MongoWriteConcernException', false)) {
  * <p>(PECL mongo &gt;= 1.5.0)</p>
  * @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 <p>A MongoDB document, if available, as an array.</p>
      */
-    public function getDocument() {}
+    public function getDocument()
+    {
+        return $this->document;
+    }
 }

+ 0 - 1
tests/Alcaeus/MongoDbAdapter/Mongo/MongoDeleteBatchTest.php

@@ -58,7 +58,6 @@ class MongoDeleteBatchTest extends TestCase
         $this->assertSame(0, $newCollection->count());
     }
 
-
     public function testValidateItem()
     {
         $collection = $this->getCollection();

+ 2 - 0
tests/Alcaeus/MongoDbAdapter/Mongo/MongoInsertBatchTest.php

@@ -56,8 +56,10 @@ class MongoInsertBatchTest extends TestCase
 
         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());
         }
     }

+ 72 - 1
tests/Alcaeus/MongoDbAdapter/Mongo/MongoUpdateBatchTest.php

@@ -39,6 +39,42 @@ class MongoUpdateBatchTest extends TestCase
         $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 @@ class MongoUpdateBatchTest extends TestCase
         unset($document['_id']);
         $collection->insert($document);
 
-
         $this->assertTrue($batch->add(['q' => ['foo' => 'bar'], 'u' => ['$set' => ['foo' => 'foo']], 'multi' => true]));
 
         $expected = [
@@ -69,6 +104,42 @@ class MongoUpdateBatchTest extends TestCase
         $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'];