From b573271080a0bbbad1649506064398a199f14a08 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 13 Apr 2015 17:43:42 -0400 Subject: [PATCH 01/21] Link to canonical documentation URL --- src/Model/CollectionInfoLegacyIterator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/CollectionInfoLegacyIterator.php b/src/Model/CollectionInfoLegacyIterator.php index 4426def61..7f163fa91 100644 --- a/src/Model/CollectionInfoLegacyIterator.php +++ b/src/Model/CollectionInfoLegacyIterator.php @@ -48,7 +48,7 @@ public function current() /** * 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() From 793bb8bf758d14af948a88c045402072c2b0a237 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 13 Apr 2015 17:47:16 -0400 Subject: [PATCH 02/21] PHPLIB-72: Use model class when listing databases --- src/Client.php | 9 +-- src/Model/DatabaseInfo.php | 52 +++++++++++++++++ src/Model/DatabaseInfoIterator.php | 15 +++++ src/Model/DatabaseInfoLegacyIterator.php | 73 ++++++++++++++++++++++++ tests/ClientFunctionalTest.php | 59 ++++++++++++++++--- 5 files changed, 195 insertions(+), 13 deletions(-) create mode 100644 src/Model/DatabaseInfo.php create mode 100644 src/Model/DatabaseInfoIterator.php create mode 100644 src/Model/DatabaseInfoLegacyIterator.php diff --git a/src/Client.php b/src/Client.php index f83f0b87f..3654d0cc5 100644 --- a/src/Client.php +++ b/src/Client.php @@ -7,7 +7,8 @@ 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 @@ public function dropDatabase($databaseName) * 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() @@ -73,13 +74,13 @@ 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($databases); } /** diff --git a/src/Model/DatabaseInfo.php b/src/Model/DatabaseInfo.php new file mode 100644 index 000000000..2f6038f25 --- /dev/null +++ b/src/Model/DatabaseInfo.php @@ -0,0 +1,52 @@ +name = (string) $info['name']; + $this->empty = (boolean) $info['empty']; + $this->sizeOnDisk = (integer) $info['sizeOnDisk']; + } + + /** + * Return the database name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return the databases size on disk (in bytes). + * + * @return integer + */ + public function getSizeOnDisk() + { + return $this->sizeOnDisk; + } + + /** + * Return whether the database is empty. + * + * @return boolean + */ + public function isEmpty() + { + return $this->empty; + } +} diff --git a/src/Model/DatabaseInfoIterator.php b/src/Model/DatabaseInfoIterator.php new file mode 100644 index 000000000..04b5b7652 --- /dev/null +++ b/src/Model/DatabaseInfoIterator.php @@ -0,0 +1,15 @@ +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; + } +} diff --git a/tests/ClientFunctionalTest.php b/tests/ClientFunctionalTest.php index 06b67f169..a7f3fe4ee 100644 --- a/tests/ClientFunctionalTest.php +++ b/tests/ClientFunctionalTest.php @@ -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 @@ public function testListDatabases() $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('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'); + } - $this->assertInstanceOf('Traversable', $databases); + $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); + } } } From 7b6b8e0acda38d6ff347283019b5245ae7d0e115 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 13 Apr 2015 17:47:48 -0400 Subject: [PATCH 03/21] Link to interface docs from implementations --- src/Model/CollectionInfoCommandIterator.php | 2 ++ src/Model/CollectionInfoLegacyIterator.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Model/CollectionInfoCommandIterator.php b/src/Model/CollectionInfoCommandIterator.php index ce0c28737..58313035b 100644 --- a/src/Model/CollectionInfoCommandIterator.php +++ b/src/Model/CollectionInfoCommandIterator.php @@ -9,6 +9,8 @@ class CollectionInfoCommandIterator extends IteratorIterator implements Collecti /** * Return the current element as a CollectionInfo instance. * + * @see CollectionInfoIterator::current() + * @see http://php.net/iterator.current * @return CollectionInfo */ public function current() diff --git a/src/Model/CollectionInfoLegacyIterator.php b/src/Model/CollectionInfoLegacyIterator.php index 7f163fa91..8f55eb672 100644 --- a/src/Model/CollectionInfoLegacyIterator.php +++ b/src/Model/CollectionInfoLegacyIterator.php @@ -29,6 +29,8 @@ public function __construct(Traversable $iterator) /** * Return the current element as a CollectionInfo instance. * + * @see CollectionInfoIterator::current() + * @see http://php.net/iterator.current * @return CollectionInfo */ public function current() From 6a17ac90556f74be062482ca7940a582da057acf Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 21 Apr 2015 18:04:51 -0400 Subject: [PATCH 04/21] PHPLIB-69: Index drop methods --- src/Collection.php | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Collection.php b/src/Collection.php index 82bc054fa..a3aa8eb3e 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -9,6 +9,7 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Driver\BulkWrite; use MongoDB\Driver\WriteConcern; +use InvalidArgumentException; class Collection { @@ -354,11 +355,21 @@ public function drop() * @see http://docs.mongodb.org/manual/reference/method/db.collection.dropIndex/ * @param string $indexName * @return Cursor - * @throws InvalidArgumentException if "*" is specified + * @throws InvalidArgumentException if "*" is specified, since dropIndexes() + * should be used to drop multiple indexes */ public function dropIndex($indexName) { - // TODO + $indexName = (string) $indexName; + + 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 +381,10 @@ public function dropIndex($indexName) */ 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); } /** From 982889cb575e94271bfdaced7d1da2b688ebebe7 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 22 Apr 2015 22:20:42 -0400 Subject: [PATCH 05/21] Fix word wrap in documentation --- src/Database.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database.php b/src/Database.php index 1da5d90db..8d49de0b6 100644 --- a/src/Database.php +++ b/src/Database.php @@ -146,8 +146,8 @@ private function listCollectionsCommand(Server $server, array $options = array() } /** - * 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 From 7191886ddec3b8553f466ee66d8690a565ab1f28 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 22 Apr 2015 22:24:17 -0400 Subject: [PATCH 06/21] Use type map for database and collection enumeration This obviates the need to handle stdClass instances within the results. --- composer.json | 2 +- src/Client.php | 8 ++------ src/Database.php | 2 ++ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 2ca78742e..1a55ed972 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ { "name": "Derick Rethans", "email": "github@derickrethans.nl" } ], "require": { - "ext-mongodb": "*" + "ext-mongodb": ">=0.5.0" }, "require-dev": { "fzaninotto/faker": "~1.0" diff --git a/src/Client.php b/src/Client.php index 3654d0cc5..572a610f0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -63,24 +63,20 @@ public function listDatabases() $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 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($databases); + return new DatabaseInfoLegacyIterator($result['databases']); } /** diff --git a/src/Database.php b/src/Database.php index 8d49de0b6..3e4e22f59 100644 --- a/src/Database.php +++ b/src/Database.php @@ -141,6 +141,7 @@ 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); } @@ -177,6 +178,7 @@ private function listCollectionsLegacy(Server $server, array $options = array()) $namespace = $this->databaseName . '.system.namespaces'; $query = new Query($filter); $cursor = $server->executeQuery($namespace, $query); + $cursor->setTypeMap(array('document' => 'array')); return new CollectionInfoLegacyIterator($cursor); } From 2f63218ab0c95d11495fc7150fe0fdebc8d747e4 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 22 Apr 2015 22:29:17 -0400 Subject: [PATCH 07/21] PHPLIB-70: Add class-level docs to model iterators --- src/Model/CollectionInfoCommandIterator.php | 11 +++++++++++ src/Model/CollectionInfoIterator.php | 8 ++++++++ src/Model/CollectionInfoLegacyIterator.php | 14 ++++++++++++++ src/Model/DatabaseInfoIterator.php | 8 ++++++++ src/Model/DatabaseInfoLegacyIterator.php | 10 ++++++++++ 5 files changed, 51 insertions(+) diff --git a/src/Model/CollectionInfoCommandIterator.php b/src/Model/CollectionInfoCommandIterator.php index 58313035b..b0dc98684 100644 --- a/src/Model/CollectionInfoCommandIterator.php +++ b/src/Model/CollectionInfoCommandIterator.php @@ -4,6 +4,17 @@ 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 { /** diff --git a/src/Model/CollectionInfoIterator.php b/src/Model/CollectionInfoIterator.php index f9e0b6135..0c904d3ab 100644 --- a/src/Model/CollectionInfoIterator.php +++ b/src/Model/CollectionInfoIterator.php @@ -4,6 +4,14 @@ use Iterator; +/** + * CollectionInfoIterator interface. + * + * This iterator is used for enumerating collections in a database. + * + * @api + * @see MongoDB\Database::listCollections() + */ interface CollectionInfoIterator extends Iterator { /** diff --git a/src/Model/CollectionInfoLegacyIterator.php b/src/Model/CollectionInfoLegacyIterator.php index 8f55eb672..01a6a33df 100644 --- a/src/Model/CollectionInfoLegacyIterator.php +++ b/src/Model/CollectionInfoLegacyIterator.php @@ -7,6 +7,20 @@ 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 { /** diff --git a/src/Model/DatabaseInfoIterator.php b/src/Model/DatabaseInfoIterator.php index 04b5b7652..e18ef18aa 100644 --- a/src/Model/DatabaseInfoIterator.php +++ b/src/Model/DatabaseInfoIterator.php @@ -4,6 +4,14 @@ use Iterator; +/** + * DatabaseInfoIterator interface. + * + * This iterator is used for enumerating databases on a server. + * + * @api + * @see MongoDB\Client::listDatabases() + */ interface DatabaseInfoIterator extends Iterator { /** diff --git a/src/Model/DatabaseInfoLegacyIterator.php b/src/Model/DatabaseInfoLegacyIterator.php index b416ab800..af1421ba6 100644 --- a/src/Model/DatabaseInfoLegacyIterator.php +++ b/src/Model/DatabaseInfoLegacyIterator.php @@ -2,6 +2,16 @@ 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; From 48b3de1c201c033a6cf8122f7adc2a1a922846ee Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 22 Apr 2015 22:30:02 -0400 Subject: [PATCH 08/21] PHPLIB-75: Refactor model classes and add class-level docs --- src/Model/CollectionInfo.php | 27 ++++++++++++++++++--------- src/Model/DatabaseInfo.php | 24 +++++++++++++++--------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/Model/CollectionInfo.php b/src/Model/CollectionInfo.php index 9617250ad..318c5e5c2 100644 --- a/src/Model/CollectionInfo.php +++ b/src/Model/CollectionInfo.php @@ -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 @@ public function __construct(array $info) */ public function getName() { - return $this->name; + return (string) $this->info['name']; } /** @@ -35,7 +44,7 @@ public function getName() */ public function getOptions() { - return $this->options; + return isset($this->info['options']) ? (array) $this->info['options'] : array(); } /** @@ -45,7 +54,7 @@ public function getOptions() */ public function isCapped() { - return isset($this->options['capped']) ? (boolean) $this->options['capped'] : false; + return ! empty($this->info['options']['capped']); } /** @@ -55,7 +64,7 @@ public function isCapped() */ 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 @@ public function getCappedMax() */ 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; } } diff --git a/src/Model/DatabaseInfo.php b/src/Model/DatabaseInfo.php index 2f6038f25..b304ab63a 100644 --- a/src/Model/DatabaseInfo.php +++ b/src/Model/DatabaseInfo.php @@ -2,11 +2,19 @@ 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 $empty; - private $name; - private $sizeOnDisk; + private $info; /** * Constructor. @@ -15,9 +23,7 @@ class DatabaseInfo */ public function __construct(array $info) { - $this->name = (string) $info['name']; - $this->empty = (boolean) $info['empty']; - $this->sizeOnDisk = (integer) $info['sizeOnDisk']; + $this->info = $info; } /** @@ -27,7 +33,7 @@ public function __construct(array $info) */ public function getName() { - return $this->name; + return (string) $this->info['name']; } /** @@ -37,7 +43,7 @@ public function getName() */ public function getSizeOnDisk() { - return $this->sizeOnDisk; + return (integer) $this->info['sizeOnDisk']; } /** @@ -47,6 +53,6 @@ public function getSizeOnDisk() */ public function isEmpty() { - return $this->empty; + return (boolean) $this->info['empty']; } } From 79d07ff75f97a3c41fb8ba195c864cc24366ab90 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 22 Apr 2015 22:32:29 -0400 Subject: [PATCH 09/21] PHPLIB-46: Index info and corresponding iterator class --- src/Model/IndexInfo.php | 160 ++++++++++++++++++++++++ src/Model/IndexInfoIterator.php | 23 ++++ src/Model/IndexInfoIteratorIterator.php | 33 +++++ 3 files changed, 216 insertions(+) create mode 100644 src/Model/IndexInfo.php create mode 100644 src/Model/IndexInfoIterator.php create mode 100644 src/Model/IndexInfoIteratorIterator.php diff --git a/src/Model/IndexInfo.php b/src/Model/IndexInfo.php new file mode 100644 index 000000000..709272f6c --- /dev/null +++ b/src/Model/IndexInfo.php @@ -0,0 +1,160 @@ +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 read-only + */ + public function offsetSet($key, $value) + { + throw new BadMethodCallException('IndexInfo is read-only'); + } + + /** + * Not supported. + * + * @see http://php.net/arrayaccess.offsetunset + * @throws BadMethodCallException IndexInfo is read-only + */ + public function offsetUnset($key) + { + throw new BadMethodCallException('IndexInfo is read-only'); + } +} diff --git a/src/Model/IndexInfoIterator.php b/src/Model/IndexInfoIterator.php new file mode 100644 index 000000000..e4275bb66 --- /dev/null +++ b/src/Model/IndexInfoIterator.php @@ -0,0 +1,23 @@ + Date: Fri, 24 Apr 2015 15:50:20 -0400 Subject: [PATCH 10/21] PHPLIB-46: Index enumeration methods --- src/Collection.php | 52 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Collection.php b/src/Collection.php index a3aa8eb3e..b959fd9d6 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -2,13 +2,16 @@ 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\Model\IndexInfoIterator; +use MongoDB\Model\IndexInfoIteratorIterator; use InvalidArgumentException; class Collection @@ -963,15 +966,23 @@ public function insertOne(array $document) } /** - * 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); + + $serverInfo = $server->getInfo(); + $maxWireVersion = isset($serverInfo['maxWireVersion']) ? $serverInfo['maxWireVersion'] : 0; + + return ($maxWireVersion >= 3) + ? $this->listIndexesCommand($server) + : $this->listIndexesLegacy($server); } /** @@ -1150,4 +1161,37 @@ protected function _update($filter, $update, $options) $bulk->update($filter, $update, $options); return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); } + + /** + * 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); + } } From 4de66e39d72c509f87e77d78a5de11f45a18b0b9 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 24 Apr 2015 15:51:42 -0400 Subject: [PATCH 11/21] PHPLIB-63: Index creation methods --- src/Collection.php | 129 ++++++++++++++++++--- src/Exception/Exception.php | 7 ++ src/Exception/InvalidArgumentException.php | 7 ++ src/Exception/UnexpectedTypeException.php | 11 ++ 4 files changed, 141 insertions(+), 13 deletions(-) create mode 100644 src/Exception/Exception.php create mode 100644 src/Exception/InvalidArgumentException.php create mode 100644 src/Exception/UnexpectedTypeException.php diff --git a/src/Collection.php b/src/Collection.php index b959fd9d6..b11fe713c 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -10,9 +10,11 @@ use MongoDB\Driver\ReadPreference; 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 InvalidArgumentException; +use stdClass; class Collection { @@ -248,34 +250,84 @@ public function count(array $filter = array(), array $options = array()) } /** - * 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. * - * 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]). + * 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: + * + * $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 does not + * contain a "key" document + * @throws UnexpectedTypeException if an index specification is not an array + * or a "key" document is not an array or + * object */ - public function createIndexes(array $models) + public function createIndexes(array $indexes) { - // TODO + foreach ($indexes as &$index) { + if ( ! is_array($index)) { + throw new UnexpectedTypeException($index, 'array'); + } + + 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'); + } + + $index['key'] = (object) $index['key']; + $index['ns'] = $this->ns; + + if ( ! isset($index['name'])) { + $index['name'] = $this->generateIndexName($index['key']); + } + } + + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + $server = $this->manager->selectServer($readPreference); + + $serverInfo = $server->getInfo(); + $maxWireVersion = isset($serverInfo['maxWireVersion']) ? $serverInfo['maxWireVersion'] : 0; + + if ($maxWireVersion >= 2) { + $this->createIndexesCommand($server, $indexes); + } else { + $this->createIndexesLegacy($server, $indexes); + } + + return array_map(function(array $index) { return $index['name']; }, $indexes); } /** @@ -1162,6 +1214,57 @@ protected function _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 array $indexes + */ + private function createIndexesCommand(Server $server, array $indexes) + { + $command = new Command(array( + 'createIndexes' => $this->collname, + 'indexes' => $indexes, + )); + $server->executeCommand($this->dbname, $command); + } + + /** + * Create one or more indexes for the collection by inserting into the + * "system.indexes" collection (MongoDB <2.6). + * + * @param Server $server + * @param array $indexes + */ + private function createIndexesLegacy(Server $server, array $indexes) + { + $bulk = new BulkWrite(true); + + foreach ($indexes as $index) { + $bulk->insert($index); + } + + $server->executeBulkWrite($this->dbname . '.system.indexes', $bulk); + } + + /** + * Generates an index name from its key specification. + * + * @param object $key + * @return string + */ + private function generateIndexName(stdClass $key) + { + $name = ''; + + foreach ($key as $field => $type) { + $name .= ($name != '' ? '_' : '') . $field . '_' . $type; + } + + return $name; + } + /** * Returns information for all indexes for this collection using the * listIndexes command. diff --git a/src/Exception/Exception.php b/src/Exception/Exception.php new file mode 100644 index 000000000..bf6158423 --- /dev/null +++ b/src/Exception/Exception.php @@ -0,0 +1,7 @@ + Date: Fri, 24 Apr 2015 15:52:15 -0400 Subject: [PATCH 12/21] PHPLIB-46, PHPLIB-63, PHPLIB-69: Functional tests for index methods --- tests/CollectionFunctionalTest.php | 163 +++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/tests/CollectionFunctionalTest.php b/tests/CollectionFunctionalTest.php index 4d3bc5d20..9a5e87e95 100644 --- a/tests/CollectionFunctionalTest.php +++ b/tests/CollectionFunctionalTest.php @@ -4,6 +4,8 @@ use MongoDB\Collection; use MongoDB\Driver\Manager; +use MongoDB\Model\IndexInfo; +use InvalidArgumentException; class CollectionFunctionalTest extends FunctionalTestCase { @@ -56,4 +58,165 @@ function testInsertAndRetrieve() } $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('t_1', $this->collection->createIndex(array('t' => 1), array('expireAfterSeconds' => 0))); + $this->assertIndexExists('t_1', 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', 't_1'); + + $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), + ); + + $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('t_1', function(IndexInfo $info) use ($that) { + $that->assertFalse($info->isSparse()); + $that->assertFalse($info->isUnique()); + $that->assertTrue($info->isTtl()); + }); + } + + 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 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); + } + } } From 39cf9182270871e40151d15f2c9c11f6493fea49 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 24 Apr 2015 19:46:44 -0400 Subject: [PATCH 13/21] PHPLIB-63: Use model class to validate index creation args --- src/Collection.php | 44 +++--------------- src/Model/IndexInput.php | 99 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 src/Model/IndexInput.php diff --git a/src/Collection.php b/src/Collection.php index b11fe713c..595474f37 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -14,7 +14,7 @@ use MongoDB\Exception\UnexpectedTypeException; use MongoDB\Model\IndexInfoIterator; use MongoDB\Model\IndexInfoIteratorIterator; -use stdClass; +use MongoDB\Model\IndexInput; class Collection { @@ -286,33 +286,20 @@ public function createIndex($key, array $options = array()) * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/ * @param array $indexes List of index specifications * @return string[] The names of the created indexes - * @throws InvalidArgumentException if an index specification does not - * contain a "key" document - * @throws UnexpectedTypeException if an index specification is not an array - * or a "key" document is not an array or - * object + * @throws InvalidArgumentException if an index specification is invalid */ public function createIndexes(array $indexes) { - foreach ($indexes as &$index) { + foreach ($indexes as $i => $index) { if ( ! is_array($index)) { throw new UnexpectedTypeException($index, 'array'); } - if ( ! isset($index['key'])) { - throw new InvalidArgumentException('Required "key" document is missing from index specification'); + if ( ! isset($index['ns'])) { + $index['ns'] = $this->ns; } - if ( ! is_array($index['key']) && ! is_object($index['key'])) { - throw new UnexpectedTypeException($index['key'], 'array or object'); - } - - $index['key'] = (object) $index['key']; - $index['ns'] = $this->ns; - - if ( ! isset($index['name'])) { - $index['name'] = $this->generateIndexName($index['key']); - } + $indexes[$i] = new IndexInput($index); } $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); @@ -327,7 +314,7 @@ public function createIndexes(array $indexes) $this->createIndexesLegacy($server, $indexes); } - return array_map(function(array $index) { return $index['name']; }, $indexes); + return array_map(function(IndexInput $index) { return (string) $index; }, $indexes); } /** @@ -1248,23 +1235,6 @@ private function createIndexesLegacy(Server $server, array $indexes) $server->executeBulkWrite($this->dbname . '.system.indexes', $bulk); } - /** - * Generates an index name from its key specification. - * - * @param object $key - * @return string - */ - private function generateIndexName(stdClass $key) - { - $name = ''; - - foreach ($key as $field => $type) { - $name .= ($name != '' ? '_' : '') . $field . '_' . $type; - } - - return $name; - } - /** * Returns information for all indexes for this collection using the * listIndexes command. diff --git a/src/Model/IndexInput.php b/src/Model/IndexInput.php new file mode 100644 index 000000000..03398ae7d --- /dev/null +++ b/src/Model/IndexInput.php @@ -0,0 +1,99 @@ +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; + } +} From 2083eede4829bc54fefe1558592bd051dfdf9864 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 24 Apr 2015 19:47:14 -0400 Subject: [PATCH 14/21] PHPLIB-63: Empty input to createIndexes() is a NOP --- src/Collection.php | 4 ++++ tests/CollectionFunctionalTest.php | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/Collection.php b/src/Collection.php index 595474f37..48ce67241 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -290,6 +290,10 @@ public function createIndex($key, array $options = array()) */ public function createIndexes(array $indexes) { + if (empty($indexes)) { + return array(); + } + foreach ($indexes as $i => $index) { if ( ! is_array($index)) { throw new UnexpectedTypeException($index, 'array'); diff --git a/tests/CollectionFunctionalTest.php b/tests/CollectionFunctionalTest.php index 9a5e87e95..2050666b3 100644 --- a/tests/CollectionFunctionalTest.php +++ b/tests/CollectionFunctionalTest.php @@ -132,6 +132,11 @@ public function testCreateIndexes() }); } + public function testCreateIndexesWithEmptyInputIsNop() + { + $this->assertSame(array(), $this->collection->createIndexes(array())); + } + public function testDropIndex() { $this->assertSame('x_1', $this->collection->createIndex(array('x' => 1))); From 33f6ca4d70c8b6df2cc9d1f12fa5bb8eb9004e54 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 24 Apr 2015 19:47:50 -0400 Subject: [PATCH 15/21] PHPLIB-75: Use package BadMethodCallException for IndexInfo Also change "read-only" language to "immutable". --- src/Exception/BadMethodCallException.php | 7 +++++++ src/Model/IndexInfo.php | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 src/Exception/BadMethodCallException.php diff --git a/src/Exception/BadMethodCallException.php b/src/Exception/BadMethodCallException.php new file mode 100644 index 000000000..331c3b08d --- /dev/null +++ b/src/Exception/BadMethodCallException.php @@ -0,0 +1,7 @@ + Date: Fri, 24 Apr 2015 19:50:49 -0400 Subject: [PATCH 16/21] PHPLIB-69: Do not allow empty index name for dropIndex() --- src/Collection.php | 7 +++++-- tests/CollectionFunctionalTest.php | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Collection.php b/src/Collection.php index 48ce67241..0e39ba039 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -401,13 +401,16 @@ public function drop() * @see http://docs.mongodb.org/manual/reference/method/db.collection.dropIndex/ * @param string $indexName * @return Cursor - * @throws InvalidArgumentException if "*" is specified, since dropIndexes() - * should be used to drop multiple indexes + * @throws InvalidArgumentException if $indexName is an empty string or "*" */ public function dropIndex($indexName) { $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'); } diff --git a/tests/CollectionFunctionalTest.php b/tests/CollectionFunctionalTest.php index 2050666b3..dc3e71614 100644 --- a/tests/CollectionFunctionalTest.php +++ b/tests/CollectionFunctionalTest.php @@ -150,6 +150,16 @@ public function testDropIndex() } } + /** + * @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 */ From a0aceaf4c2ef0afe7d75be0006d36010d1629d9a Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 24 Apr 2015 19:58:41 -0400 Subject: [PATCH 17/21] PHPLIB-63: Test custom name for index creation --- tests/CollectionFunctionalTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/CollectionFunctionalTest.php b/tests/CollectionFunctionalTest.php index dc3e71614..7c3bd0300 100644 --- a/tests/CollectionFunctionalTest.php +++ b/tests/CollectionFunctionalTest.php @@ -84,8 +84,8 @@ public function testCreateIndex() $that->assertFalse($info->isTtl()); }); - $this->assertSame('t_1', $this->collection->createIndex(array('t' => 1), array('expireAfterSeconds' => 0))); - $this->assertIndexExists('t_1', function(IndexInfo $info) use ($that) { + $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()); @@ -96,13 +96,13 @@ public function testCreateIndexes() { $that = $this; - $expectedNames = array('x_1', 'y_-1_z_1', 'g_2dsphere_z_1', 't_1'); + $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), + array('key' => array('t' => 1), 'expireAfterSeconds' => 0, 'name' => 'my_ttl'), ); $this->assertSame($expectedNames, $this->collection->createIndexes($indexes)); @@ -125,7 +125,7 @@ public function testCreateIndexes() $that->assertFalse($info->isTtl()); }); - $this->assertIndexExists('t_1', function(IndexInfo $info) use ($that) { + $this->assertIndexExists('my_ttl', function(IndexInfo $info) use ($that) { $that->assertFalse($info->isSparse()); $that->assertFalse($info->isUnique()); $that->assertTrue($info->isTtl()); From bca7df7fecf8cc9494f6d056af5adf9ed2852825 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 24 Apr 2015 20:29:52 -0400 Subject: [PATCH 18/21] PHPLIB-63: Refactor to avoid else condition and void methods --- src/Collection.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Collection.php b/src/Collection.php index 0e39ba039..b675ae342 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -312,13 +312,9 @@ public function createIndexes(array $indexes) $serverInfo = $server->getInfo(); $maxWireVersion = isset($serverInfo['maxWireVersion']) ? $serverInfo['maxWireVersion'] : 0; - if ($maxWireVersion >= 2) { - $this->createIndexesCommand($server, $indexes); - } else { - $this->createIndexesLegacy($server, $indexes); - } - - return array_map(function(IndexInput $index) { return (string) $index; }, $indexes); + return ($maxWireVersion >= 2) + ? $this->createIndexesCommand($server, $indexes) + : $this->createIndexesLegacy($server, $indexes); } /** @@ -1212,8 +1208,9 @@ protected function _update($filter, $update, $options) * Create one or more indexes for the collection using the createIndexes * command. * - * @param Server $server - * @param array $indexes + * @param Server $server + * @param IndexInput[] $indexes + * @return string[] The names of the created indexes */ private function createIndexesCommand(Server $server, array $indexes) { @@ -1222,14 +1219,17 @@ private function createIndexesCommand(Server $server, array $indexes) '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 array $indexes + * @param Server $server + * @param IndexInput[] $indexes + * @return string[] The names of the created indexes */ private function createIndexesLegacy(Server $server, array $indexes) { @@ -1240,6 +1240,8 @@ private function createIndexesLegacy(Server $server, array $indexes) } $server->executeBulkWrite($this->dbname . '.system.indexes', $bulk); + + return array_map(function(IndexInput $index) { return (string) $index; }, $indexes); } /** From 5a6ff7f7c6c8a6f5a60610c9a82e21f6faf739ee Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 24 Apr 2015 20:33:06 -0400 Subject: [PATCH 19/21] Use wire protocol version constants for feature detection This removes some copypasta from existing Database and Collection code. --- src/Collection.php | 10 ++-------- src/Database.php | 5 +---- src/FeatureDetection.php | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 src/FeatureDetection.php diff --git a/src/Collection.php b/src/Collection.php index b675ae342..2257847e2 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -309,10 +309,7 @@ public function createIndexes(array $indexes) $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); $server = $this->manager->selectServer($readPreference); - $serverInfo = $server->getInfo(); - $maxWireVersion = isset($serverInfo['maxWireVersion']) ? $serverInfo['maxWireVersion'] : 0; - - return ($maxWireVersion >= 2) + return (FeatureDetection::isSupported($server, FeatureDetection::API_CREATEINDEXES_CMD)) ? $this->createIndexesCommand($server, $indexes) : $this->createIndexesLegacy($server, $indexes); } @@ -1019,10 +1016,7 @@ public function listIndexes() $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_LISTINDEXES_CMD)) ? $this->listIndexesCommand($server) : $this->listIndexesLegacy($server); } diff --git a/src/Database.php b/src/Database.php index 3e4e22f59..bbee8dc0b 100644 --- a/src/Database.php +++ b/src/Database.php @@ -101,10 +101,7 @@ public function listCollections(array $options = array()) $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); } diff --git a/src/FeatureDetection.php b/src/FeatureDetection.php new file mode 100644 index 000000000..51b6783de --- /dev/null +++ b/src/FeatureDetection.php @@ -0,0 +1,33 @@ +getInfo(); + $maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0; + $minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0; + + return ($minWireVersion <= $feature && $maxWireVersion >= $feature); + } +} From 3df5e5ec94a63b6f798577e40740076647f69243 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 24 Apr 2015 20:40:15 -0400 Subject: [PATCH 20/21] Bump ext-mongodb dependency to 0.5.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1a55ed972..57c26d342 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ { "name": "Derick Rethans", "email": "github@derickrethans.nl" } ], "require": { - "ext-mongodb": ">=0.5.0" + "ext-mongodb": ">=0.5.1" }, "require-dev": { "fzaninotto/faker": "~1.0" From 7ba1966251be982d6feacb8c282e7f4395a52548 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Sun, 26 Apr 2015 16:57:15 -0400 Subject: [PATCH 21/21] PHPLIB-63: Fix index creation for legacy servers This can be removed for PHPLIB-87 once PHPC-274 is resolved. --- src/Collection.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Collection.php b/src/Collection.php index 2257847e2..8512c02cc 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -1230,7 +1230,8 @@ private function createIndexesLegacy(Server $server, array $indexes) $bulk = new BulkWrite(true); foreach ($indexes as $index) { - $bulk->insert($index); + // TODO: Remove this once PHPC-274 is resolved (see: PHPLIB-87) + $bulk->insert($index->bsonSerialize()); } $server->executeBulkWrite($this->dbname . '.system.indexes', $bulk);