Commit 495e46d4 authored by Jeremy Mikola's avatar Jeremy Mikola

Declare Collection methods alphabetically by visibility

We can move this to a separate, pedantic test class later.
parent b6a120e0
...@@ -64,6 +64,182 @@ class Collection ...@@ -64,6 +64,182 @@ class Collection
list($this->dbname, $this->collname) = explode(".", $ns, 2); list($this->dbname, $this->collname) = explode(".", $ns, 2);
} }
/**
* 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
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
* @see Collection::getAggregateOptions() for supported $options
*
* @param array $pipeline The pipeline to execute
* @param array $options Additional options
* @return Iterator
*/
public function aggregate(array $pipeline, array $options = array())
{
$options = array_merge($this->getAggregateOptions(), $options);
$options = $this->_massageAggregateOptions($options);
$cmd = array(
"aggregate" => $this->collname,
"pipeline" => $pipeline,
) + $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"]);
}
}
throw $this->_generateCommandException($doc);
}
/**
* 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 $bulk Array of operations
* @param array $options Additional options
* @return WriteResult
*/
public function bulkWrite(array $bulk, array $options = array())
{
$options = array_merge($this->getBulkOptions(), $options);
$bulk = new BulkWrite($options["ordered"]);
foreach ($bulk 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":
$bulk->insert($args[0]);
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("limit" => 0));
$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("limit" => 1));
if (key($args[1])[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("limit" => 1));
if (key($args[1])[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));
}
}
}
return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
}
/**
* 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/count/
* @see Collection::getCountOptions() for supported $options
*
* @param array $filter The find query to execute
* @param array $options Additional options
* @return integer
*/
public function count(array $filter = array(), array $options = array())
{
$cmd = array(
"count" => $this->collname,
"query" => $filter,
) + $options;
$doc = $this->_runCommand($this->dbname, $cmd)->toArray();
if ($doc["ok"]) {
return $doc["n"];
}
throw $this->_generateCommandException($doc);
}
/** /**
* Create a single index in the collection. * Create a single index in the collection.
* *
...@@ -95,6 +271,65 @@ class Collection ...@@ -95,6 +271,65 @@ class Collection
// TODO // TODO
} }
/**
* Deletes a document matching the $filter criteria.
* 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 deleteMany(array $filter)
{
$wr = $this->_delete($filter, 0);
return new DeleteResult($wr);
}
/**
* Deletes a document matching the $filter criteria.
* 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 deleteOne(array $filter)
{
$wr = $this->_delete($filter);
return new DeleteResult($wr);
}
/**
* 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
*/
public function distinct($fieldName, array $filter = array(), array $options = array())
{
$options = array_merge($this->getDistinctOptions(), $options);
$cmd = array(
"distinct" => $this->collname,
"key" => $fieldName,
"query" => $filter,
) + $options;
$doc = $this->_runCommand($this->dbname, $cmd)->toArray();
if ($doc["ok"]) {
return $doc["values"];
}
throw $this->_generateCommandException($doc);
}
/** /**
* Drop this collection. * Drop this collection.
* *
...@@ -179,487 +414,174 @@ class Collection ...@@ -179,487 +414,174 @@ class Collection
} }
/** /**
* Retrieves all find options with their default values. * Finds a single document and deletes it, returning the original.
* *
* @return array of Collection::find() options * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/ * @see Collection::getFindOneAndDelete() for supported $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 * @param array $filter The $filter criteria to search for
* @param array $options Additional options
* @return array The original document
*/ */
"allowPartialResults" => false, public function findOneAndDelete(array $filter, array $options = array())
/**
* 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
*
* @param array $options
* @return integer OP_QUERY Wire Protocol flags
* @internal
*/
final protected function _opQueryFlags($options)
{
$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;
}
/**
* 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 = array_merge($this->getFindOneAndDeleteOptions(), $options);
$options["modifiers"]['$comment'] = $options["comment"]; $options = $this->_massageFindAndModifyOptions($options);
} $cmd = array(
if ($options["maxTimeMS"]) { "findandmodify" => $this->collname,
$options["modifiers"]['$maxTimeMS'] = $options["maxTimeMS"]; "query" => $filter,
} ) + $options;
if ($options["sort"]) {
$options['$orderby'] = $options["sort"];
}
$flags = $this->_opQueryFlags($options);
$options["cursorFlags"] = $flags;
$query = new Query($filter, $options);
return $query;
}
/** $doc = $this->_runCommand($this->dbname, $cmd)->toArray();
* Retrieves all Write options with their default values. if ($doc["ok"]) {
* return $doc["value"];
* @return array of available Write options
*/
public function getWriteOptions()
{
return array(
"ordered" => false,
"upsert" => false,
"limit" => 1,
);
} }
/** throw $this->_generateCommandException($doc);
* Retrieves all Bulk Write options with their default values.
*
* @return array of available Bulk Write options
*/
public function getBulkOptions()
{
return array(
"ordered" => false,
);
} }
/** /**
* Adds a full set of write operations into a bulk and executes it * Finds a single document and replaces it, returning either the original or the replaced document
* * By default, returns the original document.
* The syntax of the $bulk array is: * To return the new document set:
* $bulk = [ * $options = array("returnDocument" => Collection::FIND_ONE_AND_RETURN_AFTER);
* [
* '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 * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
* @see Collection::getFindOneAndReplace() for supported $options
* *
* @param array $bulk Array of operations * @param array $filter The $filter criteria to search for
* @param array $replacement The document to replace with
* @param array $options Additional options * @param array $options Additional options
* @return WriteResult * @return array
*/ */
public function bulkWrite(array $bulk, array $options = array()) public function findOneAndReplace(array $filter, array $replacement, array $options = array())
{ {
$options = array_merge($this->getBulkOptions(), $options); if (key($replacement)[0] == '$') {
throw new \InvalidArgumentException("First key in \$replacement must NOT be a \$operator");
$bulk = new BulkWrite($options["ordered"]);
foreach ($bulk 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":
$bulk->insert($args[0]);
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("limit" => 0));
$bulk->update($args[0], $args[1], $options); $options = array_merge($this->getFindOneAndReplaceOptions(), $options);
break; $options = $this->_massageFindAndModifyOptions($options, $replacement);
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("limit" => 1));
if (key($args[1])[0] != '$') {
throw new \InvalidArgumentException("First key in \$update must be a \$operator");
}
$bulk->update($args[0], $args[1], $options); $cmd = array(
break; "findandmodify" => $this->collname,
"query" => $filter,
) + $options;
case "replaceOne": $doc = $this->_runCommand($this->dbname, $cmd)->toArray();
if (!isset($args[1])) { if ($doc["ok"]) {
throw new \InvalidArgumentException(sprintf("Missing argument#2 for '%s' (operation#%d)", $opname, $n)); return $doc["value"];
}
$options = array_merge($this->getWriteOptions(), isset($args[2]) ? $args[2] : array(), array("limit" => 1));
if (key($args[1])[0] == '$') {
throw new \InvalidArgumentException("First key in \$update must NOT be a \$operator");
} }
$bulk->update($args[0], $args[1], $options); throw $this->_generateCommandException($doc);
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));
}
}
}
return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
} }
/** /**
* Inserts the provided document * 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/insert/
* *
* @param array $document The document to insert * @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 * @param array $options Additional options
* @return InsertOneResult * @return array
*/ */
public function insertOne(array $document) public function findOneAndUpdate(array $filter, array $update, array $options = array())
{ {
$options = array_merge($this->getWriteOptions()); if (key($update)[0] != '$') {
throw new \InvalidArgumentException("First key in \$update must be a \$operator");
$bulk = new BulkWrite($options["ordered"]);
$id = $bulk->insert($document);
$wr = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
return new InsertOneResult($wr, $id);
} }
/** $options = array_merge($this->getFindOneAndUpdateOptions(), $options);
* Inserts the provided documents $options = $this->_massageFindAndModifyOptions($options, $update);
*
* @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); $cmd = array(
"findandmodify" => $this->collname,
"query" => $filter,
) + $options;
return new InsertManyResult($writeResult, $insertedIds); $doc = $this->_runCommand($this->dbname, $cmd)->toArray();
if ($doc["ok"]) {
return $doc["value"];
} }
/** throw $this->_generateCommandException($doc);
* 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);
} }
/** /**
* Deletes a document matching the $filter criteria. * Retrieves all aggregate options with their default values.
* 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 array of Collection::aggregate() options
* @return DeleteResult
*/ */
public function deleteOne(array $filter) public function getAggregateOptions()
{ {
$wr = $this->_delete($filter); $opts = array(
return new DeleteResult($wr);
}
/** /**
* Deletes a document matching the $filter criteria. * Enables writing to temporary files. When set to true, aggregation stages
* NOTE: Will delete ALL documents matching $filter * can write data to the _tmp subdirectory in the dbPath directory. The
* * default is false.
* @see http://docs.mongodb.org/manual/reference/command/delete/
* *
* @param array $filter The $filter criteria to delete * @see http://docs.mongodb.org/manual/reference/command/aggregate/
* @return DeleteResult
*/ */
public function deleteMany(array $filter) "allowDiskUse" => false,
{
$wr = $this->_delete($filter, 0);
return new DeleteResult($wr);
}
/** /**
* Internal helper for replacing/updating one/many documents * The number of documents to return per batch.
* @internal *
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/ */
protected function _update($filter, $update, $options) "batchSize" => 0,
{
$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);
}
/** /**
* Replace one document * The maximum amount of time to allow the query to run.
*
* @see http://docs.mongodb.org/manual/reference/command/update/
* @see Collection::getWriteOptions() for supported $options
* *
* @param array $filter The document to be replaced * @see http://docs.mongodb.org/manual/reference/command/aggregate/
* @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()) "maxTimeMS" => 0,
{
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 * Indicates if the results should be provided as a cursor.
* NOTE: Will update at most ONE document matching $filter
* *
* @see http://docs.mongodb.org/manual/reference/command/update/ * The default for this value depends on the version of the server.
* @see Collection::getWriteOptions() for supported $options * - Servers >= 2.6 will use a default of true.
* - Servers < 2.6 will use a default of false.
* *
* @param array $filter The document to be replaced * As with any other property, this value can be changed.
* @param array $update An array of update operators to apply to the document *
* @param array $options Additional options * @see http://docs.mongodb.org/manual/reference/command/aggregate/
* @return UpdateResult
*/ */
public function updateOne(array $filter, array $update, array $options = array()) "useCursor" => true,
{ );
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); /* FIXME: Add a version check for useCursor */
return $opts;
} }
/** /**
* Update one document * Retrieves all Bulk Write options with their default values.
* 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 * @return array of available Bulk Write options
* @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()) public function getBulkOptions()
{ {
$wr = $this->_update($filter, $update, $options + array("limit" => 0)); return array(
"ordered" => false,
return new UpdateResult($wr); );
} }
/** /**
* Counts all documents matching $filter * Returns the CollectionName this object operates on
* If no $filter provided, returns the numbers of documents in the collection
*
* @see http://docs.mongodb.org/manual/reference/command/count/
* @see Collection::getCountOptions() for supported $options
* *
* @param array $filter The find query to execute * @return string
* @param array $options Additional options
* @return integer
*/ */
public function count(array $filter = array(), array $options = array()) public function getCollectionName()
{ {
$cmd = array( return $this->collname;
"count" => $this->collname,
"query" => $filter,
) + $options;
$doc = $this->_runCommand($this->dbname, $cmd)->toArray();
if ($doc["ok"]) {
return $doc["n"];
}
throw $this->_generateCommandException($doc);
} }
/** /**
...@@ -701,30 +623,13 @@ class Collection ...@@ -701,30 +623,13 @@ class Collection
} }
/** /**
* Finds the distinct values for a specified field across the collection * Returns the DatabaseName this object operates on
*
* @see http://docs.mongodb.org/manual/reference/command/distinct/
* @see Collection::getDistinctOptions() for supported $options
* *
* @param string $fieldName The fieldname to use * @return string
* @param array $filter The find query to execute
* @param array $options Additional options
* @return integer
*/ */
public function distinct($fieldName, array $filter = array(), array $options = array()) public function getDatabaseName()
{ {
$options = array_merge($this->getDistinctOptions(), $options); return $this->dbname;
$cmd = array(
"distinct" => $this->collname,
"key" => $fieldName,
"query" => $filter,
) + $options;
$doc = $this->_runCommand($this->dbname, $cmd)->toArray();
if ($doc["ok"]) {
return $doc["values"];
}
throw $this->_generateCommandException($doc);
} }
/** /**
...@@ -745,137 +650,91 @@ class Collection ...@@ -745,137 +650,91 @@ class Collection
} }
/** /**
* Runs an aggregation framework pipeline * Retrieves all findOneDelete options with their default values.
* 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
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
* @see Collection::getAggregateOptions() for supported $options
* *
* @param array $pipeline The pipeline to execute * @return array of Collection::findOneAndDelete() options
* @param array $options Additional options
* @return Iterator
*/ */
public function aggregate(array $pipeline, array $options = array()) public function getFindOneAndDeleteOptions()
{ {
$options = array_merge($this->getAggregateOptions(), $options); return array(
$options = $this->_massageAggregateOptions($options);
$cmd = array(
"aggregate" => $this->collname,
"pipeline" => $pipeline,
) + $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"]);
}
}
throw $this->_generateCommandException($doc); /**
} * The maximum amount of time to allow the query to run.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"maxTimeMS" => 0,
/** /**
* Retrieves all aggregate options with their default values. * Limits the fields to return for all matching documents.
* *
* @return array of Collection::aggregate() options * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results
*/ */
public function getAggregateOptions() "projection" => array(),
{
$opts = array(
/** /**
* Enables writing to temporary files. When set to true, aggregation stages * Determines which document the operation modifies if the query selects multiple documents.
* can write data to the _tmp subdirectory in the dbPath directory. The
* default is false.
* *
* @see http://docs.mongodb.org/manual/reference/command/aggregate/ * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/ */
"allowDiskUse" => false, "sort" => array(),
);
}
/** /**
* The number of documents to return per batch. * Retrieves all findOneAndReplace options with their default values.
* *
* @see http://docs.mongodb.org/manual/reference/command/aggregate/ * @return array of Collection::findOneAndReplace() options
*/ */
"batchSize" => 0, public function getFindOneAndReplaceOptions()
{
return array(
/** /**
* The maximum amount of time to allow the query to run. * The maximum amount of time to allow the query to run.
* *
* @see http://docs.mongodb.org/manual/reference/command/aggregate/ * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/ */
"maxTimeMS" => 0, "maxTimeMS" => 0,
/** /**
* Indicates if the results should be provided as a cursor. * Limits the fields to return for all matching documents.
*
* The default for this value depends on the version of the server.
* - Servers >= 2.6 will use a default of true.
* - Servers < 2.6 will use a default of false.
*
* As with any other property, this value can be changed.
* *
* @see http://docs.mongodb.org/manual/reference/command/aggregate/ * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results
*/ */
"useCursor" => true, "projection" => array(),
);
/* FIXME: Add a version check for useCursor */
return $opts;
}
/** /**
* Internal helper for massaging aggregate options * When ReturnDocument.After, returns the replaced or inserted document rather than the original.
* @internal * Defaults to ReturnDocument.Before.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/ */
protected function _massageAggregateOptions($options) "returnDocument" => self::FIND_ONE_AND_RETURN_BEFORE,
{
if ($options["useCursor"]) {
$options["cursor"] = array("batchSize" => $options["batchSize"]);
}
unset($options["useCursor"], $options["batchSize"]);
return $options;
}
/** /**
* Finds a single document and deletes it, returning the original. * Determines which document the operation modifies if the query selects multiple documents.
* *
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
* @see Collection::getFindOneAndDelete() for supported $options
*
* @param array $filter The $filter criteria to search for
* @param array $options Additional options
* @return array The original document
*/ */
public function findOneAndDelete(array $filter, array $options = array()) "sort" => array(),
{
$options = array_merge($this->getFindOneAndDeleteOptions(), $options);
$options = $this->_massageFindAndModifyOptions($options);
$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); /**
* When true, findAndModify creates a new document if no document matches the query. The
* default is false.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"upsert" => false,
);
} }
/** /**
* Retrieves all findOneDelete options with their default values. * Retrieves all findOneAndUpdate options with their default values.
* *
* @return array of Collection::findOneAndDelete() options * @return array of Collection::findOneAndUpdate() options
*/ */
public function getFindOneAndDeleteOptions() public function getFindOneAndUpdateOptions()
{ {
return array( return array(
...@@ -893,202 +752,295 @@ class Collection ...@@ -893,202 +752,295 @@ class Collection
*/ */
"projection" => array(), "projection" => array(),
/**
* When ReturnDocument.After, returns the updated or inserted document rather than the original.
* Defaults to ReturnDocument.Before.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"returnDocument" => self::FIND_ONE_AND_RETURN_BEFORE,
/** /**
* Determines which document the operation modifies if the query selects multiple documents. * Determines which document the operation modifies if the query selects multiple documents.
* *
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/ */
"sort" => array(), "sort" => array(),
);
}
/** /**
* Finds a single document and replaces it, returning either the original or the replaced document * When true, creates a new document if no document matches the query. The default is false.
* 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 http://docs.mongodb.org/manual/reference/command/findAndModify/
* @see Collection::getFindOneAndReplace() for supported $options */
"upsert" => false,
);
}
/**
* Retrieves all find options with their default values.
* *
* @param array $filter The $filter criteria to search for * @return array of Collection::find() options
* @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()) public function getFindOptions()
{ {
if (key($replacement)[0] == '$') { return array(
throw new \InvalidArgumentException("First key in \$replacement must NOT be a \$operator"); /**
} * 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,
$options = array_merge($this->getFindOneAndReplaceOptions(), $options); /**
$options = $this->_massageFindAndModifyOptions($options, $replacement); * The number of documents to return per batch.
*
* @see http://docs.mongodb.org/manual/reference/method/cursor.batchSize/
*/
"batchSize" => 101,
$cmd = array( /**
"findandmodify" => $this->collname, * Attaches a comment to the query. If $comment also exists
"query" => $filter, * in the modifiers document, the comment field overwrites $comment.
) + $options; *
* @see http://docs.mongodb.org/manual/reference/operator/meta/comment/
*/
"comment" => "",
$doc = $this->_runCommand($this->dbname, $cmd)->toArray(); /**
if ($doc["ok"]) { * Indicates the type of cursor to use. This value includes both
return $doc["value"]; * 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,
throw $this->_generateCommandException($doc); /**
} * The maximum number of documents to return.
*
* @see http://docs.mongodb.org/manual/reference/method/cursor.limit/
*/
"limit" => 0,
/** /**
* Retrieves all findOneAndReplace options with their default values. * 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.
* *
* @return array of Collection::findOneAndReplace() options * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
*/ */
public function getFindOneAndReplaceOptions() "noCursorTimeout" => false,
{
return array(
/** /**
* The maximum amount of time to allow the query to run. * Internal replication use only - driver should not set
* *
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
* @internal
*/ */
"maxTimeMS" => 0, "oplogReplay" => false,
/** /**
* Limits the fields to return for all matching documents. * Limits the fields to return for all matching documents.
* *
* @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/
*/ */
"projection" => array(), "projection" => array(),
/** /**
* When ReturnDocument.After, returns the replaced or inserted document rather than the original. * The number of documents to skip before returning.
* Defaults to ReturnDocument.Before.
* *
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @see http://docs.mongodb.org/manual/reference/method/cursor.skip/
*/ */
"returnDocument" => self::FIND_ONE_AND_RETURN_BEFORE, "skip" => 0,
/** /**
* Determines which document the operation modifies if the query selects multiple documents. * 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/command/findAndModify/ * @see http://docs.mongodb.org/manual/reference/method/cursor.sort/
*/ */
"sort" => array(), "sort" => array(),
);
}
/** /**
* When true, findAndModify creates a new document if no document matches the query. The * Retrieves all Write options with their default values.
* default is false.
* *
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @return array of available Write options
*/ */
public function getWriteOptions()
{
return array(
"ordered" => false,
"upsert" => false, "upsert" => false,
"limit" => 1,
); );
} }
/** /**
* Finds a single document and updates it, returning either the original or the updated document * Inserts the provided documents
* 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 http://docs.mongodb.org/manual/reference/command/insert/
* @see Collection::getFindOneAndUpdate() for supported $options
* *
* @param array $filter The $filter criteria to search for * @param array $documents The documents to insert
* @param array $update An array of update operators to apply to the document * @return InsertManyResult
* @param array $options Additional options
* @return array
*/ */
public function findOneAndUpdate(array $filter, array $update, array $options = array()) public function insertMany(array $documents)
{ {
if (key($update)[0] != '$') { $options = array_merge($this->getWriteOptions());
throw new \InvalidArgumentException("First key in \$update must be a \$operator");
}
$options = array_merge($this->getFindOneAndUpdateOptions(), $options); $bulk = new BulkWrite($options["ordered"]);
$options = $this->_massageFindAndModifyOptions($options, $update); $insertedIds = array();
$cmd = array( foreach ($documents as $i => $document) {
"findandmodify" => $this->collname, $insertedId = $bulk->insert($document);
"query" => $filter,
) + $options;
$doc = $this->_runCommand($this->dbname, $cmd)->toArray(); if ($insertedId !== null) {
if ($doc["ok"]) { $insertedIds[$i] = $insertedId;
return $doc["value"]; }
} }
throw $this->_generateCommandException($doc); $writeResult = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
return new InsertManyResult($writeResult, $insertedIds);
} }
/** /**
* Retrieves all findOneAndUpdate options with their default values. * Inserts the provided document
* *
* @return array of Collection::findOneAndUpdate() options * @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 getFindOneAndUpdateOptions() public function insertOne(array $document)
{ {
return array( $options = array_merge($this->getWriteOptions());
/** $bulk = new BulkWrite($options["ordered"]);
* The maximum amount of time to allow the query to run. $id = $bulk->insert($document);
* $wr = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/ return new InsertOneResult($wr, $id);
"maxTimeMS" => 0, }
/** /**
* Limits the fields to return for all matching documents. * Replace one document
* *
* @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results * @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
*/ */
"projection" => array(), 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);
}
/** /**
* When ReturnDocument.After, returns the updated or inserted document rather than the original. * Update one document
* Defaults to ReturnDocument.Before. * NOTE: Will update ALL documents matching $filter
* *
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @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
*/ */
"returnDocument" => self::FIND_ONE_AND_RETURN_BEFORE, public function updateMany(array $filter, $update, array $options = array())
{
$wr = $this->_update($filter, $update, $options + array("limit" => 0));
return new UpdateResult($wr);
}
/** /**
* Determines which document the operation modifies if the query selects multiple documents. * Update one document
* NOTE: Will update at most ONE document matching $filter
* *
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @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
*/ */
"sort" => array(), 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);
}
/** /**
* When true, creates a new document if no document matches the query. The default is false. * Helper to build a Query object
* *
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @param array $filter the query document
* @param array $options query/protocol options
* @return Query
* @internal
*/ */
"upsert" => false, 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 massaging findandmodify options * Internal helper for delete one/many documents
* @internal * @internal
*/ */
final protected function _massageFindAndModifyOptions($options, $update = array()) final protected function _delete($filter, $limit = 1)
{ {
$ret = array( $options = array_merge($this->getWriteOptions(), array("limit" => $limit));
"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; $bulk = new BulkWrite($options["ordered"]);
$bulk->delete($filter, $options);
return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
} }
/** /**
...@@ -1105,33 +1057,60 @@ class Collection ...@@ -1105,33 +1057,60 @@ class Collection
} }
/** /**
* Internal helper for running a command * Internal helper for massaging aggregate options
* @internal * @internal
*/ */
final protected function _runCommand($dbname, array $cmd, ReadPreference $rp = null) protected function _massageAggregateOptions($options)
{ {
//var_dump(\BSON\toJSON(\BSON\fromArray($cmd))); if ($options["useCursor"]) {
$command = new Command($cmd); $options["cursor"] = array("batchSize" => $options["batchSize"]);
return $this->manager->executeCommand($dbname, $command, $rp); }
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 * Internal helper for running a command
* * @internal
* @return string
*/ */
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);
} }
} }
...@@ -6,7 +6,7 @@ use MongoDB\Driver\Manager; ...@@ -6,7 +6,7 @@ use MongoDB\Driver\Manager;
class CollectionTest extends PHPUnit_Framework_TestCase { class CollectionTest extends PHPUnit_Framework_TestCase {
function setUp() { function setUp() {
require __DIR__ . "/" . "utils.inc"; require_once __DIR__ . "/" . "utils.inc";
$this->faker = Faker\Factory::create(); $this->faker = Faker\Factory::create();
$this->faker->seed(1234); $this->faker->seed(1234);
...@@ -44,5 +44,28 @@ class CollectionTest extends PHPUnit_Framework_TestCase { ...@@ -44,5 +44,28 @@ class CollectionTest extends PHPUnit_Framework_TestCase {
} }
$this->assertEquals(0, $n); $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)));
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment