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,916 +414,633 @@ class Collection ...@@ -179,916 +414,633 @@ 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
*
* @param array $filter The $filter criteria to search for
* @param array $options Additional options
* @return array The original document
*/ */
public function getFindOptions() public function findOneAndDelete(array $filter, array $options = array())
{ {
return array( $options = array_merge($this->getFindOneAndDeleteOptions(), $options);
/** $options = $this->_massageFindAndModifyOptions($options);
* Get partial results from a mongos if some shards are down (instead of throwing an error). $cmd = array(
* "findandmodify" => $this->collname,
* @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query "query" => $filter,
*/ ) + $options;
"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(),
/** $doc = $this->_runCommand($this->dbname, $cmd)->toArray();
* The number of documents to skip before returning. if ($doc["ok"]) {
* return $doc["value"];
* @see http://docs.mongodb.org/manual/reference/method/cursor.skip/ }
*/
"skip" => 0,
/** throw $this->_generateCommandException($doc);
* 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 * Finds a single document and replaces it, returning either the original or the replaced document
* provided to other helpers * By default, returns the original document.
* To return the new document set:
* $options = array("returnDocument" => Collection::FIND_ONE_AND_RETURN_AFTER);
* *
* @param array $options * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
* @return integer OP_QUERY Wire Protocol flags * @see Collection::getFindOneAndReplace() for supported $options
* @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 $filter The $filter criteria to search for
* @param array $options query/protocol options * @param array $replacement The document to replace with
* @return Query * @param array $options Additional options
* @internal * @return array
*/ */
final protected function _buildQuery($filter, $options) public function findOneAndReplace(array $filter, array $replacement, array $options = array())
{ {
if ($options["comment"]) { if (key($replacement)[0] == '$') {
$options["modifiers"]['$comment'] = $options["comment"]; throw new \InvalidArgumentException("First key in \$replacement must NOT be a \$operator");
}
if ($options["maxTimeMS"]) {
$options["modifiers"]['$maxTimeMS'] = $options["maxTimeMS"];
}
if ($options["sort"]) {
$options['$orderby'] = $options["sort"];
} }
$flags = $this->_opQueryFlags($options); $options = array_merge($this->getFindOneAndReplaceOptions(), $options);
$options["cursorFlags"] = $flags; $options = $this->_massageFindAndModifyOptions($options, $replacement);
$query = new Query($filter, $options);
return $query; $cmd = array(
} "findandmodify" => $this->collname,
"query" => $filter,
) + $options;
/** $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 updates it, returning either the original or the updated 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::getFindOneAndUpdate() for supported $options
* *
* @param array $bulk Array of operations * @param array $filter The $filter criteria to search for
* @param array $options Additional options * @param array $update An array of update operators to apply to the document
* @return WriteResult * @param array $options Additional options
* @return array
*/ */
public function bulkWrite(array $bulk, array $options = array()) public function findOneAndUpdate(array $filter, array $update, array $options = array())
{ {
$options = array_merge($this->getBulkOptions(), $options); if (key($update)[0] != '$') {
throw new \InvalidArgumentException("First key in \$update must 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);
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->getFindOneAndUpdateOptions(), $options);
$options = array_merge($this->getWriteOptions(), isset($args[1]) ? $args[1] : array(), array("limit" => 1)); $options = $this->_massageFindAndModifyOptions($options, $update);
$bulk->delete($args[0], $options);
break;
case "deleteMany": $cmd = array(
$options = array_merge($this->getWriteOptions(), isset($args[1]) ? $args[1] : array(), array("limit" => 0)); "findandmodify" => $this->collname,
$bulk->delete($args[0], $options); "query" => $filter,
break; ) + $options;
default: $doc = $this->_runCommand($this->dbname, $cmd)->toArray();
throw new \InvalidArgumentException(sprintf("Unknown operation type called '%s' (operation#%d)", $opname, $n)); if ($doc["ok"]) {
} return $doc["value"];
}
} }
return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
}
/**
* 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); throw $this->_generateCommandException($doc);
} }
/** /**
* Inserts the provided documents * Retrieves all aggregate options with their default values.
*
* @see http://docs.mongodb.org/manual/reference/command/insert/
* *
* @param array $documents The documents to insert * @return array of Collection::aggregate() options
* @return InsertManyResult
*/ */
public function insertMany(array $documents) public function getAggregateOptions()
{ {
$options = array_merge($this->getWriteOptions()); $opts = array(
/**
$bulk = new BulkWrite($options["ordered"]); * Enables writing to temporary files. When set to true, aggregation stages
$insertedIds = array(); * can write data to the _tmp subdirectory in the dbPath directory. The
* default is false.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
"allowDiskUse" => false,
foreach ($documents as $i => $document) { /**
$insertedId = $bulk->insert($document); * The number of documents to return per batch.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
"batchSize" => 0,
if ($insertedId !== null) { /**
$insertedIds[$i] = $insertedId; * The maximum amount of time to allow the query to run.
} *
} * @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
"maxTimeMS" => 0,
$writeResult = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); /**
* Indicates if the results should be provided as a cursor.
*
* 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/
*/
"useCursor" => true,
);
return new InsertManyResult($writeResult, $insertedIds); /* FIXME: Add a version check for useCursor */
return $opts;
} }
/** /**
* Internal helper for delete one/many documents * Retrieves all Bulk Write options with their default values.
* @internal *
* @return array of available Bulk Write options
*/ */
final protected function _delete($filter, $limit = 1) public function getBulkOptions()
{ {
$options = array_merge($this->getWriteOptions(), array("limit" => $limit)); return array(
"ordered" => false,
$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. * Returns the CollectionName this object operates on
* 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 string
* @return DeleteResult
*/ */
public function deleteOne(array $filter) public function getCollectionName()
{ {
$wr = $this->_delete($filter); return $this->collname;
return new DeleteResult($wr);
} }
/** /**
* Deletes a document matching the $filter criteria. * Retrieves all count options with their default values.
* 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 array of Collection::count() options
* @return DeleteResult
*/ */
public function deleteMany(array $filter) public function getCountOptions()
{ {
$wr = $this->_delete($filter, 0); return array(
/**
* The index to use.
*
* @see http://docs.mongodb.org/manual/reference/command/count/
*/
"hint" => "", // string or document
return new DeleteResult($wr); /**
* The maximum number of documents to count.
*
* @see http://docs.mongodb.org/manual/reference/command/count/
*/
"limit" => 0,
/**
* 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,
);
} }
/** /**
* Internal helper for replacing/updating one/many documents * Returns the DatabaseName this object operates on
* @internal *
* @return string
*/ */
protected function _update($filter, $update, $options) public function getDatabaseName()
{ {
$options = array_merge($this->getWriteOptions(), $options); return $this->dbname;
$bulk = new BulkWrite($options["ordered"]);
$bulk->update($filter, $update, $options);
return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
} }
/** /**
* Replace one document * Retrieves all distinct options with their default values.
*
* @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 Collection::distinct() options
* @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()) public function getDistinctOptions()
{ {
if (key($update)[0] == '$') { return array(
throw new \InvalidArgumentException("First key in \$update must NOT be a \$operator"); /**
} * The maximum amount of time to allow the query to run. The default is infinite.
$wr = $this->_update($filter, $update, $options); *
* @see http://docs.mongodb.org/manual/reference/command/distinct/
return new UpdateResult($wr); */
"maxTimeMS" => 0,
);
} }
/** /**
* Update one document * Retrieves all findOneDelete options with their default values.
* 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 * @return array of Collection::findOneAndDelete() options
* @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()) public function getFindOneAndDeleteOptions()
{ {
if (key($update)[0] != '$') { return array(
throw new \InvalidArgumentException("First key in \$update must be a \$operator");
}
$wr = $this->_update($filter, $update, $options);
return new UpdateResult($wr); /**
* The maximum amount of time to allow the query to run.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"maxTimeMS" => 0,
/**
* Limits the fields to return for all matching documents.
*
* @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results
*/
"projection" => array(),
/**
* Determines which document the operation modifies if the query selects multiple documents.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"sort" => array(),
);
} }
/** /**
* Update one document * Retrieves all findOneAndReplace 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 Collection::findOneAndReplace() 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 getFindOneAndReplaceOptions()
{ {
$wr = $this->_update($filter, $update, $options + array("limit" => 0)); return array(
return new UpdateResult($wr); /**
* The maximum amount of time to allow the query to run.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"maxTimeMS" => 0,
/**
* Limits the fields to return for all matching documents.
*
* @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results
*/
"projection" => array(),
/**
* When ReturnDocument.After, returns the replaced 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.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"sort" => array(),
/**
* 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,
);
} }
/** /**
* Counts all documents matching $filter * Retrieves all findOneAndUpdate options with their default values.
* 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 array of Collection::findOneAndUpdate() options
* @param array $options Additional options
* @return integer
*/ */
public function count(array $filter = array(), array $options = array()) public function getFindOneAndUpdateOptions()
{ {
$cmd = array( return array(
"count" => $this->collname,
"query" => $filter,
) + $options;
$doc = $this->_runCommand($this->dbname, $cmd)->toArray(); /**
if ($doc["ok"]) { * The maximum amount of time to allow the query to run.
return $doc["n"]; *
} * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
throw $this->_generateCommandException($doc); */
"maxTimeMS" => 0,
/**
* Limits the fields to return for all matching documents.
*
* @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results
*/
"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.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"sort" => array(),
/**
* When true, 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 count options with their default values. * Retrieves all find options with their default values.
* *
* @return array of Collection::count() options * @return array of Collection::find() options
*/ */
public function getCountOptions() public function getFindOptions()
{ {
return array( return array(
/** /**
* The index to use. * Get partial results from a mongos if some shards are down (instead of throwing an error).
* *
* @see http://docs.mongodb.org/manual/reference/command/count/ * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
*/ */
"hint" => "", // string or document "allowPartialResults" => false,
/** /**
* The maximum number of documents to count. * The number of documents to return per batch.
* *
* @see http://docs.mongodb.org/manual/reference/command/count/ * @see http://docs.mongodb.org/manual/reference/method/cursor.batchSize/
*/ */
"limit" => 0, "batchSize" => 101,
/** /**
* The maximum amount of time to allow the query to run. * 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/command/count/ * @see http://docs.mongodb.org/manual/reference/operator/meta/comment/
*/ */
"maxTimeMS" => 0, "comment" => "",
/** /**
* The number of documents to skip before returning the documents. * 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/command/count/ * @see http://docs.mongodb.org/manual/reference/operator/meta/comment/
*/ */
"skip" => 0, "cursorType" => self::CURSOR_TYPE_NON_TAILABLE,
);
}
/**
* 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"]) { * The maximum number of documents to return.
return $doc["values"]; *
} * @see http://docs.mongodb.org/manual/reference/method/cursor.limit/
throw $this->_generateCommandException($doc); */
} "limit" => 0,
/**
* 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. * 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/command/distinct/ * @see http://docs.mongodb.org/manual/reference/operator/meta/maxTimeMS/
*/ */
"maxTimeMS" => 0, "maxTimeMS" => 0,
);
}
/**
* 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);
}
/**
* Retrieves all aggregate options with their default values.
*
* @return array of Collection::aggregate() options
*/
public function getAggregateOptions()
{
$opts = array(
/** /**
* Enables writing to temporary files. When set to true, aggregation stages * Meta-operators modifying the output or behavior of a query.
* 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/operator/query-modifier/
*/ */
"allowDiskUse" => false, "modifiers" => array(),
/** /**
* The number of documents to return per batch. * 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/manual/reference/command/aggregate/ * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
*/ */
"batchSize" => 0, "noCursorTimeout" => false,
/** /**
* 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/aggregate/ * @see http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-query
* @internal
*/ */
"maxTimeMS" => 0, "oplogReplay" => false,
/** /**
* 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. * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/
* - Servers >= 2.6 will use a default of true. */
* - Servers < 2.6 will use a default of false. "projection" => array(),
/**
* The number of documents to skip before returning.
* *
* As with any other property, this value can be changed. * @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/command/aggregate/ * @see http://docs.mongodb.org/manual/reference/method/cursor.sort/
*/ */
"useCursor" => true, "sort" => array(),
); );
/* FIXME: Add a version check for useCursor */
return $opts;
} }
/** /**
* Internal helper for massaging aggregate options * Retrieves all Write options with their default values.
* @internal *
* @return array of available Write options
*/ */
protected function _massageAggregateOptions($options) public function getWriteOptions()
{ {
if ($options["useCursor"]) { return array(
$options["cursor"] = array("batchSize" => $options["batchSize"]); "ordered" => false,
} "upsert" => false,
unset($options["useCursor"], $options["batchSize"]); "limit" => 1,
);
return $options;
} }
/** /**
* Finds a single document and deletes it, returning the original. * Inserts the provided documents
* *
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ * @see http://docs.mongodb.org/manual/reference/command/insert/
* @see Collection::getFindOneAndDelete() for supported $options
* *
* @param array $filter The $filter criteria to search for * @param array $documents The documents to insert
* @param array $options Additional options * @return InsertManyResult
* @return array The original document
*/ */
public function findOneAndDelete(array $filter, array $options = array()) public function insertMany(array $documents)
{ {
$options = array_merge($this->getFindOneAndDeleteOptions(), $options); $options = array_merge($this->getWriteOptions());
$options = $this->_massageFindAndModifyOptions($options);
$cmd = array(
"findandmodify" => $this->collname,
"query" => $filter,
) + $options;
$doc = $this->_runCommand($this->dbname, $cmd)->toArray(); $bulk = new BulkWrite($options["ordered"]);
if ($doc["ok"]) { $insertedIds = array();
return $doc["value"];
foreach ($documents as $i => $document) {
$insertedId = $bulk->insert($document);
if ($insertedId !== null) {
$insertedIds[$i] = $insertedId;
}
} }
throw $this->_generateCommandException($doc); $writeResult = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
return new InsertManyResult($writeResult, $insertedIds);
} }
/** /**
* Retrieves all findOneDelete options with their default values. * Inserts the provided document
* *
* @return array of Collection::findOneAndDelete() 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 getFindOneAndDeleteOptions() public function insertOne(array $document)
{ {
return array( $options = array_merge($this->getWriteOptions());
/**
* The maximum amount of time to allow the query to run.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"maxTimeMS" => 0,
/** $bulk = new BulkWrite($options["ordered"]);
* Limits the fields to return for all matching documents. $id = $bulk->insert($document);
* $wr = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
* @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results
*/
"projection" => array(),
/** return new InsertOneResult($wr, $id);
* Determines which document the operation modifies if the query selects multiple documents.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"sort" => array(),
);
} }
/** /**
* Finds a single document and replaces it, returning either the original or the replaced document * Replace one 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 http://docs.mongodb.org/manual/reference/command/update/
* @see Collection::getFindOneAndReplace() for supported $options * @see Collection::getWriteOptions() for supported $options
* *
* @param array $filter The $filter criteria to search for * @param array $filter The document to be replaced
* @param array $replacement The document to replace with * @param array $update The document to replace with
* @param array $options Additional options * @param array $options Additional options
* @return array * @return UpdateResult
*/ */
public function findOneAndReplace(array $filter, array $replacement, array $options = array()) public function replaceOne(array $filter, array $update, array $options = array())
{ {
if (key($replacement)[0] == '$') { if (key($update)[0] == '$') {
throw new \InvalidArgumentException("First key in \$replacement must NOT be a \$operator"); throw new \InvalidArgumentException("First key in \$update 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"];
} }
$wr = $this->_update($filter, $update, $options);
throw $this->_generateCommandException($doc); return new UpdateResult($wr);
} }
/** /**
* Retrieves all findOneAndReplace options with their default values. * Update one document
* NOTE: Will update ALL documents matching $filter
* *
* @return array of Collection::findOneAndReplace() 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
* @return UpdateResult
*/ */
public function getFindOneAndReplaceOptions() public function updateMany(array $filter, $update, array $options = array())
{ {
return array( $wr = $this->_update($filter, $update, $options + array("limit" => 0));
/**
* The maximum amount of time to allow the query to run.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"maxTimeMS" => 0,
/**
* Limits the fields to return for all matching documents.
*
* @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results
*/
"projection" => array(),
/**
* When ReturnDocument.After, returns the replaced 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.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"sort" => array(),
/** return new UpdateResult($wr);
* 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,
);
} }
/** /**
* Finds a single document and updates it, returning either the original or the updated document * Update one document
* By default, returns the original document. * NOTE: Will update at most ONE document matching $filter
* 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/update/
* @see Collection::getFindOneAndUpdate() for supported $options * @see Collection::getWriteOptions() for supported $options
* *
* @param array $filter The $filter criteria to search for * @param array $filter The document to be replaced
* @param array $update An array of update operators to apply to the document * @param array $update An array of update operators to apply to the document
* @param array $options Additional options * @param array $options Additional options
* @return array * @return UpdateResult
*/ */
public function findOneAndUpdate(array $filter, array $update, array $options = array()) public function updateOne(array $filter, array $update, array $options = array())
{ {
if (key($update)[0] != '$') { if (key($update)[0] != '$') {
throw new \InvalidArgumentException("First key in \$update must be a \$operator"); throw new \InvalidArgumentException("First key in \$update must be a \$operator");
} }
$wr = $this->_update($filter, $update, $options);
$options = array_merge($this->getFindOneAndUpdateOptions(), $options); return new UpdateResult($wr);
$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. * Helper to build a Query object
* *
* @return array of Collection::findOneAndUpdate() options * @param array $filter the query document
* @param array $options query/protocol options
* @return Query
* @internal
*/ */
public function getFindOneAndUpdateOptions() final protected function _buildQuery($filter, $options)
{ {
return array( if ($options["comment"]) {
$options["modifiers"]['$comment'] = $options["comment"];
/** }
* The maximum amount of time to allow the query to run. if ($options["maxTimeMS"]) {
* $options["modifiers"]['$maxTimeMS'] = $options["maxTimeMS"];
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/ }
*/ if ($options["sort"]) {
"maxTimeMS" => 0, $options['$orderby'] = $options["sort"];
}
/** $flags = $this->_opQueryFlags($options);
* Limits the fields to return for all matching documents. $options["cursorFlags"] = $flags;
*
* @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results
*/
"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,
/** $query = new Query($filter, $options);
* Determines which document the operation modifies if the query selects multiple documents.
*
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
"sort" => array(),
/** return $query;
* When true, 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,
);
} }
/** /**
* 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