Commit 5b6f54e3 authored by Jeremy Mikola's avatar Jeremy Mikola

Merge pull request #7

parents 71464555 7ba19662
......@@ -10,7 +10,7 @@
{ "name": "Derick Rethans", "email": "github@derickrethans.nl" }
],
"require": {
"ext-mongodb": "*"
"ext-mongodb": ">=0.5.1"
},
"require-dev": {
"fzaninotto/faker": "~1.0"
......
......@@ -7,7 +7,8 @@ use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use ArrayIterator;
use MongoDB\Model\DatabaseInfoIterator;
use MongoDB\Model\DatabaseInfoLegacyIterator;
use stdClass;
use UnexpectedValueException;
......@@ -54,7 +55,7 @@ class Client
* List databases.
*
* @see http://docs.mongodb.org/manual/reference/command/listDatabases/
* @return Traversable
* @return DatabaseInfoIterator
* @throws UnexpectedValueException if the command result is malformed
*/
public function listDatabases()
......@@ -62,24 +63,20 @@ class Client
$command = new Command(array('listDatabases' => 1));
$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');
}
$databases = array_map(
function(stdClass $database) { return (array) $database; },
$result['databases']
);
/* Return a Traversable instead of an array in case listDatabases is
/* 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 ArrayIterator($databases);
return new DatabaseInfoLegacyIterator($result['databases']);
}
/**
......
......@@ -2,13 +2,19 @@
namespace MongoDB;
use MongoDB\Driver\BulkWrite;
use MongoDB\Driver\Command;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\Query;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\BulkWrite;
use MongoDB\Driver\Server;
use MongoDB\Driver\WriteConcern;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedTypeException;
use MongoDB\Model\IndexInfoIterator;
use MongoDB\Model\IndexInfoIteratorIterator;
use MongoDB\Model\IndexInput;
class Collection
{
......@@ -244,34 +250,68 @@ class Collection
}
/**
* Create a single index in the collection.
* Create a single index for the collection.
*
* @see http://docs.mongodb.org/manual/reference/command/createIndexes/
* @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
* @param array|object $keys
* @param array $options
* @see Collection::createIndexes()
* @param array|object $key Document containing fields mapped to values,
* which denote order or an index type
* @param array $options Index options
* @return string The name of the created index
*/
public function createIndex($keys, array $options = array())
public function createIndex($key, array $options = array())
{
// TODO
return current($this->createIndexes(array(array('key' => $key) + $options)));
}
/**
* Create multiple indexes in the collection.
* Create one or more indexes for the collection.
*
* Each element in the $indexes array must have a "key" document, which
* contains fields mapped to an order or type. Other options may follow.
* For example:
*
* TODO: decide if $models should be an array of associative arrays, using
* createIndex()'s parameter names as keys, or tuples, using parameters in
* order (e.g. [keys, options]).
* $indexes = [
* // Create a unique index on the "username" field
* [ 'key' => [ 'username' => 1 ], 'unique' => true ],
* // Create a 2dsphere index on the "loc" field with a custom name
* [ 'key' => [ 'loc' => '2dsphere' ], 'name' => 'geo' ],
* ];
*
* If the "name" option is unspecified, a name will be generated from the
* "key" document.
*
* @see http://docs.mongodb.org/manual/reference/command/createIndexes/
* @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
* @param array $models
* @param array $indexes List of index specifications
* @return string[] The names of the created indexes
* @throws InvalidArgumentException if an index specification is invalid
*/
public function createIndexes(array $models)
public function createIndexes(array $indexes)
{
// TODO
if (empty($indexes)) {
return array();
}
foreach ($indexes as $i => $index) {
if ( ! is_array($index)) {
throw new UnexpectedTypeException($index, 'array');
}
if ( ! isset($index['ns'])) {
$index['ns'] = $this->ns;
}
$indexes[$i] = new IndexInput($index);
}
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
$server = $this->manager->selectServer($readPreference);
return (FeatureDetection::isSupported($server, FeatureDetection::API_CREATEINDEXES_CMD))
? $this->createIndexesCommand($server, $indexes)
: $this->createIndexesLegacy($server, $indexes);
}
/**
......@@ -354,11 +394,24 @@ class Collection
* @see http://docs.mongodb.org/manual/reference/method/db.collection.dropIndex/
* @param string $indexName
* @return Cursor
* @throws InvalidArgumentException if "*" is specified
* @throws InvalidArgumentException if $indexName is an empty string or "*"
*/
public function dropIndex($indexName)
{
// TODO
$indexName = (string) $indexName;
if ($indexName === '') {
throw new InvalidArgumentException('Index name cannot be empty');
}
if ($indexName === '*') {
throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes');
}
$command = new Command(array('dropIndexes' => $this->collname, 'index' => $indexName));
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
return $this->manager->executeCommand($this->dbname, $command, $readPreference);
}
/**
......@@ -370,7 +423,10 @@ class Collection
*/
public function dropIndexes()
{
// TODO
$command = new Command(array('dropIndexes' => $this->collname, 'index' => '*'));
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
return $this->manager->executeCommand($this->dbname, $command, $readPreference);
}
/**
......@@ -949,15 +1005,20 @@ class Collection
}
/**
* Returns information for all indexes in the collection.
* Returns information for all indexes for the collection.
*
* @see http://docs.mongodb.org/manual/reference/command/listIndexes/
* @see http://docs.mongodb.org/manual/reference/method/db.collection.getIndexes/
* @return Cursor
* @return IndexInfoIterator
*/
public function listIndexes()
{
// TODO
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
$server = $this->manager->selectServer($readPreference);
return (FeatureDetection::isSupported($server, FeatureDetection::API_LISTINDEXES_CMD))
? $this->listIndexesCommand($server)
: $this->listIndexesLegacy($server);
}
/**
......@@ -1136,4 +1197,78 @@ class Collection
$bulk->update($filter, $update, $options);
return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
}
/**
* Create one or more indexes for the collection using the createIndexes
* command.
*
* @param Server $server
* @param IndexInput[] $indexes
* @return string[] The names of the created indexes
*/
private function createIndexesCommand(Server $server, array $indexes)
{
$command = new Command(array(
'createIndexes' => $this->collname,
'indexes' => $indexes,
));
$server->executeCommand($this->dbname, $command);
return array_map(function(IndexInput $index) { return (string) $index; }, $indexes);
}
/**
* Create one or more indexes for the collection by inserting into the
* "system.indexes" collection (MongoDB <2.6).
*
* @param Server $server
* @param IndexInput[] $indexes
* @return string[] The names of the created indexes
*/
private function createIndexesLegacy(Server $server, array $indexes)
{
$bulk = new BulkWrite(true);
foreach ($indexes as $index) {
// TODO: Remove this once PHPC-274 is resolved (see: PHPLIB-87)
$bulk->insert($index->bsonSerialize());
}
$server->executeBulkWrite($this->dbname . '.system.indexes', $bulk);
return array_map(function(IndexInput $index) { return (string) $index; }, $indexes);
}
/**
* Returns information for all indexes for this collection using the
* listIndexes command.
*
* @see http://docs.mongodb.org/manual/reference/command/listIndexes/
* @param Server $server
* @return IndexInfoIteratorIterator
*/
private function listIndexesCommand(Server $server)
{
$command = new Command(array('listIndexes' => $this->collname));
$cursor = $server->executeCommand($this->dbname, $command);
$cursor->setTypeMap(array('document' => 'array'));
return new IndexInfoIteratorIterator($cursor);
}
/**
* Returns information for all indexes for this collection by querying the
* "system.indexes" collection (MongoDB <2.8).
*
* @param Server $server
* @return IndexInfoIteratorIterator
*/
private function listIndexesLegacy(Server $server)
{
$query = new Query(array('ns' => $this->ns));
$cursor = $server->executeQuery($this->dbname . '.system.indexes', $query);
$cursor->setTypeMap(array('document' => 'array'));
return new IndexInfoIteratorIterator($cursor);
}
}
......@@ -101,10 +101,7 @@ class Database
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
$server = $this->manager->selectServer($readPreference);
$serverInfo = $server->getInfo();
$maxWireVersion = isset($serverInfo['maxWireVersion']) ? $serverInfo['maxWireVersion'] : 0;
return ($maxWireVersion >= 3)
return (FeatureDetection::isSupported($server, FeatureDetection::API_LISTCOLLECTIONS_CMD))
? $this->listCollectionsCommand($server, $options)
: $this->listCollectionsLegacy($server, $options);
}
......@@ -141,13 +138,14 @@ class Database
{
$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).
* Returns information for all collections in this database by querying the
* "system.namespaces" collection (MongoDB <2.8).
*
* @param Server $server
* @param array $options
......@@ -177,6 +175,7 @@ class Database
$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 BadMethodCallException extends \BadMethodCallException implements Exception
{
}
<?php
namespace MongoDB\Exception;
interface Exception
{
}
<?php
namespace MongoDB\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements Exception
{
}
<?php
namespace MongoDB\Exception;
class UnexpectedTypeException extends InvalidArgumentException
{
public function __construct($value, $expectedType)
{
parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $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;
/**
* 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);
}
}
......@@ -2,10 +2,20 @@
namespace MongoDB\Model;
/**
* Collection information model class.
*
* This class models the collection information returned by the listCollections
* command or, for legacy servers, queries on the "system.namespaces"
* collection. It provides methods to access options for the collection.
*
* @api
* @see MongoDB\Database::listCollections()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst
*/
class CollectionInfo
{
private $name;
private $options;
private $info;
/**
* Constructor.
......@@ -14,8 +24,7 @@ class CollectionInfo
*/
public function __construct(array $info)
{
$this->name = (string) $info['name'];
$this->options = isset($info['options']) ? (array) $info['options'] : array();
$this->info = $info;
}
/**
......@@ -25,7 +34,7 @@ class CollectionInfo
*/
public function getName()
{
return $this->name;
return (string) $this->info['name'];
}
/**
......@@ -35,7 +44,7 @@ class CollectionInfo
*/
public function getOptions()
{
return $this->options;
return isset($this->info['options']) ? (array) $this->info['options'] : array();
}
/**
......@@ -45,7 +54,7 @@ class CollectionInfo
*/
public function isCapped()
{
return isset($this->options['capped']) ? (boolean) $this->options['capped'] : false;
return ! empty($this->info['options']['capped']);
}
/**
......@@ -55,7 +64,7 @@ class CollectionInfo
*/
public function getCappedMax()
{
return isset($this->options['max']) ? (integer) $this->options['max'] : null;
return isset($this->info['options']['max']) ? (integer) $this->info['options']['max'] : null;
}
/**
......@@ -65,6 +74,6 @@ class CollectionInfo
*/
public function getCappedSize()
{
return isset($this->options['size']) ? (integer) $this->options['size'] : null;
return isset($this->info['options']['size']) ? (integer) $this->info['options']['size'] : null;
}
}
......@@ -4,11 +4,24 @@ namespace MongoDB\Model;
use IteratorIterator;
/**
* CollectionInfoIterator for listCollections command results.
*
* This iterator may be used to wrap a Cursor returned by the listCollections
* command.
*
* @internal
* @see MongoDB\Database::listCollections()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst
* @see http://docs.mongodb.org/manual/reference/command/listCollections/
*/
class CollectionInfoCommandIterator extends IteratorIterator implements CollectionInfoIterator
{
/**
* Return the current element as a CollectionInfo instance.
*
* @see CollectionInfoIterator::current()
* @see http://php.net/iterator.current
* @return CollectionInfo
*/
public function current()
......
......@@ -4,6 +4,14 @@ namespace MongoDB\Model;
use Iterator;
/**
* CollectionInfoIterator interface.
*
* This iterator is used for enumerating collections in a database.
*
* @api
* @see MongoDB\Database::listCollections()
*/
interface CollectionInfoIterator extends Iterator
{
/**
......
......@@ -7,6 +7,20 @@ use Iterator;
use IteratorIterator;
use Traversable;
/**
* CollectionInfoIterator for legacy "system.namespaces" query results.
*
* This iterator may be used to wrap a Cursor returned for queries on the
* "system.namespaces" collection. It includes logic to filter out internal
* collections and modify the collection name to be consistent with results from
* the listCollections command.
*
* @internal
* @see MongoDB\Database::listCollections()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst
* @see http://docs.mongodb.org/manual/reference/command/listCollections/
* @see http://docs.mongodb.org/manual/reference/system-collections/
*/
class CollectionInfoLegacyIterator extends FilterIterator implements CollectionInfoIterator
{
/**
......@@ -29,6 +43,8 @@ class CollectionInfoLegacyIterator extends FilterIterator implements CollectionI
/**
* Return the current element as a CollectionInfo instance.
*
* @see CollectionInfoIterator::current()
* @see http://php.net/iterator.current
* @return CollectionInfo
*/
public function current()
......@@ -48,7 +64,7 @@ class CollectionInfoLegacyIterator extends FilterIterator implements CollectionI
/**
* Filter out internal or invalid collections.
*
* @see http://php.net/manual/en/filteriterator.accept.php
* @see http://php.net/filteriterator.accept
* @return boolean
*/
public function accept()
......
<?php
namespace MongoDB\Model;
/**
* Database information model class.
*
* This class models the database information returned by the listDatabases
* command. It provides methods to access common database properties.
*
* @api
* @see MongoDB\Client::listDatabases()
* @see http://docs.mongodb.org/manual/reference/command/listDatabases/
*/
class DatabaseInfo
{
private $info;
/**
* Constructor.
*
* @param array $info Database info
*/
public function __construct(array $info)
{
$this->info = $info;
}
/**
* Return the database name.
*
* @return string
*/
public function getName()
{
return (string) $this->info['name'];
}
/**
* Return the databases size on disk (in bytes).
*
* @return integer
*/
public function getSizeOnDisk()
{
return (integer) $this->info['sizeOnDisk'];
}
/**
* Return whether the database is empty.
*
* @return boolean
*/
public function isEmpty()
{
return (boolean) $this->info['empty'];
}
}
<?php
namespace MongoDB\Model;
use Iterator;
/**
* DatabaseInfoIterator interface.
*
* This iterator is used for enumerating databases on a server.
*
* @api
* @see MongoDB\Client::listDatabases()
*/
interface DatabaseInfoIterator extends Iterator
{
/**
* Return the current element as a DatabaseInfo instance.
*
* @return DatabaseInfo
*/
public function current();
}
<?php
namespace MongoDB\Model;
/**
* DatabaseInfoIterator for inline listDatabases command results.
*
* This iterator may be used to wrap the array returned within the listDatabases
* command's single-document result.
*
* @internal
* @see MongoDB\Client::listDatabases()
* @see http://docs.mongodb.org/manual/reference/command/listDatabases/
*/
class DatabaseInfoLegacyIterator implements DatabaseInfoIterator
{
private $databases;
private $index = 0;
/**
* Constructor.
*
* @param array $databases
*/
public function __construct(array $databases)
{
$this->databases = $databases;
}
/**
* Return the current element as a DatabaseInfo instance.
*
* @see DatabaseInfoIterator::current()
* @see http://php.net/iterator.current
* @return DatabaseInfo
*/
public function current()
{
return new DatabaseInfo(current($this->databases));
}
/**
* Return the key of the current element.
*
* @see http://php.net/iterator.key
* @return integer
*/
public function key()
{
return key($this->databases);
}
/**
* Move forward to next element.
*
* @see http://php.net/iterator.next
*/
public function next()
{
next($this->databases);
}
/**
* Rewind the Iterator to the first element.
*
* @see http://php.net/iterator.rewind
*/
public function rewind()
{
reset($this->databases);
}
/**
* Checks if current position is valid.
*
* @see http://php.net/iterator.valid
* @return boolean
*/
public function valid()
{
return key($this->databases) !== null;
}
}
<?php
namespace MongoDB\Model;
use MongoDB\Exception\BadMethodCallException;
use ArrayAccess;
/**
* Index information model class.
*
* This class models the index information returned by the listIndexes command
* or, for legacy servers, queries on the "system.indexes" collection. It
* provides methods to access common index options, and allows access to other
* options through the ArrayAccess interface (write methods are not supported).
* For information on keys and index options, see the referenced
* db.collection.createIndex() documentation.
*
* @api
* @see MongoDB\Collection::listIndexes()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
* @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
*/
class IndexInfo implements ArrayAccess
{
private $info;
/**
* Constructor.
*
* @param array $info Index info
*/
public function __construct(array $info)
{
$this->info = $info;
}
/**
* Return the index key(s).
*
* @return array
*/
public function getKeys()
{
return (array) $this->info['key'];
}
/**
* Return the index name.
*
* @return string
*/
public function getName()
{
return (string) $this->info['name'];
}
/**
* Return the index namespace (e.g. "db.collection").
*
* @return string
*/
public function getNamespace()
{
return (string) $this->info['ns'];
}
/**
* Return the index version.
*
* @return integer
*/
public function getVersion()
{
return (integer) $this->info['v'];
}
/**
* Return whether this is a sparse index.
*
* @see http://docs.mongodb.org/manual/core/index-sparse/
* @return boolean
*/
public function isSparse()
{
return ! empty($this->info['sparse']);
}
/**
* Return whether this is a TTL index.
*
* @see http://docs.mongodb.org/manual/core/index-ttl/
* @return boolean
*/
public function isTtl()
{
return array_key_exists('expireAfterSeconds', $this->info);
}
/**
* Return whether this is a unique index.
*
* @see http://docs.mongodb.org/manual/core/index-unique/
* @return boolean
*/
public function isUnique()
{
return ! empty($this->info['unique']);
}
/**
* Check whether a field exists in the index information.
*
* @see http://php.net/arrayaccess.offsetexists
* @param mixed $key
* @return boolean
*/
public function offsetExists($key)
{
return array_key_exists($key, $this->info);
}
/**
* Return the field's value from the index information.
*
* This method satisfies the Enumerating Indexes specification's requirement
* that index fields be made accessible under their original names. It may
* also be used to access fields that do not have a helper method.
*
* @see http://php.net/arrayaccess.offsetget
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst#getting-full-index-information
* @param mixed $key
* @return mixed
*/
public function offsetGet($key)
{
return $this->data[$key];
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetset
* @throws BadMethodCallException IndexInfo is immutable
*/
public function offsetSet($key, $value)
{
throw new BadMethodCallException('IndexInfo is immutable');
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetunset
* @throws BadMethodCallException IndexInfo is immutable
*/
public function offsetUnset($key)
{
throw new BadMethodCallException('IndexInfo is immutable');
}
}
<?php
namespace MongoDB\Model;
use Iterator;
/**
* IndexInfoIterator interface.
*
* This iterator is used for enumerating indexes in a collection.
*
* @api
* @see MongoDB\Collection::listIndexes()
*/
interface IndexInfoIterator extends Iterator
{
/**
* Return the current element as a IndexInfo instance.
*
* @return IndexInfo
*/
public function current();
}
<?php
namespace MongoDB\Model;
use IteratorIterator;
/**
* IndexInfoIterator for both listIndexes command and legacy query results.
*
* This common iterator may be used to wrap a Cursor returned by both the
* listIndexes command and, for legacy servers, queries on the "system.indexes"
* collection.
*
* @internal
* @see MongoDB\Collection::listIndexes()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
* @see http://docs.mongodb.org/manual/reference/command/listIndexes/
* @see http://docs.mongodb.org/manual/reference/system-collections/
*/
class IndexInfoIteratorIterator extends IteratorIterator implements IndexInfoIterator
{
/**
* Return the current element as an IndexInfo instance.
*
* @see IndexInfoIterator::current()
* @see http://php.net/iterator.current
* @return IndexInfo
*/
public function current()
{
return new IndexInfo(parent::current());
}
}
<?php
namespace MongoDB\Model;
use BSON\Serializable;
/**
* Index input model class.
*
* This class is used to validate user input for index creation.
*
* @internal
* @see MongoDB\Collection::createIndexes()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
* @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
*/
class IndexInput implements Serializable
{
private $index;
/**
* Constructor.
*
* @param array $index Index specification
*/
public function __construct(array $index)
{
if ( ! isset($index['key'])) {
throw new InvalidArgumentException('Required "key" document is missing from index specification');
}
if ( ! is_array($index['key']) && ! is_object($index['key'])) {
throw new UnexpectedTypeException($index['key'], 'array or object');
}
foreach ($index['key'] as $order) {
if ( ! is_int($order) && ! is_float($order) && ! is_string($order)) {
throw new UnexpectedTypeException($order, 'numeric or string');
}
}
if ( ! isset($index['ns'])) {
throw new InvalidArgumentException('Required "ns" option is missing from index specification');
}
if ( ! is_string($index['ns'])) {
throw new UnexpectedTypeException($index['ns'], 'string');
}
if ( ! isset($index['name'])) {
$index['name'] = $this->generateName($index['key']);
}
if ( ! is_string($index['name'])) {
throw new UnexpectedTypeException($index['name'], 'string');
}
$this->index = $index;
}
/**
* Serialize the index information to BSON for index creation.
*
* @see MongoDB\Collection::createIndexes()
* @see http://php.net/bson-serializable.bsonserialize
*/
public function bsonSerialize()
{
return $this->index;
}
/**
* Return the index name.
*
* @param string
*/
public function __toString()
{
return $this->index['name'];
}
/**
* 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;
}
}
......@@ -3,19 +3,30 @@
namespace MongoDB\Tests;
use MongoDB\Client;
use MongoDB\Driver\Command;
use MongoDB\Model\DatabaseInfo;
/**
* Functional tests for the Client class.
*/
class ClientFunctionalTest extends FunctionalTestCase
{
private $client;
public function setUp()
{
parent::setUp();
$this->client = new Client($this->getUri());
$this->client->dropDatabase($this->getDatabaseName());
}
public function testDropDatabase()
{
$writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1));
$this->assertEquals(1, $writeResult->getInsertedCount());
$client = new Client($this->getUri());
$commandResult = $client->dropDatabase($this->getDatabaseName());
$commandResult = $this->client->dropDatabase($this->getDatabaseName());
$this->assertCommandSucceeded($commandResult);
$this->assertCollectionCount($this->getNamespace(), 0);
}
......@@ -25,22 +36,52 @@ class ClientFunctionalTest extends FunctionalTestCase
$writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1));
$this->assertEquals(1, $writeResult->getInsertedCount());
$client = new Client($this->getUri());
$databases = $client->listDatabases();
$databases = $this->client->listDatabases();
$this->assertInstanceOf('Traversable', $databases);
$this->assertInstanceOf('MongoDB\Model\DatabaseInfoIterator', $databases);
foreach ($databases as $database) {
$this->assertInstanceOf('MongoDB\Model\DatabaseInfo', $database);
}
$that = $this;
$this->assertDatabaseExists($this->getDatabaseName(), function(DatabaseInfo $info) use ($that) {
$that->assertFalse($info->isEmpty());
$that->assertGreaterThan(0, $info->getSizeOnDisk());
});
}
/**
* Asserts that a database with the given name exists on the server.
*
* An optional $callback may be provided, which should take a DatabaseInfo
* argument as its first and only parameter. If a DatabaseInfo matching
* the given name is found, it will be passed to the callback, which may
* perform additional assertions.
*
* @param callable $callback
*/
private function assertDatabaseExists($databaseName, $callback = null)
{
if ($callback !== null && ! is_callable($callback)) {
throw new InvalidArgumentException('$callback is not a callable');
}
$databases = $this->client->listDatabases();
$foundDatabase = null;
foreach ($databases as $database) {
if ($database['name'] === $this->getDatabaseName()) {
if ($database->getName() === $databaseName) {
$foundDatabase = $database;
break;
}
}
$this->assertNotNull($foundDatabase, 'Found test database in list of databases');
$this->assertFalse($foundDatabase['empty'], 'Test database is not empty');
$this->assertGreaterThan(0, $foundDatabase['sizeOnDisk'], 'Test database takes up disk space');
$this->assertNotNull($foundDatabase, sprintf('Found %s database on the server', $databaseName));
if ($callback !== null) {
call_user_func($callback, $foundDatabase);
}
}
}
......@@ -4,6 +4,8 @@ namespace MongoDB\Tests;
use MongoDB\Collection;
use MongoDB\Driver\Manager;
use MongoDB\Model\IndexInfo;
use InvalidArgumentException;
class CollectionFunctionalTest extends FunctionalTestCase
{
......@@ -56,4 +58,180 @@ class CollectionFunctionalTest extends FunctionalTestCase
}
$this->assertEquals(0, $n);
}
public function testCreateIndex()
{
$that = $this;
$this->assertSame('x_1', $this->collection->createIndex(array('x' => 1), array('sparse' => true, 'unique' => true)));
$this->assertIndexExists('x_1', function(IndexInfo $info) use ($that) {
$that->assertTrue($info->isSparse());
$that->assertTrue($info->isUnique());
$that->assertFalse($info->isTtl());
});
$this->assertSame('y_-1_z_1', $this->collection->createIndex(array('y' => -1, 'z' => 1)));
$this->assertIndexExists('y_-1_z_1', function(IndexInfo $info) use ($that) {
$that->assertFalse($info->isSparse());
$that->assertFalse($info->isUnique());
$that->assertFalse($info->isTtl());
});
$this->assertSame('g_2dsphere_z_1', $this->collection->createIndex(array('g' => '2dsphere', 'z' => 1)));
$this->assertIndexExists('g_2dsphere_z_1', function(IndexInfo $info) use ($that) {
$that->assertFalse($info->isSparse());
$that->assertFalse($info->isUnique());
$that->assertFalse($info->isTtl());
});
$this->assertSame('my_ttl', $this->collection->createIndex(array('t' => 1), array('expireAfterSeconds' => 0, 'name' => 'my_ttl')));
$this->assertIndexExists('my_ttl', function(IndexInfo $info) use ($that) {
$that->assertFalse($info->isSparse());
$that->assertFalse($info->isUnique());
$that->assertTrue($info->isTtl());
});
}
public function testCreateIndexes()
{
$that = $this;
$expectedNames = array('x_1', 'y_-1_z_1', 'g_2dsphere_z_1', 'my_ttl');
$indexes = array(
array('key' => array('x' => 1), 'sparse' => true, 'unique' => true),
array('key' => array('y' => -1, 'z' => 1)),
array('key' => array('g' => '2dsphere', 'z' => 1)),
array('key' => array('t' => 1), 'expireAfterSeconds' => 0, 'name' => 'my_ttl'),
);
$this->assertSame($expectedNames, $this->collection->createIndexes($indexes));
$this->assertIndexExists('x_1', function(IndexInfo $info) use ($that) {
$that->assertTrue($info->isSparse());
$that->assertTrue($info->isUnique());
$that->assertFalse($info->isTtl());
});
$this->assertIndexExists('y_-1_z_1', function(IndexInfo $info) use ($that) {
$that->assertFalse($info->isSparse());
$that->assertFalse($info->isUnique());
$that->assertFalse($info->isTtl());
});
$this->assertIndexExists('g_2dsphere_z_1', function(IndexInfo $info) use ($that) {
$that->assertFalse($info->isSparse());
$that->assertFalse($info->isUnique());
$that->assertFalse($info->isTtl());
});
$this->assertIndexExists('my_ttl', function(IndexInfo $info) use ($that) {
$that->assertFalse($info->isSparse());
$that->assertFalse($info->isUnique());
$that->assertTrue($info->isTtl());
});
}
public function testCreateIndexesWithEmptyInputIsNop()
{
$this->assertSame(array(), $this->collection->createIndexes(array()));
}
public function testDropIndex()
{
$this->assertSame('x_1', $this->collection->createIndex(array('x' => 1)));
$this->assertIndexExists('x_1');
$this->assertCommandSucceeded($this->collection->dropIndex('x_1'));
foreach ($this->collection->listIndexes() as $index) {
if ($index->getName() === 'x_1') {
$this->fail('The "x_1" index should have been deleted');
}
}
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
*/
public function testDropIndexShouldNotAllowEmptyIndexName()
{
$this->assertSame('x_1', $this->collection->createIndex(array('x' => 1)));
$this->assertIndexExists('x_1');
$this->collection->dropIndex('');
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
*/
public function testDropIndexShouldNotAllowWildcardCharacter()
{
$this->assertSame('x_1', $this->collection->createIndex(array('x' => 1)));
$this->assertIndexExists('x_1');
$this->collection->dropIndex('*');
}
public function testDropIndexes()
{
$this->assertSame('x_1', $this->collection->createIndex(array('x' => 1)));
$this->assertSame('y_1', $this->collection->createIndex(array('y' => 1)));
$this->assertIndexExists('x_1');
$this->assertIndexExists('y_1');
$this->assertCommandSucceeded($this->collection->dropIndexes());
foreach ($this->collection->listIndexes() as $index) {
if ($index->getName() === 'x_1') {
$this->fail('The "x_1" index should have been deleted');
}
if ($index->getName() === 'y_1') {
$this->fail('The "y_1" index should have been deleted');
}
}
}
public function testListIndexes()
{
$this->assertSame('x_1', $this->collection->createIndex(array('x' => 1)));
$indexes = $this->collection->listIndexes();
$this->assertInstanceOf('MongoDB\Model\IndexInfoIterator', $indexes);
foreach ($indexes as $index) {
$this->assertInstanceOf('MongoDB\Model\IndexInfo', $index);
}
}
/**
* Asserts that an index with the given name exists for the collection.
*
* An optional $callback may be provided, which should take an IndexInfo
* argument as its first and only parameter. If an IndexInfo matching the
* given name is found, it will be passed to the callback, which may perform
* additional assertions.
*
* @param callable $callback
*/
private function assertIndexExists($indexName, $callback = null)
{
if ($callback !== null && ! is_callable($callback)) {
throw new InvalidArgumentException('$callback is not a callable');
}
$indexes = $this->collection->listIndexes();
$foundIndex = null;
foreach ($indexes as $index) {
if ($index->getName() === $indexName) {
$foundIndex = $index;
break;
}
}
$this->assertNotNull($foundIndex, sprintf('Found %s index for the collection', $indexName));
if ($callback !== null) {
call_user_func($callback, $foundIndex);
}
}
}
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