Commit 60931d82 authored by Jeremy Mikola's avatar Jeremy Mikola

Merge pull request #13

parents 64c40145 cab405d6
......@@ -13,7 +13,8 @@
"ext-mongodb": ">=0.6.0"
},
"autoload": {
"psr-4": { "MongoDB\\": "src/" }
"psr-4": { "MongoDB\\": "src/" },
"files": [ "src/functions.php" ]
},
"extra": {
"branch-alias": {
......
......@@ -7,9 +7,9 @@ use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Model\DatabaseInfoIterator;
use MongoDB\Model\DatabaseInfoLegacyIterator;
use MongoDB\Operation\DropDatabase;
use MongoDB\Operation\ListDatabases;
class Client
{
......@@ -37,45 +37,29 @@ class Client
/**
* Drop a database.
*
* @see http://docs.mongodb.org/manual/reference/command/dropDatabase/
* @param string $databaseName
* @return Cursor
* @return object Command result document
*/
public function dropDatabase($databaseName)
{
$databaseName = (string) $databaseName;
$command = new Command(array('dropDatabase' => 1));
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
$operation = new DropDatabase($databaseName);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
return $this->manager->executeCommand($databaseName, $command, $readPreference);
return $operation->execute($server);
}
/**
* List databases.
*
* @see http://docs.mongodb.org/manual/reference/command/listDatabases/
* @see ListDatabases::__construct() for supported options
* @return DatabaseInfoIterator
* @throws UnexpectedValueException if the command result is malformed
*/
public function listDatabases()
public function listDatabases(array $options = array())
{
$command = new Command(array('listDatabases' => 1));
$operation = new ListDatabases($options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$cursor = $this->manager->executeCommand('admin', $command);
$cursor->setTypeMap(array('document' => 'array'));
$result = current($cursor->toArray());
if ( ! isset($result['databases']) || ! is_array($result['databases'])) {
throw new UnexpectedValueException('listDatabases command did not return a "databases" array');
}
/* Return an Iterator instead of an array in case listDatabases is
* eventually changed to return a command cursor, like the collection
* and index enumeration commands. This makes the "totalSize" command
* field inaccessible, but users can manually invoke the command if they
* need that value.
*/
return new DatabaseInfoLegacyIterator($result['databases']);
return $operation->execute($server);
}
/**
......
This diff is collapsed.
......@@ -12,8 +12,10 @@ use MongoDB\Driver\Server;
use MongoDB\Driver\WriteConcern;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Model\CollectionInfoIterator;
use MongoDB\Model\CollectionInfoCommandIterator;
use MongoDB\Model\CollectionInfoLegacyIterator;
use MongoDB\Operation\CreateCollection;
use MongoDB\Operation\DropCollection;
use MongoDB\Operation\DropDatabase;
use MongoDB\Operation\ListCollections;
class Database
{
......@@ -54,49 +56,44 @@ class Database
/**
* Create a new collection explicitly.
*
* @see http://docs.mongodb.org/manual/reference/command/create/
* @see http://docs.mongodb.org/manual/reference/method/db.createCollection/
* @see CreateCollection::__construct() for supported options
* @param string $collectionName
* @param array $options
* @return Cursor
* @return object Command result document
*/
public function createCollection($collectionName, array $options = array())
{
$collectionName = (string) $collectionName;
$command = new Command(array('create' => $collectionName) + $options);
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
$operation = new CreateCollection($this->databaseName, $collectionName, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
return $this->manager->executeCommand($this->databaseName, $command, $readPreference);
return $operation->execute($server);
}
/**
* Drop this database.
*
* @see http://docs.mongodb.org/manual/reference/command/dropDatabase/
* @return Cursor
* @return object Command result document
*/
public function drop()
{
$command = new Command(array('dropDatabase' => 1));
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
$operation = new DropDatabase($this->databaseName);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
return $this->manager->executeCommand($this->databaseName, $command, $readPreference);
return $operation->execute($server);
}
/**
* Drop a collection within this database.
*
* @see http://docs.mongodb.org/manual/reference/command/drop/
* @param string $collectionName
* @return Cursor
* @return object Command result document
*/
public function dropCollection($collectionName)
{
$collectionName = (string) $collectionName;
$command = new Command(array('drop' => $collectionName));
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
$operation = new DropCollection($this->databaseName, $collectionName);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
return $this->manager->executeCommand($this->databaseName, $command, $readPreference);
return $operation->execute($server);
}
/**
......@@ -112,18 +109,16 @@ class Database
/**
* Returns information for all collections in this database.
*
* @see http://docs.mongodb.org/manual/reference/command/listCollections/
* @see ListCollections::__construct() for supported options
* @param array $options
* @return CollectionInfoIterator
*/
public function listCollections(array $options = array())
{
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
$server = $this->manager->selectServer($readPreference);
$operation = new ListCollections($this->databaseName, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
return (FeatureDetection::isSupported($server, FeatureDetection::API_LISTCOLLECTIONS_CMD))
? $this->listCollectionsCommand($server, $options)
: $this->listCollectionsLegacy($server, $options);
return $operation->execute($server);
}
/**
......@@ -145,58 +140,4 @@ class Database
return new Collection($this->manager, $namespace, $writeConcern, $readPreference);
}
/**
* Returns information for all collections in this database using the
* listCollections command.
*
* @param Server $server
* @param array $options
* @return CollectionInfoCommandIterator
*/
private function listCollectionsCommand(Server $server, array $options = array())
{
$command = new Command(array('listCollections' => 1) + $options);
$cursor = $server->executeCommand($this->databaseName, $command);
$cursor->setTypeMap(array('document' => 'array'));
return new CollectionInfoCommandIterator($cursor);
}
/**
* Returns information for all collections in this database by querying the
* "system.namespaces" collection (MongoDB <2.8).
*
* @param Server $server
* @param array $options
* @return CollectionInfoLegacyIterator
* @throws InvalidArgumentException if the filter option is neither an array
* nor object, or if filter.name is not a
* string.
*/
private function listCollectionsLegacy(Server $server, array $options = array())
{
$filter = array_key_exists('filter', $options) ? $options['filter'] : array();
if ( ! is_array($filter) && ! is_object($filter)) {
throw new InvalidArgumentException(sprintf('Expected filter to be array or object, %s given', gettype($filter)));
}
if (array_key_exists('name', (array) $filter)) {
$filter = (array) $filter;
if ( ! is_string($filter['name'])) {
throw new InvalidArgumentException(sprintf('Filter "name" must be a string for MongoDB <2.8, %s given', gettype($filter['name'])));
}
$filter['name'] = $this->databaseName . '.' . $filter['name'];
}
$namespace = $this->databaseName . '.system.namespaces';
$query = new Query($filter);
$cursor = $server->executeQuery($namespace, $query);
$cursor->setTypeMap(array('document' => 'array'));
return new CollectionInfoLegacyIterator($cursor);
}
}
<?php
namespace MongoDB\Exception;
class InvalidArgumentTypeException extends InvalidArgumentException
{
public function __construct($name, $value, $expectedType)
{
parent::__construct(sprintf('Expected %s to have type "%s" but found "%s"', $name, $expectedType, is_object($value) ? get_class($value) : gettype($value)));
}
}
<?php
namespace MongoDB\Exception;
class UnexpectedValueTypeException extends UnexpectedValueException
{
public function __construct($name, $value, $expectedType)
{
parent::__construct(sprintf('Expected %s to have type "%s" but found "%s"', $name, $expectedType, is_object($value) ? get_class($value) : gettype($value)));
}
}
<?php
namespace MongoDB;
use MongoDB\Driver\Server;
/**
* Utility class for detecting features based on wire protocol versions.
*
* @internal
*/
class FeatureDetection
{
const API_LISTCOLLECTIONS_CMD = 3;
const API_LISTINDEXES_CMD = 3;
const API_CREATEINDEXES_CMD = 2;
const API_AGGREGATE_CURSOR = 2;
/**
* Return whether the server supports a particular feature.
*
* @param Server $server Server to check
* @param integer $feature Feature constant (i.e. wire protocol version)
* @return boolean
*/
static public function isSupported(Server $server, $feature)
{
$info = $server->getInfo();
$maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0;
$minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0;
return ($minWireVersion <= $feature && $maxWireVersion >= $feature);
}
}
......@@ -50,7 +50,7 @@ class IndexInput implements Serializable
}
if ( ! isset($index['name'])) {
$index['name'] = $this->generateName($index['key']);
$index['name'] = \MongoDB\generate_index_name($index['key']);
}
if ( ! is_string($index['name'])) {
......@@ -80,22 +80,4 @@ class IndexInput implements Serializable
{
return $this->index;
}
/**
* Generates an index name from its key specification.
*
* @param array|object $key Document containing fields mapped to values,
* which denote order or an index type
* @return string
*/
private function generateName($key)
{
$name = '';
foreach ($key as $field => $type) {
$name .= ($name != '' ? '_' : '') . $field . '_' . $type;
}
return $name;
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
use MongoDB\Exception\RuntimeException;
use MongoDB\Exception\UnexpectedValueException;
use ArrayIterator;
use stdClass;
use Traversable;
/**
* Operation for the aggregate command.
*
* @api
* @see MongoDB\Collection::aggregate()
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
class Aggregate implements Executable
{
private static $wireVersionForCursor = 2;
private $databaseName;
private $collectionName;
private $pipeline;
private $options;
/**
* Constructs an aggregate command.
*
* Supported options:
*
* * allowDiskUse (boolean): Enables writing to temporary files. When set
* to true, aggregation stages can write data to the _tmp sub-directory
* in the dbPath directory. The default is false.
*
* * batchSize (integer): The number of documents to return per batch.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * useCursor (boolean): Indicates whether the command will request that
* the server provide results using a cursor. The default is true.
*
* For servers < 2.6, this option is ignored as aggregation cursors are
* not available.
*
* For servers >= 2.6, this option allows users to turn off cursors if
* necessary to aid in mongod/mongos upgrades.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $pipeline List of pipeline operations
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, array $pipeline, array $options = array())
{
$options += array(
'allowDiskUse' => false,
'useCursor' => true,
);
if ( ! is_bool($options['allowDiskUse'])) {
throw new InvalidArgumentTypeException('"allowDiskUse" option', $options['allowDiskUse'], 'boolean');
}
if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) {
throw new InvalidArgumentTypeException('"batchSize" option', $options['batchSize'], 'integer');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if ( ! is_bool($options['useCursor'])) {
throw new InvalidArgumentTypeException('"useCursor" option', $options['useCursor'], 'boolean');
}
if (isset($options['batchSize']) && ! $options['useCursor']) {
throw new InvalidArgumentException('"batchSize" option should not be used if "useCursor" is false');
}
$expectedIndex = 0;
foreach ($pipeline as $i => $op) {
if ($i !== $expectedIndex) {
throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($op) && ! is_object($op)) {
throw new InvalidArgumentTypeException(sprintf('$pipeline[%d]', $i), $op, 'array or object');
}
$expectedIndex += 1;
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->pipeline = $pipeline;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return Traversable
*/
public function execute(Server $server)
{
$isCursorSupported = \MongoDB\server_supports_feature($server, self::$wireVersionForCursor);
$command = $this->createCommand($server, $isCursorSupported);
$cursor = $server->executeCommand($this->databaseName, $command);
if ($isCursorSupported && $this->options['useCursor']) {
return $cursor;
}
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
if ( ! isset($result['result']) || ! is_array($result['result'])) {
throw new UnexpectedValueException('aggregate command did not return a "result" array');
}
return new ArrayIterator(array_map(
function (stdClass $document) { return (array) $document; },
$result['result']
));
}
/**
* Create the aggregate command.
*
* @param Server $server
* @param boolean $isCursorSupported
* @return Command
*/
private function createCommand(Server $server, $isCursorSupported)
{
$cmd = array(
'aggregate' => $this->collectionName,
'pipeline' => $this->pipeline,
);
// Servers < 2.6 do not support any command options
if ( ! $isCursorSupported) {
return new Command($cmd);
}
$cmd['allowDiskUse'] = $this->options['allowDiskUse'];
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
if ($this->options['useCursor']) {
$cmd['cursor'] = isset($this->options["batchSize"])
? array('batchSize' => $this->options["batchSize"])
: new stdClass;
}
return new Command($cmd);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
use MongoDB\Exception\RuntimeException;
use MongoDB\Exception\UnexpectedValueException;
/**
* Operation for the count command.
*
* @api
* @see MongoDB\Collection::count()
* @see http://docs.mongodb.org/manual/reference/command/count/
*/
class Count implements Executable
{
private $databaseName;
private $collectionName;
private $filter;
private $options;
/**
* Constructs a count command.
*
* Supported options:
*
* * hint (string|document): The index to use. If a document, it will be
* interpretted as an index specification and a name will be generated.
*
* * limit (integer): The maximum number of documents to count.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * skip (integer): The number of documents to skip before returning the
* documents.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, array $filter = array(), array $options = array())
{
if (isset($options['hint'])) {
if (is_array($options['hint']) || is_object($options['hint'])) {
$options['hint'] = \MongoDB\generate_index_name($options['hint']);
}
if ( ! is_string($options['hint'])) {
throw new InvalidArgumentTypeException('"hint" option', $options['hint'], 'string or array or object');
}
}
if (isset($options['limit']) && ! is_integer($options['limit'])) {
throw new InvalidArgumentTypeException('"limit" option', $options['limit'], 'integer');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['skip']) && ! is_integer($options['skip'])) {
throw new InvalidArgumentTypeException('"skip" option', $options['skip'], 'integer');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->filter = $filter;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return integer
*/
public function execute(Server $server)
{
$cursor = $server->executeCommand($this->databaseName, $this->createCommand());
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
// Older server versions may return a float
if ( ! isset($result['n']) || ! (is_integer($result['n']) || is_float($result['n']))) {
throw new UnexpectedValueException('count command did not return an "n" value');
}
return (integer) $result['n'];
}
/**
* Create the count command.
*
* @return Command
*/
private function createCommand()
{
$cmd = array(
'count' => $this->collectionName,
);
if ( ! empty($this->filter)) {
$cmd['query'] = (object) $this->filter;
}
foreach (array('hint', 'limit', 'maxTimeMS', 'skip') as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = $this->options[$option];
}
}
return new Command($cmd);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\BulkWrite;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\RuntimeException;
use MongoDB\Exception\UnexpectedTypeException;
use MongoDB\Model\IndexInput;
/**
* Operation for the create command.
*
* @api
* @see MongoDB\Database::createCollection()
* @see http://docs.mongodb.org/manual/reference/command/create/
*/
class CreateCollection implements Executable
{
const USE_POWER_OF_2_SIZES = 1;
const NO_PADDING = 2;
private $databaseName;
private $collectionName;
private $options = array();
/**
* Constructs a create command.
*
* Supported options:
*
* * autoIndexId (boolean): Specify false to disable the automatic creation
* of an index on the _id field. For replica sets, this option cannot be
* false. The default is true.
*
* * capped (boolean): Specify true to create a capped collection. If set,
* the size option must also be specified. The default is false.
*
* * flags (integer): Options for the MMAPv1 storage engine only. Must be a
* bitwise combination USE_POWER_OF_2_SIZES and NO_PADDING. The default
* is USE_POWER_OF_2_SIZES.
*
* * max (integer): The maximum number of documents allowed in the capped
* collection. The size option takes precedence over this limit.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * size (integer): The maximum number of bytes for a capped collection.
*
* * storageEngine (document): Storage engine options.
*
* @see http://source.wiredtiger.com/2.4.1/struct_w_t___s_e_s_s_i_o_n.html#a358ca4141d59c345f401c58501276bbb
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, array $options = array())
{
if (isset($options['autoIndexId']) && ! is_bool($options['autoIndexId'])) {
throw new InvalidArgumentTypeException('"autoIndexId" option', $options['autoIndexId'], 'boolean');
}
if (isset($options['capped']) && ! is_bool($options['capped'])) {
throw new InvalidArgumentTypeException('"capped" option', $options['capped'], 'boolean');
}
if (isset($options['flags']) && ! is_integer($options['flags'])) {
throw new InvalidArgumentTypeException('"flags" option', $options['flags'], 'integer');
}
if (isset($options['max']) && ! is_integer($options['max'])) {
throw new InvalidArgumentTypeException('"max" option', $options['max'], 'integer');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['size']) && ! is_integer($options['size'])) {
throw new InvalidArgumentTypeException('"size" option', $options['size'], 'integer');
}
if (isset($options['storageEngine']) && ! is_array($options['storageEngine']) && ! is_object($options['storageEngine'])) {
throw new InvalidArgumentTypeException('"storageEngine" option', $options['storageEngine'], 'array or object');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
}
/**
* Execute the operation.
*
* For servers < 2.6, this will actually perform an insert operation on the
* database's "system.indexes" collection.
*
* @see Executable::execute()
* @param Server $server
* @return object Command result document
*/
public function execute(Server $server)
{
$cursor = $server->executeCommand($this->databaseName, $this->createCommand());
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
return $result;
}
/**
* Create the create command.
*
* @return Command
*/
private function createCommand()
{
$cmd = array('create' => $this->collectionName);
foreach (array('autoIndexId', 'capped', 'flags', 'max', 'maxTimeMS', 'size') as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = $this->options[$option];
}
}
if ( ! empty($this->options['storageEngine'])) {
$cmd['storageEngine'] = (object) $this->options['storageEngine'];
}
return new Command($cmd);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\BulkWrite;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\RuntimeException;
use MongoDB\Exception\UnexpectedTypeException;
use MongoDB\Model\IndexInput;
/**
* Operation for the createIndexes command.
*
* @api
* @see MongoDB\Collection::createIndex()
* @see MongoDB\Collection::createIndexes()
* @see http://docs.mongodb.org/manual/reference/command/createIndexes/
*/
class CreateIndexes implements Executable
{
private static $wireVersionForCommand = 2;
private $databaseName;
private $collectionName;
private $indexes = array();
/**
* Constructs a createIndexes command.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array[] $indexes List of index specifications
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, array $indexes)
{
if (empty($indexes)) {
throw new InvalidArgumentException('$indexes is empty');
}
foreach ($indexes as $index) {
if ( ! is_array($index)) {
throw new UnexpectedTypeException($index, 'array');
}
if ( ! isset($index['ns'])) {
$index['ns'] = $databaseName . '.' . $collectionName;
}
$this->indexes[] = new IndexInput($index);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
}
/**
* Execute the operation.
*
* For servers < 2.6, this will actually perform an insert operation on the
* database's "system.indexes" collection.
*
* @see Executable::execute()
* @param Server $server
* @return string[] The names of the created indexes
*/
public function execute(Server $server)
{
if (\MongoDB\server_supports_feature($server, self::$wireVersionForCommand)) {
$this->executeCommand($server);
} else {
$this->executeLegacy($server);
}
return array_map(function(IndexInput $index) { return (string) $index; }, $this->indexes);
}
/**
* Create one or more indexes for the collection using the createIndexes
* command.
*
* @param Server $server
*/
private function executeCommand(Server $server)
{
$command = new Command(array(
'createIndexes' => $this->collectionName,
'indexes' => $this->indexes,
));
$cursor = $server->executeCommand($this->databaseName, $command);
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
}
/**
* Create one or more indexes for the collection by inserting into the
* "system.indexes" collection (MongoDB <2.6).
*
* @param Server $server
* @param IndexInput[] $indexes
*/
private function executeLegacy(Server $server)
{
$bulk = new BulkWrite(true);
foreach ($this->indexes as $index) {
$bulk->insert($index);
}
$server->executeBulkWrite($this->databaseName . '.system.indexes', $bulk);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
use MongoDB\Exception\RuntimeException;
use MongoDB\Exception\UnexpectedValueException;
/**
* Operation for the distinct command.
*
* @api
* @see MongoDB\Collection::distinct()
* @see http://docs.mongodb.org/manual/reference/command/distinct/
*/
class Distinct implements Executable
{
private $databaseName;
private $collectionName;
private $fieldName;
private $filter;
private $options;
/**
* Constructs a distinct command.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param string $fieldName Field for which to return distinct values
* @param array $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, $fieldName, array $filter = array(), array $options = array())
{
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->fieldName = (string) $fieldName;
$this->filter = $filter;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return mixed[]
*/
public function execute(Server $server)
{
$cursor = $server->executeCommand($this->databaseName, $this->createCommand());
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
if ( ! isset($result['values']) || ! is_array($result['values'])) {
throw new UnexpectedValueException('distinct command did not return a "values" array');
}
return $result['values'];
}
/**
* Create the distinct command.
*
* @return Command
*/
private function createCommand()
{
$cmd = array(
'distinct' => $this->collectionName,
'key' => $this->fieldName,
);
if ( ! empty($this->filter)) {
$cmd['query'] = (object) $this->filter;
}
if (isset($this->options['maxTimeMS'])) {
$cmd[$option] = $this->options['maxTimeMS'];
}
return new Command($cmd);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\RuntimeException;
/**
* Operation for the drop command.
*
* @api
* @see MongoDB\Collection::drop()
* @see MongoDB\Database::dropCollection()
* @see http://docs.mongodb.org/manual/reference/command/drop/
*/
class DropCollection implements Executable
{
private $databaseName;
private $collectionName;
/**
* Constructs a drop command.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
*/
public function __construct($databaseName, $collectionName)
{
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return object Command result document
*/
public function execute(Server $server)
{
$cursor = $server->executeCommand($this->databaseName, new Command(array('drop' => $this->collectionName)));
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
return $result;
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\RuntimeException;
/**
* Operation for the dropDatabase command.
*
* @api
* @see MongoDB\Client::dropDatabase()
* @see MongoDB\Database::drop()
* @see http://docs.mongodb.org/manual/reference/command/dropDatabase/
*/
class DropDatabase implements Executable
{
private $databaseName;
/**
* Constructs a dropDatabase command.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
*/
public function __construct($databaseName)
{
$this->databaseName = (string) $databaseName;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return object Command result document
*/
public function execute(Server $server)
{
$cursor = $server->executeCommand($this->databaseName, new Command(array('dropDatabase' => 1)));
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
return $result;
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\RuntimeException;
/**
* Operation for the dropIndexes command.
*
* @api
* @see MongoDB\Collection::dropIndexes()
* @see http://docs.mongodb.org/manual/reference/command/dropIndexes/
*/
class DropIndexes implements Executable
{
private $databaseName;
private $collectionName;
private $indexName;
/**
* Constructs a dropIndexes command.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param string $indexName Index name (use "*" to drop all indexes)
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, $indexName)
{
$indexName = (string) $indexName;
if ($indexName === '') {
throw new InvalidArgumentException('$indexName cannot be empty');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->indexName = $indexName;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return object Command result document
*/
public function execute(Server $server)
{
$cmd = array(
'dropIndexes' => $this->collectionName,
'index' => $this->indexName,
);
$cursor = $server->executeCommand($this->databaseName, new Command($cmd));
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
return $result;
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
/**
* Executable interface for operation classes.
*
* @api
*/
interface Executable
{
/**
* Execute the operation.
*
* @param Server $server
* @return mixed
*/
public function execute(Server $server);
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
use MongoDB\Exception\RuntimeException;
use MongoDB\Exception\UnexpectedValueException;
/**
* Operation for the findAndModify command.
*
* This class is used internally by the FindOneAndDelete, FindOneAndReplace, and
* FindOneAndUpdate operation classes.
*
* @internal
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindAndModify implements Executable
{
private $databaseName;
private $collectionName;
private $options;
/**
* Constructs a findAndModify command.
*
* Supported options:
*
* * fields (document): Limits the fields to return for the matching
* document.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * new (boolean): When true, returns the modified document rather than
* the original. This option is ignored for remove operations. The
* The default is false.
*
* * query (document): Query by which to filter documents.
*
* * remove (boolean): When true, removes the matched document. This option
* cannot be true if the update option is set. The default is false.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * update (document): Update or replacement to apply to the matched
* document. This option cannot be set if the remove option is true.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. This option is ignored for remove operations. The
* default is false.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, array $options)
{
$options += array(
'new' => false,
'remove' => false,
'upsert' => false,
);
if (isset($options['fields']) && ! is_array($options['fields']) && ! is_object($options['fields'])) {
throw new InvalidArgumentTypeException('"fields" option', $options['fields'], 'array or object');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if ( ! is_bool($options['new'])) {
throw new InvalidArgumentTypeException('"new" option', $options['new'], 'boolean');
}
if (isset($options['query']) && ! is_array($options['query']) && ! is_object($options['query'])) {
throw new InvalidArgumentTypeException('"query" option', $options['query'], 'array or object');
}
if ( ! is_bool($options['remove'])) {
throw new InvalidArgumentTypeException('"remove" option', $options['remove'], 'boolean');
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object');
}
if (isset($options['update']) && ! is_array($options['update']) && ! is_object($options['update'])) {
throw new InvalidArgumentTypeException('"update" option', $options['update'], 'array or object');
}
if ( ! is_bool($options['upsert'])) {
throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean');
}
if ( ! (isset($options['update']) xor $options['remove'])) {
throw new InvalidArgumentException('The "remove" option must be true or an "update" document must be specified, but not both');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return object|null
*/
public function execute(Server $server)
{
$cursor = $server->executeCommand($this->databaseName, $this->createCommand());
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
if ( ! isset($result['value'])) {
return null;
}
/* Prior to 3.0, findAndModify returns an empty document instead of null
* when an upsert is performed and the pre-modified document was
* requested.
*/
if ($this->options['upsert'] && ! $this->options['new'] &&
isset($result['lastErrorObject']->updatedExisting) &&
! $result['lastErrorObject']->updatedExisting) {
return null;
}
if ( ! is_object($result['value'])) {
throw new UnexpectedValueException('findAndModify command did not return a "value" document');
}
return $result['value'];
}
/**
* Create the findAndModify command.
*
* @return Command
*/
private function createCommand()
{
$cmd = array(
'findAndModify' => $this->collectionName,
);
if ($this->options['remove']) {
$cmd['remove'] = true;
} else {
$cmd['new'] = $this->options['new'];
$cmd['upsert'] = $this->options['upsert'];
}
foreach (array('fields', 'query', 'sort', 'update') as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = (object) $this->options[$option];
}
}
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
return new Command($cmd);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
/**
* Operation for deleting a document with the findAndModify command.
*
* @api
* @see MongoDB\Collection::findOneAndDelete()
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindOneAndDelete implements Executable
{
private $findAndModify;
/**
* Constructs a findAndModify command for deleting a document.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, $filter, array $options = array())
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw new InvalidArgumentTypeException('$filter', $filter, 'array or object');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw new InvalidArgumentTypeException('"projection" option', $options['projection'], 'array or object');
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object');
}
$this->findAndModify = new FindAndModify(
$databaseName,
$collectionName,
array(
'fields' => isset($options['projection']) ? $options['projection'] : null,
'maxTimeMS' => isset($options['maxTimeMS']) ? $options['maxTimeMS'] : null,
'query' => $filter,
'remove' => true,
'sort' => isset($options['sort']) ? $options['sort'] : null,
)
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return object|null
*/
public function execute(Server $server)
{
return $this->findAndModify->execute($server);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
/**
* Operation for replacing a document with the findAndModify command.
*
* @api
* @see MongoDB\Collection::findOneAndReplace()
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindOneAndReplace implements Executable
{
const RETURN_DOCUMENT_BEFORE = 1;
const RETURN_DOCUMENT_AFTER = 2;
private $findAndModify;
/**
* Constructs a findAndModify command for replacing a document.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * returnDocument (enum): Whether to return the document before or after
* the update is applied. Must be either RETURN_DOCUMENT_BEFORE or
* RETURN_DOCUMENT_AFTER. The default is RETURN_DOCUMENT_BEFORE.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array|object $replacement Replacement document
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, $filter, $replacement, array $options = array())
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw new InvalidArgumentTypeException('$filter', $filter, 'array or object');
}
if ( ! is_array($replacement) && ! is_object($replacement)) {
throw new InvalidArgumentTypeException('$replacement', $replacement, 'array or object');
}
if (\MongoDB\is_first_key_operator($replacement)) {
throw new InvalidArgumentException('First key in $replacement argument is an update operator');
}
$options += array(
'returnDocument' => self::RETURN_DOCUMENT_BEFORE,
'upsert' => false,
);
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw new InvalidArgumentTypeException('"projection" option', $options['projection'], 'array or object');
}
if ( ! is_integer($options['returnDocument'])) {
throw new InvalidArgumentTypeException('"returnDocument" option', $options['returnDocument'], 'integer');
}
if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER &&
$options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) {
throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']);
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object');
}
if ( ! is_bool($options['upsert'])) {
throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean');
}
$this->findAndModify = new FindAndModify(
$databaseName,
$collectionName,
array(
'fields' => isset($options['projection']) ? $options['projection'] : null,
'maxTimeMS' => isset($options['maxTimeMS']) ? $options['maxTimeMS'] : null,
'new' => $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER,
'query' => $filter,
'sort' => isset($options['sort']) ? $options['sort'] : null,
'update' => $replacement,
'upsert' => $options['upsert'],
)
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return object|null
*/
public function execute(Server $server)
{
return $this->findAndModify->execute($server);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
/**
* Operation for updating a document with the findAndModify command.
*
* @api
* @see MongoDB\Collection::findOneAndUpdate()
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindOneAndUpdate implements Executable
{
const RETURN_DOCUMENT_BEFORE = 1;
const RETURN_DOCUMENT_AFTER = 2;
private $findAndModify;
/**
* Constructs a findAndModify command for updating a document.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * returnDocument (enum): Whether to return the document before or after
* the update is applied. Must be either RETURN_DOCUMENT_BEFORE or
* RETURN_DOCUMENT_AFTER. The default is RETURN_DOCUMENT_BEFORE.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array|object $update Update to apply to the matched document
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = array())
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw new InvalidArgumentTypeException('$filter', $filter, 'array or object');
}
if ( ! is_array($update) && ! is_object($update)) {
throw new InvalidArgumentTypeException('$update', $update, 'array or object');
}
if ( ! \MongoDB\is_first_key_operator($update)) {
throw new InvalidArgumentException('First key in $update argument is not an update operator');
}
$options += array(
'returnDocument' => self::RETURN_DOCUMENT_BEFORE,
'upsert' => false,
);
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw new InvalidArgumentTypeException('"projection" option', $options['projection'], 'array or object');
}
if ( ! is_integer($options['returnDocument'])) {
throw new InvalidArgumentTypeException('"returnDocument" option', $options['returnDocument'], 'integer');
}
if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER &&
$options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) {
throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']);
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object');
}
if ( ! is_bool($options['upsert'])) {
throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean');
}
$this->findAndModify = new FindAndModify(
$databaseName,
$collectionName,
array(
'fields' => isset($options['projection']) ? $options['projection'] : null,
'maxTimeMS' => isset($options['maxTimeMS']) ? $options['maxTimeMS'] : null,
'new' => $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER,
'query' => $filter,
'sort' => isset($options['sort']) ? $options['sort'] : null,
'update' => $update,
'upsert' => $options['upsert'],
)
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return object|null
*/
public function execute(Server $server)
{
return $this->findAndModify->execute($server);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Query;
use MongoDB\Driver\Server;
use MongoDB\Exception\RuntimeException;
use MongoDB\Model\CollectionInfoCommandIterator;
use MongoDB\Model\CollectionInfoIterator;
use MongoDB\Model\CollectionInfoLegacyIterator;
/**
* Operation for the listCollections command.
*
* @api
* @see MongoDB\Database::listCollections()
* @see http://docs.mongodb.org/manual/reference/command/listCollections/
*/
class ListCollections implements Executable
{
private static $wireVersionForCommand = 3;
private $databaseName;
private $options;
/**
* Constructs a listCollections command.
*
* Supported options:
*
* * filter (document): Query by which to filter collections.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* @param string $databaseName Database name
* @param array $options Command options
*/
public function __construct($databaseName, array $options = array())
{
if (isset($options['filter']) && ! is_array($options['filter']) && ! is_object($options['filter'])) {
throw new InvalidArgumentTypeException('"filter" option', $options['filter'], 'array or object');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
$this->databaseName = (string) $databaseName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return CollectionInfoIterator
*/
public function execute(Server $server)
{
return \MongoDB\server_supports_feature($server, self::$wireVersionForCommand)
? $this->executeCommand($server)
: $this->executeLegacy($server);
}
/**
* Returns information for all collections in this database using the
* listCollections command.
*
* @param Server $server
* @return CollectionInfoCommandIterator
*/
private function executeCommand(Server $server)
{
$cmd = array('listCollections' => 1);
if ( ! empty($this->options['filter'])) {
$cmd['filter'] = (object) $this->options['filter'];
}
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
$cursor = $server->executeCommand($this->databaseName, new Command($cmd));
$cursor->setTypeMap(array('document' => 'array'));
return new CollectionInfoCommandIterator($cursor);
}
/**
* Returns information for all collections in this database by querying the
* "system.namespaces" collection (MongoDB <3.0).
*
* @param Server $server
* @return CollectionInfoLegacyIterator
* @throws InvalidArgumentException if filter.name is not a string.
*/
private function executeLegacy(Server $server)
{
$filter = empty($this->options['filter']) ? array() : (array) $this->options['filter'];
if (array_key_exists('name', $filter)) {
if ( ! is_string($filter['name'])) {
throw new InvalidArgumentTypeException('filter name for MongoDB <3.0', $filter['name'], 'string');
}
$filter['name'] = $this->databaseName . '.' . $filter['name'];
}
$options = isset($this->options['maxTimeMS'])
? array('modifiers' => array('$maxTimeMS' => $this->options['maxTimeMS']))
: array();
$cursor = $server->executeQuery($this->databaseName . '.system.namespaces', new Query($filter, $options));
$cursor->setTypeMap(array('document' => 'array'));
return new CollectionInfoLegacyIterator($cursor);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\RuntimeException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Model\DatabaseInfoIterator;
use MongoDB\Model\DatabaseInfoLegacyIterator;
/**
* Operation for the ListDatabases command.
*
* @api
* @see MongoDB\Client::listDatabases()
* @see http://docs.mongodb.org/manual/reference/command/ListDatabases/
*/
class ListDatabases implements Executable
{
private $options;
/**
* Constructs a listDatabases command.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* @param array $options Command options
*/
public function __construct(array $options = array())
{
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return DatabaseInfoIterator
*/
public function execute(Server $server)
{
$cmd = array('listDatabases' => 1);
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
$cursor = $server->executeCommand('admin', new Command($cmd));
$cursor->setTypeMap(array('document' => 'array'));
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
if ( ! isset($result['databases']) || ! is_array($result['databases'])) {
throw new UnexpectedValueException('listDatabases command did not return a "databases" array');
}
/* Return an Iterator instead of an array in case listDatabases is
* eventually changed to return a command cursor, like the collection
* and index enumeration commands. This makes the "totalSize" command
* field inaccessible, but users can manually invoke the command if they
* need that value.
*/
return new DatabaseInfoLegacyIterator($result['databases']);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Query;
use MongoDB\Driver\Server;
use MongoDB\Model\IndexInfoIterator;
use MongoDB\Model\IndexInfoIteratorIterator;
/**
* Operation for the listIndexes command.
*
* @api
* @see MongoDB\Collection::listIndexes()
* @see http://docs.mongodb.org/manual/reference/command/listIndexes/
*/
class ListIndexes implements Executable
{
private static $wireVersionForCommand = 3;
private $databaseName;
private $collectionName;
private $options;
/**
* Constructs a listIndexes command.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $options Command options
*/
public function __construct($databaseName, $collectionName, array $options = array())
{
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return IndexInfoIterator
*/
public function execute(Server $server)
{
return \MongoDB\server_supports_feature($server, self::$wireVersionForCommand)
? $this->executeCommand($server)
: $this->executeLegacy($server);
}
/**
* Returns information for all indexes for this collection using the
* listIndexes command.
*
* @param Server $server
* @return IndexInfoIteratorIterator
*/
private function executeCommand(Server $server)
{
$cmd = array('listIndexes' => $this->collectionName);
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
$cursor = $server->executeCommand($this->databaseName, new Command($cmd));
$cursor->setTypeMap(array('document' => 'array'));
return new IndexInfoIteratorIterator($cursor);
}
/**
* Returns information for all indexes for this collection by querying the
* "system.indexes" collection (MongoDB <3.0).
*
* @param Server $server
* @return IndexInfoIteratorIterator
*/
private function executeLegacy(Server $server)
{
$filter = array('ns' => $this->databaseName . '.' . $this->collectionName);
$options = isset($this->options['maxTimeMS'])
? array('modifiers' => array('$maxTimeMS' => $this->options['maxTimeMS']))
: array();
$cursor = $server->executeQuery($this->databaseName . '.system.indexes', new Query($filter, $options));
$cursor->setTypeMap(array('document' => 'array'));
return new IndexInfoIteratorIterator($cursor);
}
}
<?php
namespace MongoDB;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentTypeException;
/**
* Return whether the first key in the document starts with a "$" character.
*
* This is used for differentiating update and replacement documents.
*
* @internal
* @param array|object $document Update or replacement document
* @return boolean
* @throws InvalidArgumentTypeException
*/
function is_first_key_operator($document)
{
if (is_object($document)) {
$document = get_object_vars($document);
}
if ( ! is_array($document)) {
throw new InvalidArgumentTypeException('$document', $document, 'array or object');
}
$firstKey = (string) key($document);
return (isset($firstKey[0]) && $firstKey[0] == '$');
}
/**
* Generate an index name from a key specification.
*
* @internal
* @param array|object $document Document containing fields mapped to values,
* which denote order or an index type
* @return string
* @throws InvalidArgumentTypeException
*/
function generate_index_name($document)
{
if (is_object($document)) {
$document = get_object_vars($document);
}
if ( ! is_array($document)) {
throw new InvalidArgumentTypeException('$document', $document, 'array or object');
}
$name = '';
foreach ($document as $field => $type) {
$name .= ($name != '' ? '_' : '') . $field . '_' . $type;
}
return $name;
}
/**
* Return whether the server supports a particular feature.
*
* @internal
* @param Server $server Server to check
* @param integer $feature Feature constant (i.e. wire protocol version)
* @return boolean
*/
function server_supports_feature(Server $server, $feature)
{
$info = $server->getInfo();
$maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0;
$minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0;
return ($minWireVersion <= $feature && $maxWireVersion >= $feature);
}
......@@ -3,7 +3,6 @@
namespace MongoDB\Tests\Collection\CrudSpec;
use MongoDB\Collection;
use MongoDB\FeatureDetection;
use MongoDB\Driver\ReadPreference;
/**
......@@ -13,6 +12,8 @@ use MongoDB\Driver\ReadPreference;
*/
class AggregateFunctionalTest extends FunctionalTestCase
{
private static $wireVersionForOutOperator = 2;
public function setUp()
{
parent::setUp();
......@@ -36,14 +37,14 @@ class AggregateFunctionalTest extends FunctionalTestCase
);
// Use iterator_to_array() here since aggregate() may return an ArrayIterator
$this->assertSame($expected, iterator_to_array($cursor));
$this->assertEquals($expected, iterator_to_array($cursor));
}
public function testAggregateWithOut()
{
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! FeatureDetection::isSupported($server, FeatureDetection::API_AGGREGATE_CURSOR)) {
if ( ! \MongoDB\server_supports_feature($server, self::$wireVersionForOutOperator)) {
$this->markTestSkipped('$out aggregation pipeline operator is not supported');
}
......@@ -63,7 +64,7 @@ class AggregateFunctionalTest extends FunctionalTestCase
array('_id' => 3, 'x' => 33),
);
$this->assertSame($expected, $outputCollection->find()->toArray());
$this->assertEquals($expected, $outputCollection->find()->toArray());
// Manually clean up our output collection
$this->dropCollectionIfItExists($outputCollection);
......
......@@ -25,7 +25,7 @@ class FindOneAndDeleteFunctionalTest extends FunctionalTestCase
);
$document = $this->collection->findOneAndDelete($filter, $options);
$this->assertSame(array('x' => 22), $document);
$this->assertEquals((object) array('x' => 22), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......@@ -44,7 +44,7 @@ class FindOneAndDeleteFunctionalTest extends FunctionalTestCase
);
$document = $this->collection->findOneAndDelete($filter, $options);
$this->assertSame(array('x' => 22), $document);
$this->assertEquals((object) array('x' => 22), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......
......@@ -3,6 +3,7 @@
namespace MongoDB\Tests\Collection\CrudSpec;
use MongoDB\Collection;
use MongoDB\Operation\FindOneAndReplace;
/**
* CRUD spec functional tests for findOneAndReplace().
......@@ -28,7 +29,7 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
);
$document = $this->collection->findOneAndReplace($filter, $replacement, $options);
$this->assertSame(array('x' => 22), $document);
$this->assertEquals((object) array('x' => 22), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......@@ -46,11 +47,11 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndReplace($filter, $replacement, $options);
$this->assertSame(array('x' => 32), $document);
$this->assertEquals((object) array('x' => 32), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......@@ -71,7 +72,7 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
);
$document = $this->collection->findOneAndReplace($filter, $replacement, $options);
$this->assertSame(array('x' => 22), $document);
$this->assertEquals((object) array('x' => 22), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......@@ -89,11 +90,11 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndReplace($filter, $replacement, $options);
$this->assertSame(array('x' => 32), $document);
$this->assertEquals((object) array('x' => 32), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......@@ -156,7 +157,7 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndReplace($filter, $replacement, $options);
......@@ -179,12 +180,12 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER,
'upsert' => true,
);
$document = $this->collection->findOneAndReplace($filter, $replacement, $options);
$this->assertSame(array('x' => 44), $document);
$this->assertEquals((object) array('x' => 44), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......
......@@ -3,6 +3,7 @@
namespace MongoDB\Tests\Collection\CrudSpec;
use MongoDB\Collection;
use MongoDB\Operation\FindOneAndUpdate;
/**
* CRUD spec functional tests for findOneAndUpdate().
......@@ -28,7 +29,7 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
);
$document = $this->collection->findOneAndUpdate($filter, $update, $options);
$this->assertSame(array('x' => 22), $document);
$this->assertEquals((object) array('x' => 22), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......@@ -46,11 +47,11 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndUpdate($filter, $update, $options);
$this->assertSame(array('x' => 23), $document);
$this->assertEquals((object) array('x' => 23), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......@@ -71,7 +72,7 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
);
$document = $this->collection->findOneAndUpdate($filter, $update, $options);
$this->assertSame(array('x' => 22), $document);
$this->assertEquals((object) array('x' => 22), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......@@ -89,11 +90,11 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndUpdate($filter, $update, $options);
$this->assertSame(array('x' => 23), $document);
$this->assertEquals((object) array('x' => 23), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......@@ -155,7 +156,7 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndUpdate($filter, $update, $options);
......@@ -177,12 +178,12 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
'upsert' => true,
);
$document = $this->collection->findOneAndUpdate($filter, $update, $options);
$this->assertSame(array('x' => 1), $document);
$this->assertEquals((object) array('x' => 1), $document);
$expected = array(
array('_id' => 1, 'x' => 11),
......
......@@ -85,7 +85,10 @@ class IndexManagementFunctionalTest extends FunctionalTestCase
});
}
public function testCreateIndexesWithEmptyInputIsNop()
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
*/
public function testCreateIndexesRequiresAtLeastOneIndex()
{
$this->assertSame(array(), $this->collection->createIndexes(array()));
}
......
......@@ -21,15 +21,19 @@ abstract class FunctionalTestCase extends TestCase
list($databaseName, $collectionName) = explode('.', $namespace, 2);
$cursor = $this->manager->executeCommand($databaseName, new Command(array('count' => $collectionName)));
$cursor->setTypeMap(array('document' => 'array'));
$document = current($cursor->toArray());
$this->assertArrayHasKey('n', $document);
$this->assertEquals($count, $document['n']);
}
protected function assertCommandSucceeded(Cursor $cursor)
protected function assertCommandSucceeded($document)
{
$document = current($cursor->toArray());
if (is_object($document)) {
$document = get_object_vars($document);
}
$this->assertArrayHasKey('ok', $document);
$this->assertEquals(1, $document['ok']);
}
......@@ -42,6 +46,7 @@ abstract class FunctionalTestCase extends TestCase
$readPreference ?: new ReadPreference(ReadPreference::RP_PRIMARY)
);
$cursor->setTypeMap(array('document' => 'array'));
$document = current($cursor->toArray());
return $document['version'];
......
......@@ -62,6 +62,10 @@ class PedantryTest extends \PHPUnit_Framework_TestCase
$files = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($srcDir)), '/\.php$/i');
foreach ($files as $file) {
if ($file->getFilename() === 'functions.php') {
continue;
}
$classNames[][] = 'MongoDB\\' . str_replace(DIRECTORY_SEPARATOR, '\\', substr($file->getRealPath(), strlen($srcDir) + 1, -4));
}
......
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