diff --git a/src/Client.php b/src/Client.php index f0140895d..4c926a2ca 100644 --- a/src/Client.php +++ b/src/Client.php @@ -2,9 +2,10 @@ namespace MongoDB; -use MongoDB\Driver\Manager; -use MongoDB\Database; use MongoDB\Collection; +use MongoDB\Database; +use MongoDB\Driver\Manager; +use MongoDB\Driver\Result; class Client { @@ -29,6 +30,17 @@ public function __construct($uri, $options, $driverOptions) $this->manager = new Manager($uri, $options, $driverOptions); } + /** + * Drop a database. + * + * @param string $databaseName + * @return Result + */ + public function dropDatabase($databaseName) + { + // TODO + } + /** * Select a database * diff --git a/src/Collection.php b/src/Collection.php index 511439e3f..49856b55e 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -65,226 +65,39 @@ public function __construct(Manager $manager, $ns, WriteConcern $wc = null, Read } /** - * Performs a find (query) on the collection - * - * @see http://docs.mongodb.org/manual/core/read-operations-introduction/ - * @see Collection::getFindOptions() for supported $options - * - * @param array $filter The find query to execute - * @param array $options Additional options - * @return Result - */ - public function find(array $filter = array(), array $options = array()) - { - $options = array_merge($this->getFindOptions(), $options); - - $query = $this->_buildQuery($filter, $options); - - $cursor = $this->manager->executeQuery($this->ns, $query, $this->rp); - - return $cursor; - } - - /** - * Performs a find (query) on the collection, returning at most one result - * - * @see http://docs.mongodb.org/manual/core/read-operations-introduction/ - * @see Collection::getFindOptions() for supported $options - * - * @param array $filter The find query to execute - * @param array $options Additional options - * @return array|false The matched document, or false on failure - */ - public function findOne(array $filter = array(), array $options = array()) - { - $options = array_merge($this->getFindOptions(), array("limit" => 1), $options); - - $query = $this->_buildQuery($filter, $options); - - $cursor = $this->manager->executeQuery($this->ns, $query, $this->rp); - - $array = iterator_to_array($cursor); - if ($array) { - return $array[0]; - } - - return false; - } - - /** - * Retrieves all find options with their default values. + * Runs an aggregation framework pipeline + * NOTE: The return value of this method depends on your MongoDB server version + * and possibly options. + * MongoDB 2.6 (and later) will return a Cursor by default + * MongoDB pre 2.6 will return an ArrayIterator * - * @return array of Collection::find() options - */ - public function getFindOptions() - { - return array( - /** - * Get partial results from a mongos if some shards are down (instead of throwing an error). - * - * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query - */ - "allowPartialResults" => false, - - /** - * The number of documents to return per batch. - * - * @see http://docs.mongodb.org/manual/reference/method/cursor.batchSize/ - */ - "batchSize" => 101, - - /** - * Attaches a comment to the query. If $comment also exists - * in the modifiers document, the comment field overwrites $comment. - * - * @see http://docs.mongodb.org/manual/reference/operator/meta/comment/ - */ - "comment" => "", - - /** - * Indicates the type of cursor to use. This value includes both - * the tailable and awaitData options. - * The default is Collection::CURSOR_TYPE_NON_TAILABLE. - * - * @see http://docs.mongodb.org/manual/reference/operator/meta/comment/ - */ - "cursorType" => self::CURSOR_TYPE_NON_TAILABLE, - - /** - * The maximum number of documents to return. - * - * @see http://docs.mongodb.org/manual/reference/method/cursor.limit/ - */ - "limit" => 0, - - /** - * The maximum amount of time to allow the query to run. If $maxTimeMS also exists - * in the modifiers document, the maxTimeMS field overwrites $maxTimeMS. - * - * @see http://docs.mongodb.org/manual/reference/operator/meta/maxTimeMS/ - */ - "maxTimeMS" => 0, - - /** - * Meta-operators modifying the output or behavior of a query. - * - * @see http://docs.mongodb.org/manual/reference/operator/query-modifier/ - */ - "modifiers" => array(), - - /** - * The server normally times out idle cursors after an inactivity period (10 minutes) - * to prevent excess memory use. Set this option to prevent that. - * - * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query - */ - "noCursorTimeout" => false, - - /** - * Internal replication use only - driver should not set - * - * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query - * @internal - */ - "oplogReplay" => false, - - /** - * Limits the fields to return for all matching documents. - * - * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/ - */ - "projection" => array(), - - /** - * The number of documents to skip before returning. - * - * @see http://docs.mongodb.org/manual/reference/method/cursor.skip/ - */ - "skip" => 0, - - /** - * The order in which to return matching documents. If $orderby also exists - * in the modifiers document, the sort field overwrites $orderby. - * - * @see http://docs.mongodb.org/manual/reference/method/cursor.sort/ - */ - "sort" => array(), - ); - } - - /** - * Constructs the Query Wire Protocol field 'flags' based on $options - * provided to other helpers + * @see http://docs.mongodb.org/manual/reference/command/aggregate/ + * @see Collection::getAggregateOptions() for supported $options * - * @param array $options - * @return integer OP_QUERY Wire Protocol flags - * @internal + * @param array $pipeline The pipeline to execute + * @param array $options Additional options + * @return Iterator */ - final protected function _opQueryFlags($options) + public function aggregate(array $pipeline, array $options = array()) { - $flags = 0; - - $flags |= $options["allowPartialResults"] ? self::QUERY_FLAG_PARTIAL : 0; - $flags |= $options["cursorType"] ? $options["cursorType"] : 0; - $flags |= $options["oplogReplay"] ? self::QUERY_FLAG_OPLOG_REPLY: 0; - $flags |= $options["noCursorTimeout"] ? self::QUERY_FLAG_NO_CURSOR_TIMEOUT : 0; - - return $flags; - } + $options = array_merge($this->getAggregateOptions(), $options); + $options = $this->_massageAggregateOptions($options); + $cmd = array( + "aggregate" => $this->collname, + "pipeline" => $pipeline, + ) + $options; - /** - * Helper to build a Query object - * - * @param array $filter the query document - * @param array $options query/protocol options - * @return Query - * @internal - */ - final protected function _buildQuery($filter, $options) - { - if ($options["comment"]) { - $options["modifiers"]['$comment'] = $options["comment"]; - } - if ($options["maxTimeMS"]) { - $options["modifiers"]['$maxTimeMS'] = $options["maxTimeMS"]; - } - if ($options["sort"]) { - $options['$orderby'] = $options["sort"]; + $result = $this->_runCommand($this->dbname, $cmd); + $doc = $result->toArray(); + if (isset($cmd["cursor"]) && $cmd["cursor"]) { + return $result; + } else { + if ($doc["ok"]) { + return new \ArrayIterator($doc["result"]); + } } - $flags = $this->_opQueryFlags($options); - $options["cursorFlags"] = $flags; - - - $query = new Query($filter, $options); - - return $query; - } - - /** - * 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, - ); - } - - /** - * Retrieves all Bulk Write options with their default values. - * - * @return array of available Bulk Write options - */ - public function getBulkOptions() - { - return array( - "ordered" => false, - ); + throw $this->_generateCommandException($doc); } /** @@ -403,311 +216,298 @@ public function bulkWrite(array $bulk, array $options = array()) } /** - * Inserts the provided document + * Counts all documents matching $filter + * If no $filter provided, returns the numbers of documents in the collection * - * @see http://docs.mongodb.org/manual/reference/command/insert/ + * @see http://docs.mongodb.org/manual/reference/command/count/ + * @see Collection::getCountOptions() for supported $options * - * @param array $document The document to insert - * @param array $options Additional options - * @return InsertOneResult + * @param array $filter The find query to execute + * @param array $options Additional options + * @return integer */ - public function insertOne(array $document) + public function count(array $filter = array(), 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); + $cmd = array( + "count" => $this->collname, + "query" => $filter, + ) + $options; - return new InsertOneResult($wr, $id); + $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); + if ($doc["ok"]) { + return $doc["n"]; + } + throw $this->_generateCommandException($doc); } /** - * Inserts the provided documents - * - * @see http://docs.mongodb.org/manual/reference/command/insert/ + * Create a single index in the collection. * - * @param array $documents The documents to insert - * @return InsertManyResult + * @see http://docs.mongodb.org/manual/reference/command/createIndexes/ + * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/ + * @param array|object $keys + * @param array $options + * @return string The name of the created index */ - public function insertMany(array $documents) + public function createIndex($keys, 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; - } - } - - $writeResult = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); - - return new InsertManyResult($writeResult, $insertedIds); + // TODO } /** - * Internal helper for delete one/many documents - * @internal + * Create multiple indexes in the collection. + * + * TODO: decide if $models should be an array of associative arrays, using + * createIndex()'s parameter names as keys, or tuples, using parameters in + * order (e.g. [keys, options]). + * + * @see http://docs.mongodb.org/manual/reference/command/createIndexes/ + * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/ + * @param array $models + * @return string[] The names of the created indexes */ - final protected function _delete($filter, $limit = 1) + public function createIndexes(array $models) { - $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); + // TODO } /** * Deletes a document matching the $filter criteria. - * NOTE: Will delete at most ONE document matching $filter + * NOTE: Will delete ALL documents matching $filter * * @see http://docs.mongodb.org/manual/reference/command/delete/ * * @param array $filter The $filter criteria to delete * @return DeleteResult */ - public function deleteOne(array $filter) + public function deleteMany(array $filter) { - $wr = $this->_delete($filter); + $wr = $this->_delete($filter, 0); return new DeleteResult($wr); } /** * Deletes a document matching the $filter criteria. - * NOTE: Will delete ALL documents matching $filter + * NOTE: Will delete at most ONE document matching $filter * * @see http://docs.mongodb.org/manual/reference/command/delete/ * * @param array $filter The $filter criteria to delete * @return DeleteResult */ - public function deleteMany(array $filter) + public function deleteOne(array $filter) { - $wr = $this->_delete($filter, 0); + $wr = $this->_delete($filter); return new DeleteResult($wr); } /** - * Internal helper for replacing/updating one/many documents - * @internal + * Finds the distinct values for a specified field across the collection + * + * @see http://docs.mongodb.org/manual/reference/command/distinct/ + * @see Collection::getDistinctOptions() for supported $options + * + * @param string $fieldName The fieldname to use + * @param array $filter The find query to execute + * @param array $options Additional options + * @return integer */ - protected function _update($filter, $update, $options) + public function distinct($fieldName, array $filter = array(), array $options = array()) { - $options = array_merge($this->getWriteOptions(), $options); + $options = array_merge($this->getDistinctOptions(), $options); + $cmd = array( + "distinct" => $this->collname, + "key" => $fieldName, + "query" => $filter, + ) + $options; - $bulk = new BulkWrite($options["ordered"]); - $bulk->update($filter, $update, $options); - return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); + $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); + if ($doc["ok"]) { + return $doc["values"]; + } + throw $this->_generateCommandException($doc); } /** - * Replace one document - * - * @see http://docs.mongodb.org/manual/reference/command/update/ - * @see Collection::getWriteOptions() for supported $options + * Drop this collection. * - * @param array $filter The document to be replaced - * @param array $update The document to replace with - * @param array $options Additional options - * @return UpdateResult + * @return Result */ - public function replaceOne(array $filter, array $update, array $options = array()) + public function drop() { - if (key($update)[0] == '$') { - throw new \InvalidArgumentException("First key in \$update must NOT be a \$operator"); - } - $wr = $this->_update($filter, $update, $options); - - return new UpdateResult($wr); + // TODO } /** - * Update one document - * NOTE: Will update at most ONE document matching $filter - * - * @see http://docs.mongodb.org/manual/reference/command/update/ - * @see Collection::getWriteOptions() for supported $options + * Drop a single index in the collection. * - * @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 - * @return UpdateResult + * @see http://docs.mongodb.org/manual/reference/command/dropIndexes/ + * @see http://docs.mongodb.org/manual/reference/method/db.collection.dropIndex/ + * @param string $indexName + * @return Result + * @throws InvalidArgumentException if "*" is specified */ - public function updateOne(array $filter, array $update, array $options = array()) + public function dropIndex($indexName) { - if (key($update)[0] != '$') { - throw new \InvalidArgumentException("First key in \$update must be a \$operator"); - } - $wr = $this->_update($filter, $update, $options); - - return new UpdateResult($wr); + // TODO } /** - * Update one document - * NOTE: Will update ALL documents matching $filter - * - * @see http://docs.mongodb.org/manual/reference/command/update/ - * @see Collection::getWriteOptions() for supported $options + * Drop all indexes in the collection. * - * @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 - * @return UpdateResult + * @see http://docs.mongodb.org/manual/reference/command/dropIndexes/ + * @see http://docs.mongodb.org/manual/reference/method/db.collection.dropIndexes/ + * @return Result */ - public function updateMany(array $filter, $update, array $options = array()) + public function dropIndexes() { - $wr = $this->_update($filter, $update, $options + array("limit" => 0)); - - return new UpdateResult($wr); + // TODO } /** - * Counts all documents matching $filter - * If no $filter provided, returns the numbers of documents in the collection + * Performs a find (query) on the collection * - * @see http://docs.mongodb.org/manual/reference/command/count/ - * @see Collection::getCountOptions() for supported $options + * @see http://docs.mongodb.org/manual/core/read-operations-introduction/ + * @see Collection::getFindOptions() for supported $options * - * @param array $filter The find query to execute - * @param array $options Additional options - * @return integer + * @param array $filter The find query to execute + * @param array $options Additional options + * @return Result */ - public function count(array $filter = array(), array $options = array()) + public function find(array $filter = array(), array $options = array()) { - $cmd = array( - "count" => $this->collname, - "query" => $filter, - ) + $options; + $options = array_merge($this->getFindOptions(), $options); - $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); - if ($doc["ok"]) { - return $doc["n"]; - } - throw $this->_generateCommandException($doc); + $query = $this->_buildQuery($filter, $options); + + $cursor = $this->manager->executeQuery($this->ns, $query, $this->rp); + + return $cursor; } /** - * Retrieves all count options with their default values. + * Performs a find (query) on the collection, returning at most one result * - * @return array of Collection::count() options + * @see http://docs.mongodb.org/manual/core/read-operations-introduction/ + * @see Collection::getFindOptions() for supported $options + * + * @param array $filter The find query to execute + * @param array $options Additional options + * @return array|false The matched document, or false on failure */ - public function getCountOptions() + public function findOne(array $filter = array(), array $options = array()) { - return array( - /** - * The index to use. - * - * @see http://docs.mongodb.org/manual/reference/command/count/ - */ - "hint" => "", // string or document + $options = array_merge($this->getFindOptions(), array("limit" => 1), $options); - /** - * The maximum number of documents to count. - * - * @see http://docs.mongodb.org/manual/reference/command/count/ - */ - "limit" => 0, + $query = $this->_buildQuery($filter, $options); - /** - * The maximum amount of time to allow the query to run. - * - * @see http://docs.mongodb.org/manual/reference/command/count/ - */ - "maxTimeMS" => 0, + $cursor = $this->manager->executeQuery($this->ns, $query, $this->rp); - /** - * The number of documents to skip before returning the documents. - * - * @see http://docs.mongodb.org/manual/reference/command/count/ - */ - "skip" => 0, - ); + $array = iterator_to_array($cursor); + if ($array) { + return $array[0]; + } + + return false; } /** - * Finds the distinct values for a specified field across the collection + * Finds a single document and deletes it, returning the original. * - * @see http://docs.mongodb.org/manual/reference/command/distinct/ - * @see Collection::getDistinctOptions() for supported $options + * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ + * @see Collection::getFindOneAndDelete() for supported $options * - * @param string $fieldName The fieldname to use - * @param array $filter The find query to execute - * @param array $options Additional options - * @return integer + * @param array $filter The $filter criteria to search for + * @param array $options Additional options + * @return array The original document */ - public function distinct($fieldName, array $filter = array(), array $options = array()) + public function findOneAndDelete(array $filter, array $options = array()) { - $options = array_merge($this->getDistinctOptions(), $options); + $options = array_merge($this->getFindOneAndDeleteOptions(), $options); + $options = $this->_massageFindAndModifyOptions($options); $cmd = array( - "distinct" => $this->collname, - "key" => $fieldName, - "query" => $filter, + "findandmodify" => $this->collname, + "query" => $filter, ) + $options; $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); if ($doc["ok"]) { - return $doc["values"]; + return $doc["value"]; } + throw $this->_generateCommandException($doc); } /** - * Retrieves all distinct options with their default values. + * Finds a single document and replaces it, returning either the original or the replaced document + * By default, returns the original document. + * To return the new document set: + * $options = array("returnDocument" => Collection::FIND_ONE_AND_RETURN_AFTER); * - * @return array of Collection::distinct() options + * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ + * @see Collection::getFindOneAndReplace() for supported $options + * + * @param array $filter The $filter criteria to search for + * @param array $replacement The document to replace with + * @param array $options Additional options + * @return array */ - public function getDistinctOptions() + public function findOneAndReplace(array $filter, array $replacement, array $options = array()) { - return array( - /** - * The maximum amount of time to allow the query to run. The default is infinite. - * - * @see http://docs.mongodb.org/manual/reference/command/distinct/ - */ - "maxTimeMS" => 0, - ); + if (key($replacement)[0] == '$') { + throw new \InvalidArgumentException("First key in \$replacement must NOT be a \$operator"); + } + + $options = array_merge($this->getFindOneAndReplaceOptions(), $options); + $options = $this->_massageFindAndModifyOptions($options, $replacement); + + $cmd = array( + "findandmodify" => $this->collname, + "query" => $filter, + ) + $options; + + $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); + if ($doc["ok"]) { + return $doc["value"]; + } + + throw $this->_generateCommandException($doc); } /** - * Runs an aggregation framework pipeline - * NOTE: The return value of this method depends on your MongoDB server version - * and possibly options. - * MongoDB 2.6 (and later) will return a Cursor by default - * MongoDB pre 2.6 will return an ArrayIterator + * Finds a single document and updates it, returning either the original or the updated document + * By default, returns the original document. + * To return the new document set: + * $options = array("returnDocument" => Collection::FIND_ONE_AND_RETURN_AFTER); * - * @see http://docs.mongodb.org/manual/reference/command/aggregate/ - * @see Collection::getAggregateOptions() for supported $options * - * @param array $pipeline The pipeline to execute + * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ + * @see Collection::getFindOneAndUpdate() for supported $options + * + * @param array $filter The $filter criteria to search for + * @param array $update An array of update operators to apply to the document * @param array $options Additional options - * @return Iterator + * @return array */ - public function aggregate(array $pipeline, array $options = array()) + public function findOneAndUpdate(array $filter, array $update, array $options = array()) { - $options = array_merge($this->getAggregateOptions(), $options); - $options = $this->_massageAggregateOptions($options); + if (key($update)[0] != '$') { + throw new \InvalidArgumentException("First key in \$update must be a \$operator"); + } + + $options = array_merge($this->getFindOneAndUpdateOptions(), $options); + $options = $this->_massageFindAndModifyOptions($options, $update); + $cmd = array( - "aggregate" => $this->collname, - "pipeline" => $pipeline, + "findandmodify" => $this->collname, + "query" => $filter, ) + $options; - $result = $this->_runCommand($this->dbname, $cmd); - $doc = $result->toArray(); - if (isset($cmd["cursor"]) && $cmd["cursor"]) { - return $result; - } else { - if ($doc["ok"]) { - return new \ArrayIterator($doc["result"]); - } + $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); + if ($doc["ok"]) { + return $doc["value"]; } throw $this->_generateCommandException($doc); @@ -763,44 +563,90 @@ public function getAggregateOptions() } /** - * Internal helper for massaging aggregate options - * @internal + * Retrieves all Bulk Write options with their default values. + * + * @return array of available Bulk Write options */ - protected function _massageAggregateOptions($options) + public function getBulkOptions() { - if ($options["useCursor"]) { - $options["cursor"] = array("batchSize" => $options["batchSize"]); - } - unset($options["useCursor"], $options["batchSize"]); - - return $options; + return array( + "ordered" => false, + ); } /** - * Finds a single document and deletes it, returning the original. + * Returns the CollectionName this object operates on * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - * @see Collection::getFindOneAndDelete() for supported $options + * @return string + */ + public function getCollectionName() + { + return $this->collname; + } + + /** + * Retrieves all count options with their default values. * - * @param array $filter The $filter criteria to search for - * @param array $options Additional options - * @return array The original document + * @return array of Collection::count() options */ - public function findOneAndDelete(array $filter, array $options = array()) + public function getCountOptions() { - $options = array_merge($this->getFindOneAndDeleteOptions(), $options); - $options = $this->_massageFindAndModifyOptions($options); - $cmd = array( - "findandmodify" => $this->collname, - "query" => $filter, - ) + $options; + return array( + /** + * The index to use. + * + * @see http://docs.mongodb.org/manual/reference/command/count/ + */ + "hint" => "", // string or document - $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); - if ($doc["ok"]) { - return $doc["value"]; - } + /** + * The maximum number of documents to count. + * + * @see http://docs.mongodb.org/manual/reference/command/count/ + */ + "limit" => 0, - throw $this->_generateCommandException($doc); + /** + * The maximum amount of time to allow the query to run. + * + * @see http://docs.mongodb.org/manual/reference/command/count/ + */ + "maxTimeMS" => 0, + + /** + * The number of documents to skip before returning the documents. + * + * @see http://docs.mongodb.org/manual/reference/command/count/ + */ + "skip" => 0, + ); + } + + /** + * Returns the DatabaseName this object operates on + * + * @return string + */ + public function getDatabaseName() + { + return $this->dbname; + } + + /** + * Retrieves all distinct options with their default values. + * + * @return array of Collection::distinct() options + */ + public function getDistinctOptions() + { + return array( + /** + * The maximum amount of time to allow the query to run. The default is infinite. + * + * @see http://docs.mongodb.org/manual/reference/command/distinct/ + */ + "maxTimeMS" => 0, + ); } /** @@ -835,42 +681,6 @@ public function getFindOneAndDeleteOptions() ); } - /** - * Finds a single document and replaces it, returning either the original or the replaced document - * By default, returns the original document. - * To return the new document set: - * $options = array("returnDocument" => Collection::FIND_ONE_AND_RETURN_AFTER); - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - * @see Collection::getFindOneAndReplace() for supported $options - * - * @param array $filter The $filter criteria to search for - * @param array $replacement The document to replace with - * @param array $options Additional options - * @return array - */ - public function findOneAndReplace(array $filter, array $replacement, array $options = array()) - { - if (key($replacement)[0] == '$') { - throw new \InvalidArgumentException("First key in \$replacement must NOT be a \$operator"); - } - - $options = array_merge($this->getFindOneAndReplaceOptions(), $options); - $options = $this->_massageFindAndModifyOptions($options, $replacement); - - $cmd = array( - "findandmodify" => $this->collname, - "query" => $filter, - ) + $options; - - $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); - if ($doc["ok"]) { - return $doc["value"]; - } - - throw $this->_generateCommandException($doc); - } - /** * Retrieves all findOneAndReplace options with their default values. * @@ -919,43 +729,6 @@ public function getFindOneAndReplaceOptions() ); } - /** - * Finds a single document and updates it, returning either the original or the updated document - * By default, returns the original document. - * To return the new document set: - * $options = array("returnDocument" => Collection::FIND_ONE_AND_RETURN_AFTER); - * - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - * @see Collection::getFindOneAndUpdate() for supported $options - * - * @param array $filter The $filter criteria to search for - * @param array $update An array of update operators to apply to the document - * @param array $options Additional options - * @return array - */ - public function findOneAndUpdate(array $filter, array $update, array $options = array()) - { - if (key($update)[0] != '$') { - throw new \InvalidArgumentException("First key in \$update must be a \$operator"); - } - - $options = array_merge($this->getFindOneAndUpdateOptions(), $options); - $options = $this->_massageFindAndModifyOptions($options, $update); - - $cmd = array( - "findandmodify" => $this->collname, - "query" => $filter, - ) + $options; - - $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); - if ($doc["ok"]) { - return $doc["value"]; - } - - throw $this->_generateCommandException($doc); - } - /** * Retrieves all findOneAndUpdate options with their default values. * @@ -1004,29 +777,287 @@ public function getFindOneAndUpdateOptions() } /** - * Internal helper for massaging findandmodify options - * @internal + * Retrieves all find options with their default values. + * + * @return array of Collection::find() options */ - final protected function _massageFindAndModifyOptions($options, $update = array()) + public function getFindOptions() { - $ret = array( - "sort" => $options["sort"], - "new" => isset($options["returnDocument"]) ? $options["returnDocument"] == self::FIND_ONE_AND_RETURN_AFTER : false, - "fields" => $options["projection"], - "upsert" => isset($options["upsert"]) ? $options["upsert"] : false, - ); - if ($update) { - $ret["update"] = $update; - } else { - $ret["remove"] = true; - } - - return $ret; - } + return array( + /** + * Get partial results from a mongos if some shards are down (instead of throwing an error). + * + * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query + */ + "allowPartialResults" => false, - /** - * Internal helper for throwing an exception with error message - * @internal + /** + * The number of documents to return per batch. + * + * @see http://docs.mongodb.org/manual/reference/method/cursor.batchSize/ + */ + "batchSize" => 101, + + /** + * Attaches a comment to the query. If $comment also exists + * in the modifiers document, the comment field overwrites $comment. + * + * @see http://docs.mongodb.org/manual/reference/operator/meta/comment/ + */ + "comment" => "", + + /** + * Indicates the type of cursor to use. This value includes both + * the tailable and awaitData options. + * The default is Collection::CURSOR_TYPE_NON_TAILABLE. + * + * @see http://docs.mongodb.org/manual/reference/operator/meta/comment/ + */ + "cursorType" => self::CURSOR_TYPE_NON_TAILABLE, + + /** + * The maximum number of documents to return. + * + * @see http://docs.mongodb.org/manual/reference/method/cursor.limit/ + */ + "limit" => 0, + + /** + * The maximum amount of time to allow the query to run. If $maxTimeMS also exists + * in the modifiers document, the maxTimeMS field overwrites $maxTimeMS. + * + * @see http://docs.mongodb.org/manual/reference/operator/meta/maxTimeMS/ + */ + "maxTimeMS" => 0, + + /** + * Meta-operators modifying the output or behavior of a query. + * + * @see http://docs.mongodb.org/manual/reference/operator/query-modifier/ + */ + "modifiers" => array(), + + /** + * The server normally times out idle cursors after an inactivity period (10 minutes) + * to prevent excess memory use. Set this option to prevent that. + * + * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query + */ + "noCursorTimeout" => false, + + /** + * Internal replication use only - driver should not set + * + * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query + * @internal + */ + "oplogReplay" => false, + + /** + * Limits the fields to return for all matching documents. + * + * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/ + */ + "projection" => array(), + + /** + * The number of documents to skip before returning. + * + * @see http://docs.mongodb.org/manual/reference/method/cursor.skip/ + */ + "skip" => 0, + + /** + * The order in which to return matching documents. If $orderby also exists + * in the modifiers document, the sort field overwrites $orderby. + * + * @see http://docs.mongodb.org/manual/reference/method/cursor.sort/ + */ + "sort" => array(), + ); + } + + /** + * 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 the provided documents + * + * @see http://docs.mongodb.org/manual/reference/command/insert/ + * + * @param array $documents The documents to insert + * @return InsertManyResult + */ + public function insertMany(array $documents) + { + $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; + } + } + + $writeResult = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); + + return new InsertManyResult($writeResult, $insertedIds); + } + + /** + * Inserts the provided document + * + * @see http://docs.mongodb.org/manual/reference/command/insert/ + * + * @param array $document The document to insert + * @param array $options Additional options + * @return InsertOneResult + */ + public function insertOne(array $document) + { + $options = array_merge($this->getWriteOptions()); + + $bulk = new BulkWrite($options["ordered"]); + $id = $bulk->insert($document); + $wr = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); + + return new InsertOneResult($wr, $id); + } + + /** + * Returns information for all indexes in the collection. + * + * @see http://docs.mongodb.org/manual/reference/command/listIndexes/ + * @see http://docs.mongodb.org/manual/reference/method/db.collection.getIndexes/ + * @return Result + */ + public function listIndexes() + { + // TODO + } + + /** + * Replace one document + * + * @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 + * @return UpdateResult + */ + public function replaceOne(array $filter, array $update, array $options = array()) + { + if (key($update)[0] == '$') { + throw new \InvalidArgumentException("First key in \$update must NOT be a \$operator"); + } + $wr = $this->_update($filter, $update, $options); + + return new UpdateResult($wr); + } + + /** + * Update one document + * NOTE: Will update ALL documents matching $filter + * + * @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 + * @return UpdateResult + */ + public function updateMany(array $filter, $update, array $options = array()) + { + $wr = $this->_update($filter, $update, $options + array("limit" => 0)); + + return new UpdateResult($wr); + } + + /** + * Update one document + * NOTE: Will update at most ONE document matching $filter + * + * @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 + * @return UpdateResult + */ + public function updateOne(array $filter, array $update, array $options = array()) + { + if (key($update)[0] != '$') { + throw new \InvalidArgumentException("First key in \$update must be a \$operator"); + } + $wr = $this->_update($filter, $update, $options); + + return new UpdateResult($wr); + } + + /** + * Helper to build a Query object + * + * @param array $filter the query document + * @param array $options query/protocol options + * @return Query + * @internal + */ + final protected function _buildQuery($filter, $options) + { + if ($options["comment"]) { + $options["modifiers"]['$comment'] = $options["comment"]; + } + if ($options["maxTimeMS"]) { + $options["modifiers"]['$maxTimeMS'] = $options["maxTimeMS"]; + } + if ($options["sort"]) { + $options['$orderby'] = $options["sort"]; + } + + $flags = $this->_opQueryFlags($options); + $options["cursorFlags"] = $flags; + + + $query = new Query($filter, $options); + + return $query; + } + + /** + * 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 throwing an exception with error message + * @internal */ final protected function _generateCommandException($doc) { @@ -1038,33 +1069,60 @@ final protected function _generateCommandException($doc) } /** - * Internal helper for running a command + * Internal helper for massaging aggregate options * @internal */ - final protected function _runCommand($dbname, array $cmd, ReadPreference $rp = null) + protected function _massageAggregateOptions($options) { - //var_dump(\BSON\toJSON(\BSON\fromArray($cmd))); - $command = new Command($cmd); - return $this->manager->executeCommand($dbname, $command, $rp); + if ($options["useCursor"]) { + $options["cursor"] = array("batchSize" => $options["batchSize"]); + } + unset($options["useCursor"], $options["batchSize"]); + + return $options; } /** - * Returns the CollectionName this object operates on + * Constructs the Query Wire Protocol field 'flags' based on $options + * provided to other helpers * - * @return string + * @param array $options + * @return integer OP_QUERY Wire Protocol flags + * @internal */ - public function getCollectionName() + final protected function _opQueryFlags($options) { - return $this->collname; + $flags = 0; + + $flags |= $options["allowPartialResults"] ? self::QUERY_FLAG_PARTIAL : 0; + $flags |= $options["cursorType"] ? $options["cursorType"] : 0; + $flags |= $options["oplogReplay"] ? self::QUERY_FLAG_OPLOG_REPLY: 0; + $flags |= $options["noCursorTimeout"] ? self::QUERY_FLAG_NO_CURSOR_TIMEOUT : 0; + + return $flags; } /** - * Returns the DatabaseName this object operates on - * - * @return string + * Internal helper for running a command + * @internal */ - public function getDatabaseName() + final protected function _runCommand($dbname, array $cmd, ReadPreference $rp = null) { - return $this->dbname; + //var_dump(\BSON\toJSON(\BSON\fromArray($cmd))); + $command = new Command($cmd); + return $this->manager->executeCommand($dbname, $command, $rp); + } + + /** + * Internal helper for replacing/updating one/many documents + * @internal + */ + protected function _update($filter, $update, $options) + { + $options = array_merge($this->getWriteOptions(), $options); + + $bulk = new BulkWrite($options["ordered"]); + $bulk->update($filter, $update, $options); + return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); } } diff --git a/src/Database.php b/src/Database.php index d56de3bad..cb772b3a6 100644 --- a/src/Database.php +++ b/src/Database.php @@ -2,8 +2,9 @@ namespace MongoDB; -use MongoDB\Driver\Manager; use MongoDB\Collection; +use MongoDB\Driver\Manager; +use MongoDB\Driver\Result; class Database { @@ -32,6 +33,53 @@ public function __construct(Manager $manager, $databaseName, WriteConcern $wc = $this->rp = $rp; } + /** + * Create a new collection explicitly. + * + * @see http://docs.mongodb.org/manual/reference/command/create/ + * @see http://docs.mongodb.org/manual/reference/method/db.createCollection/ + * @param string $collectionName + * @param array $options + * @return Result + */ + public function createCollection($collectionName, array $options = array()) + { + // TODO + } + + /** + * Drop this database. + * + * @return Result + */ + public function drop() + { + // TODO + } + + /** + * Drop a collection within this database. + * + * @param string $collectionName + * @return Result + */ + public function dropCollection($collectionName) + { + // TODO + } + + /** + * Returns information for all collections in this database. + * + * @see http://docs.mongodb.org/manual/reference/command/listCollections/ + * @param array $options + * @return Result + */ + public function listCollections(array $options = array()) + { + // TODO + } + /** * Select a specific collection in this database * diff --git a/tests/CollectionTest.php b/tests/CollectionTest.php index 059caa547..e54ee92bb 100644 --- a/tests/CollectionTest.php +++ b/tests/CollectionTest.php @@ -6,7 +6,7 @@ class CollectionTest extends PHPUnit_Framework_TestCase { function setUp() { - require __DIR__ . "/" . "utils.inc"; + require_once __DIR__ . "/" . "utils.inc"; $this->faker = Faker\Factory::create(); $this->faker->seed(1234); @@ -44,5 +44,28 @@ function testInsertAndRetrieve() { } $this->assertEquals(0, $n); } + + public function testMethodOrder() + { + $class = new ReflectionClass('MongoDB\Collection'); + + $filters = array( + 'public' => ReflectionMethod::IS_PUBLIC, + 'protected' => ReflectionMethod::IS_PROTECTED, + 'private' => ReflectionMethod::IS_PRIVATE, + ); + + foreach ($filters as $visibility => $filter) { + $methods = array_map( + function(ReflectionMethod $method) { return $method->getName(); }, + $class->getMethods($filter) + ); + + $sortedMethods = $methods; + sort($sortedMethods); + + $this->assertEquals($methods, $sortedMethods, sprintf('%s methods are declared alphabetically', ucfirst($visibility))); + } + } }