From 70fc9535d2fdbd00508b357323a161f8b41eb026 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 27 Aug 2015 16:55:36 -0400 Subject: [PATCH 1/7] Refer to findAndModify docs in related Collection methods --- src/Collection.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Collection.php b/src/Collection.php index b0465b4d9..f4908e0b4 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -422,6 +422,7 @@ public function findOne(array $filter = array(), array $options = array()) * The document to return may be null. * * @see FindOneAndDelete::__construct() for supported options + * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @param array|object $filter Query by which to filter documents * @param array $options Command options * @return object|null @@ -443,6 +444,7 @@ public function findOneAndDelete($filter, array $options = array()) * "returnDocument" option to return the updated document. * * @see FindOneAndReplace::__construct() for supported options + * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @param array|object $filter Query by which to filter documents * @param array|object $replacement Replacement document * @param array $options Command options @@ -465,6 +467,7 @@ public function findOneAndReplace($filter, $replacement, array $options = array( * "returnDocument" option to return the updated document. * * @see FindOneAndReplace::__construct() for supported options + * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @param array|object $filter Query by which to filter documents * @param array|object $update Update to apply to the matched document * @param array $options Command options From 15af46065f828dbceb4fa80bc88e7fa27917ea9f Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 27 Aug 2015 15:59:33 -0400 Subject: [PATCH 2/7] PHPLIB-109: Extract InsertOne and InsertMany operation classes --- src/Collection.php | 51 ++++++-------- src/Operation/InsertMany.php | 109 +++++++++++++++++++++++++++++ src/Operation/InsertOne.php | 76 ++++++++++++++++++++ tests/Operation/InsertManyTest.php | 50 +++++++++++++ tests/Operation/InsertOneTest.php | 25 +++++++ tests/Operation/TestCase.php | 33 +++++++++ 6 files changed, 314 insertions(+), 30 deletions(-) create mode 100644 src/Operation/InsertMany.php create mode 100644 src/Operation/InsertOne.php create mode 100644 tests/Operation/InsertManyTest.php create mode 100644 tests/Operation/InsertOneTest.php create mode 100644 tests/Operation/TestCase.php diff --git a/src/Collection.php b/src/Collection.php index f4908e0b4..fcac7ddcf 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -24,6 +24,8 @@ use MongoDB\Operation\FindOneAndDelete; use MongoDB\Operation\FindOneAndReplace; use MongoDB\Operation\FindOneAndUpdate; +use MongoDB\Operation\InsertMany; +use MongoDB\Operation\InsertOne; use MongoDB\Operation\ListIndexes; use Traversable; @@ -539,56 +541,45 @@ public function getWriteOptions() } /** - * Inserts the provided documents + * Inserts multiple documents. * + * @see InsertMany::__construct() for supported options * @see http://docs.mongodb.org/manual/reference/command/insert/ - * * @param array[]|object[] $documents The documents to insert + * @param array $options Command options * @return InsertManyResult */ - public function insertMany(array $documents) + public function insertMany(array $documents, array $options = array()) { - $options = array_merge($this->getWriteOptions()); - - $bulk = new BulkWrite($options["ordered"]); - $insertedIds = array(); - - foreach ($documents as $i => $document) { - $insertedId = $bulk->insert($document); - - if ($insertedId !== null) { - $insertedIds[$i] = $insertedId; - } else { - $insertedIds[$i] = is_array($document) ? $document['_id'] : $document->_id; - } + if ( ! isset($options['writeConcern']) && isset($this->wc)) { + $options['writeConcern'] = $this->wc; } - $writeResult = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); + $operation = new InsertMany($this->dbname, $this->collname, $documents, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return new InsertManyResult($writeResult, $insertedIds); + return $operation->execute($server); } /** - * Inserts the provided document + * Inserts one document. * + * @see InsertOne::__construct() for supported options * @see http://docs.mongodb.org/manual/reference/command/insert/ - * * @param array|object $document The document to insert + * @param array $options Command options * @return InsertOneResult */ - public function insertOne($document) + public function insertOne($document, array $options = array()) { - $options = array_merge($this->getWriteOptions()); - - $bulk = new BulkWrite($options["ordered"]); - $id = $bulk->insert($document); - $wr = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); - - if ($id === null) { - $id = is_array($document) ? $document['_id'] : $document->_id; + if ( ! isset($options['writeConcern']) && isset($this->wc)) { + $options['writeConcern'] = $this->wc; } - return new InsertOneResult($wr, $id); + $operation = new InsertOne($this->dbname, $this->collname, $document, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + + return $operation->execute($server); } /** diff --git a/src/Operation/InsertMany.php b/src/Operation/InsertMany.php new file mode 100644 index 000000000..2786ed9ed --- /dev/null +++ b/src/Operation/InsertMany.php @@ -0,0 +1,109 @@ + $document) { + if ($i !== $expectedIndex) { + throw new InvalidArgumentException(sprintf('$documents is not a list (unexpected index: "%s")', $i)); + } + + if ( ! is_array($document) && ! is_object($document)) { + throw new InvalidArgumentTypeException(sprintf('$documents[%d]', $i), $document, 'array or object'); + } + + $expectedIndex += 1; + } + + $options += array( + 'ordered' => true, + ); + + if ( ! is_bool($options['ordered'])) { + throw new InvalidArgumentTypeException('"ordered" option', $options['ordered'], 'boolean'); + } + + if (array_key_exists('writeConcern', $options) && ! $options['writeConcern'] instanceof WriteConcern) { + throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + } + + $this->databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->documents = $documents; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return InsertManyResult + */ + public function execute(Server $server) + { + $bulk = new BulkWrite($this->options['ordered']); + $insertedIds = array(); + + foreach ($this->documents as $i => $document) { + $insertedId = $bulk->insert($document); + + if ($insertedId !== null) { + $insertedIds[$i] = $insertedId; + } else { + // TODO: This may be removed if PHPC-382 is implemented + $insertedIds[$i] = is_array($document) ? $document['_id'] : $document->_id; + } + } + + $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; + $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); + + return new InsertManyResult($writeResult, $insertedIds); + } +} diff --git a/src/Operation/InsertOne.php b/src/Operation/InsertOne.php new file mode 100644 index 000000000..ef5743fde --- /dev/null +++ b/src/Operation/InsertOne.php @@ -0,0 +1,76 @@ +databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->document = $document; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return InsertOneResult + */ + public function execute(Server $server) + { + $bulk = new BulkWrite(); + $insertedId = $bulk->insert($this->document); + + if ($insertedId === null) { + // TODO: This may be removed if PHPC-382 is implemented + $insertedId = is_array($this->document) ? $this->document['_id'] : $this->document->_id; + } + + $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; + $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); + + return new InsertOneResult($writeResult, $insertedId); + } +} diff --git a/tests/Operation/InsertManyTest.php b/tests/Operation/InsertManyTest.php new file mode 100644 index 000000000..d2eab8cec --- /dev/null +++ b/tests/Operation/InsertManyTest.php @@ -0,0 +1,50 @@ +getDatabaseName(), $this->getCollectionName(), array()); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + */ + public function testConstructorDocumentsMustBeAList() + { + new InsertMany($this->getDatabaseName(), $this->getCollectionName(), array(1 => array('x' => 1))); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @dataProvider provideInvalidDocumentArguments + */ + public function testConstructorDocumentsElementType($document) + { + new InsertMany($this->getDatabaseName(), $this->getCollectionName(), array($document)); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @dataProvider provideInvalidBooleanArguments + */ + public function testConstructorOrderedOptionType($ordered) + { + new InsertMany($this->getDatabaseName(), $this->getCollectionName(), array(array('x' => 1)), array('ordered' => $ordered)); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + */ + public function testConstructorWriteConcernOptionType() + { + new InsertMany($this->getDatabaseName(), $this->getCollectionName(), array(array('x' => 1)), array('writeConcern' => null)); + } +} diff --git a/tests/Operation/InsertOneTest.php b/tests/Operation/InsertOneTest.php new file mode 100644 index 000000000..5dffd2b55 --- /dev/null +++ b/tests/Operation/InsertOneTest.php @@ -0,0 +1,25 @@ +getDatabaseName(), $this->getCollectionName(), $document); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + */ + public function testConstructorWriteConcernOptionType() + { + new InsertOne($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('writeConcern' => null)); + } +} diff --git a/tests/Operation/TestCase.php b/tests/Operation/TestCase.php new file mode 100644 index 000000000..e20549b16 --- /dev/null +++ b/tests/Operation/TestCase.php @@ -0,0 +1,33 @@ + Date: Thu, 27 Aug 2015 16:48:20 -0400 Subject: [PATCH 3/7] PHPLIB-109: Extract DeleteOne and DeleteMany operation classes --- src/Collection.php | 53 +++++++++++----------- src/Operation/Delete.php | 83 ++++++++++++++++++++++++++++++++++ src/Operation/DeleteMany.php | 49 ++++++++++++++++++++ src/Operation/DeleteOne.php | 49 ++++++++++++++++++++ tests/Operation/DeleteTest.php | 34 ++++++++++++++ 5 files changed, 241 insertions(+), 27 deletions(-) create mode 100644 src/Operation/Delete.php create mode 100644 src/Operation/DeleteMany.php create mode 100644 src/Operation/DeleteOne.php create mode 100644 tests/Operation/DeleteTest.php diff --git a/src/Collection.php b/src/Collection.php index fcac7ddcf..f1a5a06db 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -16,6 +16,8 @@ use MongoDB\Operation\Aggregate; use MongoDB\Operation\CreateIndexes; use MongoDB\Operation\Count; +use MongoDB\Operation\DeleteMany; +use MongoDB\Operation\DeleteOne; use MongoDB\Operation\Distinct; use MongoDB\Operation\DropCollection; use MongoDB\Operation\DropIndexes; @@ -289,35 +291,45 @@ public function createIndexes(array $indexes) } /** - * Deletes a document matching the $filter criteria. - * NOTE: Will delete ALL documents matching $filter + * Deletes all documents matching the filter. * + * @see DeleteMany::__construct() for supported options * @see http://docs.mongodb.org/manual/reference/command/delete/ - * - * @param array $filter The $filter criteria to delete + * @param array|object $filter Query by which to delete documents + * @param array $options Command options * @return DeleteResult */ - public function deleteMany(array $filter) + public function deleteMany($filter, array $options = array()) { - $wr = $this->_delete($filter, 0); + if ( ! isset($options['writeConcern']) && isset($this->wc)) { + $options['writeConcern'] = $this->wc; + } + + $operation = new DeleteMany($this->dbname, $this->collname, $filter, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return new DeleteResult($wr); + return $operation->execute($server); } /** - * Deletes a document matching the $filter criteria. - * NOTE: Will delete at most ONE document matching $filter + * Deletes at most one document matching the filter. * + * @see DeleteOne::__construct() for supported options * @see http://docs.mongodb.org/manual/reference/command/delete/ - * - * @param array $filter The $filter criteria to delete + * @param array|object $filter Query by which to delete documents + * @param array $options Command options * @return DeleteResult */ - public function deleteOne(array $filter) + public function deleteOne($filter, array $options = array()) { - $wr = $this->_delete($filter); + if ( ! isset($options['writeConcern']) && isset($this->wc)) { + $options['writeConcern'] = $this->wc; + } - return new DeleteResult($wr); + $operation = new DeleteOne($this->dbname, $this->collname, $filter, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + + return $operation->execute($server); } /** @@ -660,19 +672,6 @@ public function updateOne(array $filter, array $update, array $options = array() return new UpdateResult($wr); } - /** - * Internal helper for delete one/many documents - * @internal - */ - final protected function _delete($filter, $limit = 1) - { - $options = array_merge($this->getWriteOptions(), array("limit" => $limit)); - - $bulk = new BulkWrite($options["ordered"]); - $bulk->delete($filter, $options); - return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); - } - /** * Internal helper for replacing/updating one/many documents * @internal diff --git a/src/Operation/Delete.php b/src/Operation/Delete.php new file mode 100644 index 000000000..66157f357 --- /dev/null +++ b/src/Operation/Delete.php @@ -0,0 +1,83 @@ +databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->filter = $filter; + $this->limit = $limit; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return DeleteResult + */ + public function execute(Server $server) + { + $bulk = new BulkWrite(); + $bulk->delete($this->filter, array('limit' => $this->limit)); + + $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; + $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); + + return new DeleteResult($writeResult); + } +} diff --git a/src/Operation/DeleteMany.php b/src/Operation/DeleteMany.php new file mode 100644 index 000000000..e07f5866e --- /dev/null +++ b/src/Operation/DeleteMany.php @@ -0,0 +1,49 @@ +delete = new Delete($databaseName, $collectionName, $filter, 0, $options); + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return DeleteResult + */ + public function execute(Server $server) + { + return $this->delete->execute($server); + } +} diff --git a/src/Operation/DeleteOne.php b/src/Operation/DeleteOne.php new file mode 100644 index 000000000..075b463db --- /dev/null +++ b/src/Operation/DeleteOne.php @@ -0,0 +1,49 @@ +delete = new Delete($databaseName, $collectionName, $filter, 1, $options); + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return DeleteResult + */ + public function execute(Server $server) + { + return $this->delete->execute($server); + } +} diff --git a/tests/Operation/DeleteTest.php b/tests/Operation/DeleteTest.php new file mode 100644 index 000000000..c22ec3342 --- /dev/null +++ b/tests/Operation/DeleteTest.php @@ -0,0 +1,34 @@ +getDatabaseName(), $this->getCollectionName(), $filter, 0); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @dataProvider provideInvalidDocumentArguments + */ + public function testConstructorLimitArgumentMustBeOneOrZero() + { + new Delete($this->getDatabaseName(), $this->getCollectionName(), array(), 2); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + */ + public function testConstructorWriteConcernOptionType() + { + new Delete($this->getDatabaseName(), $this->getCollectionName(), array(), 1, array('writeConcern' => null)); + } +} From 62397f02ed603872625d79c962c9afd402cfa917 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 27 Aug 2015 17:39:34 -0400 Subject: [PATCH 4/7] PHPLIB-109: Extract ReplaceOne, UpdateOne, and UpdateMany operation classes --- src/Collection.php | 86 ++++++++++------------ src/Operation/ReplaceOne.php | 68 ++++++++++++++++++ src/Operation/Update.php | 111 +++++++++++++++++++++++++++++ src/Operation/UpdateMany.php | 68 ++++++++++++++++++ src/Operation/UpdateOne.php | 68 ++++++++++++++++++ tests/Operation/ReplaceOneTest.php | 34 +++++++++ tests/Operation/UpdateManyTest.php | 34 +++++++++ tests/Operation/UpdateOneTest.php | 34 +++++++++ tests/Operation/UpdateTest.php | 52 ++++++++++++++ 9 files changed, 508 insertions(+), 47 deletions(-) create mode 100644 src/Operation/ReplaceOne.php create mode 100644 src/Operation/Update.php create mode 100644 src/Operation/UpdateMany.php create mode 100644 src/Operation/UpdateOne.php create mode 100644 tests/Operation/ReplaceOneTest.php create mode 100644 tests/Operation/UpdateManyTest.php create mode 100644 tests/Operation/UpdateOneTest.php create mode 100644 tests/Operation/UpdateTest.php diff --git a/src/Collection.php b/src/Collection.php index f1a5a06db..72986e9c6 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -29,6 +29,9 @@ use MongoDB\Operation\InsertMany; use MongoDB\Operation\InsertOne; use MongoDB\Operation\ListIndexes; +use MongoDB\Operation\ReplaceOne; +use MongoDB\Operation\UpdateMany; +use MongoDB\Operation\UpdateOne; use Traversable; class Collection @@ -609,79 +612,68 @@ public function listIndexes(array $options = array()) } /** - * Replace one document + * Replaces at most one document matching the filter. * + * @see ReplaceOne::__construct() for supported options * @see http://docs.mongodb.org/manual/reference/command/update/ - * @see Collection::getWriteOptions() for supported $options - * - * @param array $filter The document to be replaced - * @param array $update The document to replace with - * @param array $options Additional options + * @param array|object $filter Query by which to filter documents + * @param array|object $replacement Replacement document + * @param array $options Command options * @return UpdateResult */ - public function replaceOne(array $filter, array $update, array $options = array()) + public function replaceOne($filter, $replacement, array $options = array()) { - $firstKey = key($update); - if (isset($firstKey[0]) && $firstKey[0] == '$') { - throw new InvalidArgumentException("First key in \$update must NOT be a \$operator"); + if ( ! isset($options['writeConcern']) && isset($this->wc)) { + $options['writeConcern'] = $this->wc; } - $wr = $this->_update($filter, $update, $options + array("multi" => false)); - return new UpdateResult($wr); + $operation = new ReplaceOne($this->dbname, $this->collname, $filter, $replacement, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + + return $operation->execute($server); } /** - * Update one document - * NOTE: Will update ALL documents matching $filter + * Updates all documents matching the filter. * + * @see UpdateMany::__construct() for supported options * @see http://docs.mongodb.org/manual/reference/command/update/ - * @see Collection::getWriteOptions() for supported $options - * - * @param array $filter The document to be replaced - * @param array $update An array of update operators to apply to the document - * @param array $options Additional options + * @param array|object $filter Query by which to filter documents + * @param array|object $replacement Update to apply to the matched documents + * @param array $options Command options * @return UpdateResult */ - public function updateMany(array $filter, $update, array $options = array()) + public function updateMany($filter, $update, array $options = array()) { - $wr = $this->_update($filter, $update, $options + array("multi" => true)); + if ( ! isset($options['writeConcern']) && isset($this->wc)) { + $options['writeConcern'] = $this->wc; + } - return new UpdateResult($wr); + $operation = new UpdateMany($this->dbname, $this->collname, $filter, $update, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + + return $operation->execute($server); } /** - * Update one document - * NOTE: Will update at most ONE document matching $filter + * Updates at most one document matching the filter. * + * @see ReplaceOne::__construct() for supported options * @see http://docs.mongodb.org/manual/reference/command/update/ - * @see Collection::getWriteOptions() for supported $options - * - * @param array $filter The document to be replaced - * @param array $update An array of update operators to apply to the document - * @param array $options Additional options + * @param array|object $filter Query by which to filter documents + * @param array|object $replacement Update to apply to the matched document + * @param array $options Command options * @return UpdateResult */ - public function updateOne(array $filter, array $update, array $options = array()) + public function updateOne($filter, $update, array $options = array()) { - $firstKey = key($update); - if (!isset($firstKey[0]) || $firstKey[0] != '$') { - throw new InvalidArgumentException("First key in \$update must be a \$operator"); + if ( ! isset($options['writeConcern']) && isset($this->wc)) { + $options['writeConcern'] = $this->wc; } - $wr = $this->_update($filter, $update, $options + array("multi" => false)); - return new UpdateResult($wr); - } - - /** - * Internal helper for replacing/updating one/many documents - * @internal - */ - protected function _update($filter, $update, $options) - { - $options = array_merge($this->getWriteOptions(), $options); + $operation = new UpdateOne($this->dbname, $this->collname, $filter, $update, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - $bulk = new BulkWrite($options["ordered"]); - $bulk->update($filter, $update, $options); - return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); + return $operation->execute($server); } } diff --git a/src/Operation/ReplaceOne.php b/src/Operation/ReplaceOne.php new file mode 100644 index 000000000..d75727b37 --- /dev/null +++ b/src/Operation/ReplaceOne.php @@ -0,0 +1,68 @@ +update = new Update( + $databaseName, + $collectionName, + $filter, + $replacement, + array('multi' => false) + $options + ); + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return UpdateResult + */ + public function execute(Server $server) + { + return $this->update->execute($server); + } +} diff --git a/src/Operation/Update.php b/src/Operation/Update.php new file mode 100644 index 000000000..2999046bc --- /dev/null +++ b/src/Operation/Update.php @@ -0,0 +1,111 @@ + false, + 'upsert' => false, + ); + + if ( ! is_bool($options['multi'])) { + throw new InvalidArgumentTypeException('"multi" option', $options['multi'], 'boolean'); + } + + if ($options['multi'] && ! \MongoDB\is_first_key_operator($update)) { + throw new InvalidArgumentException('"multi" option cannot be true if $update is a replacement document'); + } + + if ( ! is_bool($options['upsert'])) { + throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean'); + } + + if (array_key_exists('writeConcern', $options) && ! $options['writeConcern'] instanceof WriteConcern) { + throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + } + + $this->databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->filter = $filter; + $this->update = $update; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return UpdateResult + */ + public function execute(Server $server) + { + $options = array( + 'multi' => $this->options['multi'], + 'upsert' => $this->options['upsert'], + ); + + $bulk = new BulkWrite(); + $bulk->update($this->filter, $this->update, $options); + + $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; + $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); + + return new UpdateResult($writeResult); + } +} diff --git a/src/Operation/UpdateMany.php b/src/Operation/UpdateMany.php new file mode 100644 index 000000000..4f7284b6d --- /dev/null +++ b/src/Operation/UpdateMany.php @@ -0,0 +1,68 @@ +update = new Update( + $databaseName, + $collectionName, + $filter, + $update, + array('multi' => true) + $options + ); + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return UpdateResult + */ + public function execute(Server $server) + { + return $this->update->execute($server); + } +} diff --git a/src/Operation/UpdateOne.php b/src/Operation/UpdateOne.php new file mode 100644 index 000000000..6e67e6694 --- /dev/null +++ b/src/Operation/UpdateOne.php @@ -0,0 +1,68 @@ +update = new Update( + $databaseName, + $collectionName, + $filter, + $update, + array('multi' => false) + $options + ); + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return UpdateResult + */ + public function execute(Server $server) + { + return $this->update->execute($server); + } +} diff --git a/tests/Operation/ReplaceOneTest.php b/tests/Operation/ReplaceOneTest.php new file mode 100644 index 000000000..a76c6f092 --- /dev/null +++ b/tests/Operation/ReplaceOneTest.php @@ -0,0 +1,34 @@ +getDatabaseName(), $this->getCollectionName(), $filter, array('y' => 1)); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @dataProvider provideInvalidDocumentArguments + */ + public function testConstructorReplacementArgumentType($replacement) + { + new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), $replacement); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + */ + public function testConstructorReplacementArgumentRequiresNoOperators() + { + new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('$set' => array('x' => 1))); + } +} diff --git a/tests/Operation/UpdateManyTest.php b/tests/Operation/UpdateManyTest.php new file mode 100644 index 000000000..3f420be6b --- /dev/null +++ b/tests/Operation/UpdateManyTest.php @@ -0,0 +1,34 @@ +getDatabaseName(), $this->getCollectionName(), $filter, array('$set' => array('x' => 1))); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @dataProvider provideInvalidDocumentArguments + */ + public function testConstructorUpdateArgumentType($update) + { + new UpdateMany($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), $update); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + */ + public function testConstructorUpdateArgumentRequiresOperators() + { + new UpdateMany($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('y' => 1)); + } +} diff --git a/tests/Operation/UpdateOneTest.php b/tests/Operation/UpdateOneTest.php new file mode 100644 index 000000000..28004ea9b --- /dev/null +++ b/tests/Operation/UpdateOneTest.php @@ -0,0 +1,34 @@ +getDatabaseName(), $this->getCollectionName(), $filter, array('$set' => array('x' => 1))); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @dataProvider provideInvalidDocumentArguments + */ + public function testConstructorUpdateArgumentType($update) + { + new UpdateOne($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), $update); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + */ + public function testConstructorUpdateArgumentRequiresOperators() + { + new UpdateOne($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('y' => 1)); + } +} diff --git a/tests/Operation/UpdateTest.php b/tests/Operation/UpdateTest.php new file mode 100644 index 000000000..e427e9b2a --- /dev/null +++ b/tests/Operation/UpdateTest.php @@ -0,0 +1,52 @@ +getDatabaseName(), $this->getCollectionName(), $filter, array('$set' => array('x' => 1))); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @dataProvider provideInvalidDocumentArguments + */ + public function testConstructorUpdateArgumentType($update) + { + new Update($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), $update); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @dataProvider provideInvalidBooleanArguments + */ + public function testConstructorMultiOptionType($multi) + { + new Update($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('y' => 1), array('multi' => $multi)); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @dataProvider provideInvalidBooleanArguments + */ + public function testConstructorUpsertOptionType($upsert) + { + new Update($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('y' => 1), array('upsert' => $upsert)); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + */ + public function testConstructorWriteConcernOptionType() + { + new Update($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('y' => 1), array('writeConcern' => null)); + } +} From c94cb8080aafe5ae33f0e833c95ab542611589ef Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 31 Aug 2015 16:31:59 -0400 Subject: [PATCH 5/7] PHPLIB-109: Extract BulkWrite operation class --- src/Collection.php | 164 +-------- src/Operation/BulkWrite.php | 247 ++++++++++++++ src/Operation/CreateCollection.php | 1 - src/Operation/CreateIndexes.php | 4 +- src/Operation/Delete.php | 4 +- src/Operation/InsertMany.php | 4 +- src/Operation/InsertOne.php | 4 +- src/Operation/Update.php | 4 +- tests/Collection/BulkWriteFunctionalTest.php | 10 +- tests/Operation/BulkWriteTest.php | 329 +++++++++++++++++++ 10 files changed, 604 insertions(+), 167 deletions(-) create mode 100644 src/Operation/BulkWrite.php create mode 100644 tests/Operation/BulkWriteTest.php diff --git a/src/Collection.php b/src/Collection.php index 72986e9c6..0bc822066 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -2,7 +2,6 @@ namespace MongoDB; -use MongoDB\Driver\BulkWrite; use MongoDB\Driver\Command; use MongoDB\Driver\Cursor; use MongoDB\Driver\Manager; @@ -14,6 +13,7 @@ use MongoDB\Model\IndexInfoIterator; use MongoDB\Model\IndexInput; use MongoDB\Operation\Aggregate; +use MongoDB\Operation\BulkWrite; use MongoDB\Operation\CreateIndexes; use MongoDB\Operation\Count; use MongoDB\Operation\DeleteMany; @@ -101,135 +101,23 @@ public function aggregate(array $pipeline, array $options = array()) } /** - * Adds a full set of write operations into a bulk and executes it - * - * The syntax of the $bulk array is: - * $bulk = [ - * [ - * 'METHOD' => [ - * $document, - * $extraArgument1, - * $extraArgument2, - * ], - * ], - * [ - * 'METHOD' => [ - * $document, - * $extraArgument1, - * $extraArgument2, - * ], - * ], - * ] - * - * - * Where METHOD is one of - * - 'insertOne' - * Supports no $extraArgument - * - 'updateMany' - * Requires $extraArgument1, same as $update for Collection::updateMany() - * Optional $extraArgument2, same as $options for Collection::updateMany() - * - 'updateOne' - * Requires $extraArgument1, same as $update for Collection::updateOne() - * Optional $extraArgument2, same as $options for Collection::updateOne() - * - 'replaceOne' - * Requires $extraArgument1, same as $update for Collection::replaceOne() - * Optional $extraArgument2, same as $options for Collection::replaceOne() - * - 'deleteOne' - * Supports no $extraArgument - * - 'deleteMany' - * Supports no $extraArgument - * - * @example Collection-bulkWrite.php Using Collection::bulkWrite() - * - * @see Collection::getBulkOptions() for supported $options - * - * @param array $ops Array of operations - * @param array $options Additional options - * @return WriteResult + * Executes multiple write operations. + * + * @see BulkWrite::__construct() for supported options + * @param array[] $operations List of write operations + * @param array $options Command options + * @return BulkWriteResult */ - public function bulkWrite(array $ops, array $options = array()) + public function bulkWrite(array $operations, array $options = array()) { - $options = array_merge($this->getBulkOptions(), $options); - - $bulk = new BulkWrite($options["ordered"]); - $insertedIds = array(); - - foreach ($ops as $n => $op) { - foreach ($op as $opname => $args) { - if (!isset($args[0])) { - throw new InvalidArgumentException(sprintf("Missing argument#1 for '%s' (operation#%d)", $opname, $n)); - } - - switch ($opname) { - case "insertOne": - $insertedId = $bulk->insert($args[0]); - - if ($insertedId !== null) { - $insertedIds[$n] = $insertedId; - } else { - $insertedIds[$n] = is_array($args[0]) ? $args[0]['_id'] : $args[0]->_id; - } - - break; - - case "updateMany": - if (!isset($args[1])) { - throw new InvalidArgumentException(sprintf("Missing argument#2 for '%s' (operation#%d)", $opname, $n)); - } - $options = array_merge($this->getWriteOptions(), isset($args[2]) ? $args[2] : array(), array("multi" => true)); - $firstKey = key($args[1]); - if (!isset($firstKey[0]) || $firstKey[0] != '$') { - throw new InvalidArgumentException("First key in \$update must be a \$operator"); - } - - $bulk->update($args[0], $args[1], $options); - break; - - case "updateOne": - if (!isset($args[1])) { - throw new InvalidArgumentException(sprintf("Missing argument#2 for '%s' (operation#%d)", $opname, $n)); - } - $options = array_merge($this->getWriteOptions(), isset($args[2]) ? $args[2] : array(), array("multi" => false)); - $firstKey = key($args[1]); - if (!isset($firstKey[0]) || $firstKey[0] != '$') { - throw new InvalidArgumentException("First key in \$update must be a \$operator"); - } - - $bulk->update($args[0], $args[1], $options); - break; - - case "replaceOne": - if (!isset($args[1])) { - throw new InvalidArgumentException(sprintf("Missing argument#2 for '%s' (operation#%d)", $opname, $n)); - } - $options = array_merge($this->getWriteOptions(), isset($args[2]) ? $args[2] : array(), array("multi" => false)); - $firstKey = key($args[1]); - if (isset($firstKey[0]) && $firstKey[0] == '$') { - throw new InvalidArgumentException("First key in \$update must NOT be a \$operator"); - } - - $bulk->update($args[0], $args[1], $options); - break; - - case "deleteOne": - $options = array_merge($this->getWriteOptions(), isset($args[1]) ? $args[1] : array(), array("limit" => 1)); - $bulk->delete($args[0], $options); - break; - - case "deleteMany": - $options = array_merge($this->getWriteOptions(), isset($args[1]) ? $args[1] : array(), array("limit" => 0)); - $bulk->delete($args[0], $options); - break; - - default: - throw new InvalidArgumentException(sprintf("Unknown operation type called '%s' (operation#%d)", $opname, $n)); - } - } + if ( ! isset($options['writeConcern']) && isset($this->wc)) { + $options['writeConcern'] = $this->wc; } - $writeResult = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); + $operation = new BulkWrite($this->dbname, $this->collname, $operations, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return new BulkWriteResult($writeResult, $insertedIds); + return $operation->execute($server); } /** @@ -498,18 +386,6 @@ public function findOneAndUpdate($filter, $update, array $options = array()) return $operation->execute($server); } - /** - * Retrieves all Bulk Write options with their default values. - * - * @return array of available Bulk Write options - */ - public function getBulkOptions() - { - return array( - "ordered" => false, - ); - } - /** * Return the collection name. * @@ -541,20 +417,6 @@ public function getNamespace() return $this->ns; } - /** - * Retrieves all Write options with their default values. - * - * @return array of available Write options - */ - public function getWriteOptions() - { - return array( - "ordered" => false, - "upsert" => false, - "limit" => 1, - ); - } - /** * Inserts multiple documents. * diff --git a/src/Operation/BulkWrite.php b/src/Operation/BulkWrite.php new file mode 100644 index 000000000..54ebebaf6 --- /dev/null +++ b/src/Operation/BulkWrite.php @@ -0,0 +1,247 @@ + [ $filter ] ], + * [ 'deleteMany' => [ $filter ] ], + * [ 'insertOne' => [ $document ] ], + * [ 'replaceOne' => [ $filter, $replacement, $options ] ], + * [ 'updateMany' => [ $filter, $update, $options ] ], + * [ 'updateOne' => [ $filter, $update, $options ] ], + * ] + * + * Arguments correspond to the respective Operation classes; however, the + * writeConcern option is specified for the top-level bulk write operation + * instead of each individual operations. + * + * Supported options for replaceOne, updateMany, and updateOne operations: + * + * * upsert (boolean): When true, a new document is created if no document + * matches the query. The default is false. + * + * Supported options for the bulk write operation: + * + * * ordered (boolean): If true, when an insert fails, return without + * performing the remaining writes. If false, when a write fails, + * continue with the remaining writes, if any. The default is true. + * + * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. + * + * @param string $databaseName Database name + * @param string $collectionName Collection name + * @param array[] $operations List of write operations + * @param array $options Command options + * @throws InvalidArgumentException + */ + public function __construct($databaseName, $collectionName, array $operations, array $options = array()) + { + if (empty($operations)) { + throw new InvalidArgumentException('$operations is empty'); + } + + $expectedIndex = 0; + + foreach ($operations as $i => $operation) { + if ($i !== $expectedIndex) { + throw new InvalidArgumentException(sprintf('$operations is not a list (unexpected index: "%s")', $i)); + } + + if ( ! is_array($operation)) { + throw new InvalidArgumentTypeException(sprintf('$operations[%d]', $i), $operation, 'array'); + } + + if (count($operation) !== 1) { + throw new InvalidArgumentException(sprintf('Expected one element in $operation[%d], actually: %d', $i, count($operation))); + } + + $type = key($operation); + $args = current($operation); + + if ( ! isset($args[0]) && ! array_key_exists(0, $args)) { + throw new InvalidArgumentException(sprintf('Missing first argument for $operations[%d]["%s"]', $i, $type)); + } + + if ( ! is_array($args[0]) && ! is_object($args[0])) { + throw new InvalidArgumentTypeException(sprintf('$operations[%d]["%s"][0]', $i, $type), $args[0], 'array or object'); + } + + switch ($type) { + case self::INSERT_ONE: + break; + + case self::DELETE_MANY: + case self::DELETE_ONE: + $operations[$i][$type][1] = array('limit' => ($type === self::DELETE_ONE ? 1 : 0)); + + break; + + case self::REPLACE_ONE: + if ( ! isset($args[1]) && ! array_key_exists(1, $args)) { + throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type)); + } + + if ( ! is_array($args[1]) && ! is_object($args[1])) { + throw new InvalidArgumentTypeException(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object'); + } + + if (\MongoDB\is_first_key_operator($args[1])) { + throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is an update operator', $i, $type)); + } + + if ( ! isset($args[2])) { + $args[2] = array(); + } + + if ( ! is_array($args[2])) { + throw new InvalidArgumentTypeException(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array'); + } + + $args[2]['multi'] = false; + $args[2] += array('upsert' => false); + + if ( ! is_bool($args[2]['upsert'])) { + throw new InvalidArgumentTypeException(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean'); + } + + $operations[$i][$type][2] = $args[2]; + + break; + + case self::UPDATE_MANY: + case self::UPDATE_ONE: + if ( ! isset($args[1]) && ! array_key_exists(1, $args)) { + throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type)); + } + + if ( ! is_array($args[1]) && ! is_object($args[1])) { + throw new InvalidArgumentTypeException(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object'); + } + + if ( ! \MongoDB\is_first_key_operator($args[1])) { + throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is not an update operator', $i, $type)); + } + + if ( ! isset($args[2])) { + $args[2] = array(); + } + + if ( ! is_array($args[2])) { + throw new InvalidArgumentTypeException(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array'); + } + + $args[2]['multi'] = ($type === self::UPDATE_MANY); + $args[2] += array('upsert' => false); + + if ( ! is_bool($args[2]['upsert'])) { + throw new InvalidArgumentTypeException(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean'); + } + + $operations[$i][$type][2] = $args[2]; + + break; + + default: + throw new InvalidArgumentException(sprintf('Unknown operation type "%s" in $operations[%d]', $type, $i)); + } + + $expectedIndex += 1; + } + + $options += array( + 'ordered' => true, + ); + + if ( ! is_bool($options['ordered'])) { + throw new InvalidArgumentTypeException('"ordered" option', $options['ordered'], 'boolean'); + } + + if (array_key_exists('writeConcern', $options) && ! $options['writeConcern'] instanceof WriteConcern) { + throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + } + + $this->databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->operations = $operations; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return BulkWriteResult + */ + public function execute(Server $server) + { + $bulk = new Bulk($this->options['ordered']); + $insertedIds = array(); + + foreach ($this->operations as $i => $operation) { + $type = key($operation); + $args = current($operation); + + switch ($type) { + case self::DELETE_MANY: + case self::DELETE_ONE: + $bulk->delete($args[0], $args[1]); + break; + + case self::INSERT_ONE: + $insertedId = $bulk->insert($args[0]); + + if ($insertedId !== null) { + $insertedIds[$i] = $insertedId; + } else { + // TODO: This may be removed if PHPC-382 is implemented + $insertedIds[$i] = is_array($args[0]) ? $args[0]['_id'] : $args[0]->_id; + } + + break; + + case self::REPLACE_ONE: + case self::UPDATE_MANY: + case self::UPDATE_ONE: + $bulk->update($args[0], $args[1], $args[2]); + } + } + + $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; + $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); + + return new BulkWriteResult($writeResult, $insertedIds); + } +} diff --git a/src/Operation/CreateCollection.php b/src/Operation/CreateCollection.php index d62564e39..82b3411de 100644 --- a/src/Operation/CreateCollection.php +++ b/src/Operation/CreateCollection.php @@ -4,7 +4,6 @@ use MongoDB\Driver\Command; use MongoDB\Driver\Server; -use MongoDB\Driver\BulkWrite; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\RuntimeException; use MongoDB\Exception\UnexpectedTypeException; diff --git a/src/Operation/CreateIndexes.php b/src/Operation/CreateIndexes.php index 98237bbd0..765ce477d 100644 --- a/src/Operation/CreateIndexes.php +++ b/src/Operation/CreateIndexes.php @@ -4,7 +4,7 @@ use MongoDB\Driver\Command; use MongoDB\Driver\Server; -use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\BulkWrite as Bulk; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\RuntimeException; use MongoDB\Exception\UnexpectedTypeException; @@ -108,7 +108,7 @@ private function executeCommand(Server $server) */ private function executeLegacy(Server $server) { - $bulk = new BulkWrite(true); + $bulk = new Bulk(true); foreach ($this->indexes as $index) { $bulk->insert($index); diff --git a/src/Operation/Delete.php b/src/Operation/Delete.php index 66157f357..acd23882d 100644 --- a/src/Operation/Delete.php +++ b/src/Operation/Delete.php @@ -3,7 +3,7 @@ namespace MongoDB\Operation; use MongoDB\DeleteResult; -use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\BulkWrite as Bulk; use MongoDB\Driver\Server; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; @@ -72,7 +72,7 @@ public function __construct($databaseName, $collectionName, $filter, $limit, arr */ public function execute(Server $server) { - $bulk = new BulkWrite(); + $bulk = new Bulk(); $bulk->delete($this->filter, array('limit' => $this->limit)); $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; diff --git a/src/Operation/InsertMany.php b/src/Operation/InsertMany.php index 2786ed9ed..554a309c1 100644 --- a/src/Operation/InsertMany.php +++ b/src/Operation/InsertMany.php @@ -3,7 +3,7 @@ namespace MongoDB\Operation; use MongoDB\InsertManyResult; -use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\BulkWrite as Bulk; use MongoDB\Driver\Server; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; @@ -87,7 +87,7 @@ public function __construct($databaseName, $collectionName, array $documents, ar */ public function execute(Server $server) { - $bulk = new BulkWrite($this->options['ordered']); + $bulk = new Bulk($this->options['ordered']); $insertedIds = array(); foreach ($this->documents as $i => $document) { diff --git a/src/Operation/InsertOne.php b/src/Operation/InsertOne.php index ef5743fde..c582dda5e 100644 --- a/src/Operation/InsertOne.php +++ b/src/Operation/InsertOne.php @@ -3,7 +3,7 @@ namespace MongoDB\Operation; use MongoDB\InsertOneResult; -use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\BulkWrite as Bulk; use MongoDB\Driver\Server; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentTypeException; @@ -60,7 +60,7 @@ public function __construct($databaseName, $collectionName, $document, array $op */ public function execute(Server $server) { - $bulk = new BulkWrite(); + $bulk = new Bulk(); $insertedId = $bulk->insert($this->document); if ($insertedId === null) { diff --git a/src/Operation/Update.php b/src/Operation/Update.php index 2999046bc..3cc063667 100644 --- a/src/Operation/Update.php +++ b/src/Operation/Update.php @@ -3,7 +3,7 @@ namespace MongoDB\Operation; use MongoDB\UpdateResult; -use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\BulkWrite as Bulk; use MongoDB\Driver\Server; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; @@ -100,7 +100,7 @@ public function execute(Server $server) 'upsert' => $this->options['upsert'], ); - $bulk = new BulkWrite(); + $bulk = new Bulk(); $bulk->update($this->filter, $this->update, $options); $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; diff --git a/tests/Collection/BulkWriteFunctionalTest.php b/tests/Collection/BulkWriteFunctionalTest.php index c8a6def20..2b2185a71 100644 --- a/tests/Collection/BulkWriteFunctionalTest.php +++ b/tests/Collection/BulkWriteFunctionalTest.php @@ -128,7 +128,7 @@ public function testMixedOrderedOperations() /** * @expectedException MongoDB\Exception\InvalidArgumentException - * @expectedExceptionMessage Unknown operation type called 'foo' (operation#0) + * @expectedExceptionMessage Unknown operation type "foo" in $operations[0] */ public function testUnknownOperation() { @@ -139,7 +139,7 @@ public function testUnknownOperation() /** * @expectedException MongoDB\Exception\InvalidArgumentException - * @expectedExceptionMessageRegExp /Missing argument#\d+ for '\w+' \(operation#\d+\)/ + * @expectedExceptionMessageRegExp /Missing (first|second) argument for \$operations\[\d+\]\["\w+\"]/ * @dataProvider provideOpsWithMissingArguments */ public function testMissingArguments(array $ops) @@ -164,7 +164,7 @@ public function provideOpsWithMissingArguments() /** * @expectedException MongoDB\Exception\InvalidArgumentException - * @expectedExceptionMessage First key in $update must be a $operator + * @expectedExceptionMessage First key in $operations[0]["updateOne"][1] is not an update operator */ public function testUpdateOneRequiresUpdateOperators() { @@ -175,7 +175,7 @@ public function testUpdateOneRequiresUpdateOperators() /** * @expectedException MongoDB\Exception\InvalidArgumentException - * @expectedExceptionMessage First key in $update must be a $operator + * @expectedExceptionMessage First key in $operations[0]["updateMany"][1] is not an update operator */ public function testUpdateManyRequiresUpdateOperators() { @@ -186,7 +186,7 @@ public function testUpdateManyRequiresUpdateOperators() /** * @expectedException MongoDB\Exception\InvalidArgumentException - * @expectedExceptionMessage First key in $update must NOT be a $operator + * @expectedExceptionMessage First key in $operations[0]["replaceOne"][1] is an update operator */ public function testReplaceOneRequiresReplacementDocument() { diff --git a/tests/Operation/BulkWriteTest.php b/tests/Operation/BulkWriteTest.php new file mode 100644 index 000000000..c1b0c168d --- /dev/null +++ b/tests/Operation/BulkWriteTest.php @@ -0,0 +1,329 @@ +getDatabaseName(), $this->getCollectionName(), array()); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage $operations is not a list (unexpected index: "1") + */ + public function testOperationsMustBeAList() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + 1 => array(BulkWrite::INSERT_ONE => array(array('x' => 1))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Expected one element in $operation[0], actually: 2 + */ + public function testMultipleOperationsInOneElement() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array( + BulkWrite::INSERT_ONE => array(array('x' => 1)), + BulkWrite::DELETE_ONE => array(array('x' => 1)), + ), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Unknown operation type "foo" in $operations[0] + */ + public function testUnknownOperation() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array('foo' => array(array('_id' => 1))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing first argument for $operations[0]["insertOne"] + */ + public function testInsertOneDocumentArgumentMissing() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::INSERT_ONE => array()), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["insertOne"\]\[0\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidDocumentArguments + */ + public function testInsertOneDocumentArgumentType($document) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::INSERT_ONE => array($document)), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing first argument for $operations[0]["deleteMany"] + */ + public function testDeleteManyFilterArgumentMissing() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::DELETE_MANY => array()), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["deleteMany"\]\[0\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidDocumentArguments + */ + public function testDeleteManyFilterArgumentType($document) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::DELETE_MANY => array($document)), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing first argument for $operations[0]["deleteOne"] + */ + public function testDeleteOneFilterArgumentMissing() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::DELETE_ONE => array()), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["deleteOne"\]\[0\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidDocumentArguments + */ + public function testDeleteOneFilterArgumentType($document) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::DELETE_ONE => array($document)), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing first argument for $operations[0]["replaceOne"] + */ + public function testReplaceOneFilterArgumentMissing() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::REPLACE_ONE => array()), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["replaceOne"\]\[0\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidDocumentArguments + */ + public function testReplaceOneFilterArgumentType($filter) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::REPLACE_ONE => array($filter, array('y' => 1))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing second argument for $operations[0]["replaceOne"] + */ + public function testReplaceOneReplacementArgumentMissing() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::REPLACE_ONE => array(array('x' => 1))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["replaceOne"\]\[1\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidDocumentArguments + */ + public function testReplaceOneReplacementArgumentType($replacement) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::REPLACE_ONE => array(array('x' => 1), $replacement)), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage First key in $operations[0]["replaceOne"][1] is an update operator + */ + public function testReplaceOneReplacementArgumentRequiresNoOperators() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::REPLACE_ONE => array(array('_id' => 1), array('$inc' => array('x' => 1)))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["replaceOne"\]\[2\]\["upsert"\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidBooleanArguments + */ + public function testReplaceOneUpsertOptionType($upsert) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::REPLACE_ONE => array(array('x' => 1), array('y' => 1), array('upsert' => $upsert))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing first argument for $operations[0]["updateMany"] + */ + public function testUpdateManyFilterArgumentMissing() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_MANY => array()), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["updateMany"\]\[0\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidDocumentArguments + */ + public function testUpdateManyFilterArgumentType($filter) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_MANY => array($filter, array('$set' => array('x' => 1)))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing second argument for $operations[0]["updateMany"] + */ + public function testUpdateManyUpdateArgumentMissing() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_MANY => array(array('x' => 1))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["updateMany"\]\[1\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidDocumentArguments + */ + public function testUpdateManyUpdateArgumentType($update) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_MANY => array(array('x' => 1), $update)), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage First key in $operations[0]["updateMany"][1] is not an update operator + */ + public function testUpdateManyUpdateArgumentRequiresOperators() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_MANY => array(array('_id' => array('$gt' => 1)), array('x' => 1))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["updateMany"\]\[2\]\["upsert"\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidBooleanArguments + */ + public function testUpdateManyUpsertOptionType($upsert) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_MANY => array(array('x' => 1), array('$set' => array('x' => 1)), array('upsert' => $upsert))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing first argument for $operations[0]["updateOne"] + */ + public function testUpdateOneFilterArgumentMissing() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_ONE => array()), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["updateOne"\]\[0\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidDocumentArguments + */ + public function testUpdateOneFilterArgumentType($filter) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_ONE => array($filter, array('$set' => array('x' => 1)))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing second argument for $operations[0]["updateOne"] + */ + public function testUpdateOneUpdateArgumentMissing() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_ONE => array(array('x' => 1))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["updateOne"\]\[1\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidDocumentArguments + */ + public function testUpdateOneUpdateArgumentType($update) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_ONE => array(array('x' => 1), $update)), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + * @expectedExceptionMessage First key in $operations[0]["updateOne"][1] is not an update operator + */ + public function testUpdateOneUpdateArgumentRequiresOperators() + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_ONE => array(array('_id' => 1), array('x' => 1))), + )); + } + + /** + * @expectedException MongoDB\Exception\InvalidArgumentTypeException + * @expectedExceptionMessageRegExp /Expected \$operations\[0\]\["updateOne"\]\[2\]\["upsert"\] to have type "[\w ]+" but found "[\w ]+"/ + * @dataProvider provideInvalidBooleanArguments + */ + public function testUpdateOneUpsertOptionType($upsert) + { + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), array( + array(BulkWrite::UPDATE_ONE => array(array('x' => 1), array('$set' => array('x' => 1)), array('upsert' => $upsert))), + )); + } +} From dd5c5ecd58c860ab1f59324b4f41d6d8785eb5f7 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 31 Aug 2015 16:32:56 -0400 Subject: [PATCH 6/7] Remove copypasta in CreateCollection --- src/Operation/CreateCollection.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Operation/CreateCollection.php b/src/Operation/CreateCollection.php index 82b3411de..562015b1a 100644 --- a/src/Operation/CreateCollection.php +++ b/src/Operation/CreateCollection.php @@ -95,9 +95,6 @@ public function __construct($databaseName, $collectionName, array $options = arr /** * Execute the operation. * - * For servers < 2.6, this will actually perform an insert operation on the - * database's "system.indexes" collection. - * * @see Executable::execute() * @param Server $server * @return object Command result document From c9ab86f94f8520a5eedcd57790cc67397834c142 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Sun, 30 Aug 2015 21:12:18 -0400 Subject: [PATCH 7/7] Print core dumps for segfaults on Travis See: https://github.com/springmeyer/travis-coredump/blob/master/.travis.yml --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index fa771335f..47e1bfa38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ before_install: install: - if dpkg --compare-versions ${SERVER_VERSION} le "2.4"; then export SERVER_PACKAGE=mongodb-10gen-enterprise; else export SERVER_PACKAGE=mongodb-enterprise; fi - sudo apt-get install ${SERVER_PACKAGE} + - sudo apt-get -y install gdb before_script: - if dpkg --compare-versions ${SERVER_VERSION} le "2.4"; then export SERVER_SERVICE=mongodb; else export SERVER_SERVICE=mongod; fi @@ -33,3 +34,10 @@ before_script: - pecl install -f mongodb-${DRIVER_VERSION} - php --ri mongodb - composer install --dev --no-interaction --prefer-source + - ulimit -c + - ulimit -c unlimited -S + +script: + - phpunit --debug || RESULT=$? + - for i in $(find ./ -maxdepth 1 -name 'core*' -print); do gdb `php -r 'echo PHP_BINARY;'` core* -ex "thread apply all bt" -ex "set pagination 0" -batch; done; + - if [[ ${RESULT} != 0 ]]; then exit $RESULT ; fi;