Commit 2c689a2b authored by Jeremy Mikola's avatar Jeremy Mikola

PHPLIB-138: Support typeMap option for aggregate and find operations

parent 330e1e30
......@@ -145,6 +145,10 @@ class Collection
* returned; otherwise, an ArrayIterator is returned, which wraps the
* "result" array from the command response document.
*
* Note: BSON deserialization of inline aggregation results (i.e. not using
* a command cursor) does not yet support a custom type map
* (depends on: https://jira.mongodb.org/browse/PHPC-314).
*
* @see Aggregate::__construct() for supported options
* @param array $pipeline List of pipeline operations
* @param array $options Command options
......@@ -169,6 +173,10 @@ class Collection
$options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new Aggregate($this->databaseName, $this->collectionName, $pipeline, $options);
$server = $this->manager->selectServer($options['readPreference']);
......@@ -397,6 +405,10 @@ class Collection
$options['readPreference'] = $this->readPreference;
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new Find($this->databaseName, $this->collectionName, $filter, $options);
$server = $this->manager->selectServer($options['readPreference']);
......@@ -422,6 +434,10 @@ class Collection
$options['readPreference'] = $this->readPreference;
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new FindOne($this->databaseName, $this->collectionName, $filter, $options);
$server = $this->manager->selectServer($options['readPreference']);
......@@ -433,6 +449,9 @@ class Collection
*
* The document to return may be null.
*
* Note: BSON deserialization of the returned document does not yet support
* a custom type map (depends on: https://jira.mongodb.org/browse/PHPC-314).
*
* @see FindOneAndDelete::__construct() for supported options
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
* @param array|object $filter Query by which to filter documents
......@@ -460,6 +479,9 @@ class Collection
* returned. Specify FindOneAndReplace::RETURN_DOCUMENT_AFTER for the
* "returnDocument" option to return the updated document.
*
* Note: BSON deserialization of the returned document does not yet support
* a custom type map (depends on: https://jira.mongodb.org/browse/PHPC-314).
*
* @see FindOneAndReplace::__construct() for supported options
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
* @param array|object $filter Query by which to filter documents
......@@ -488,6 +510,9 @@ class Collection
* returned. Specify FindOneAndUpdate::RETURN_DOCUMENT_AFTER for the
* "returnDocument" option to return the updated document.
*
* Note: BSON deserialization of the returned document does not yet support
* a custom type map (depends on: https://jira.mongodb.org/browse/PHPC-314).
*
* @see FindOneAndReplace::__construct() for supported options
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
* @param array|object $filter Query by which to filter documents
......
......@@ -60,6 +60,12 @@ class Aggregate implements Executable
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* * typeMap (array): Type map for BSON deserialization. This will be
* applied to the returned Cursor (it is not sent to the server).
*
* This is not supported for inline aggregation results (i.e. useCursor
* option is false or the server versions < 2.6).
*
* * useCursor (boolean): Indicates whether the command will request that
* the server provide results using a cursor. The default is true.
*
......@@ -124,6 +130,10 @@ class Aggregate implements Executable
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw new InvalidArgumentTypeException('"typeMap" option', $options['typeMap'], 'array');
}
if ( ! is_bool($options['useCursor'])) {
throw new InvalidArgumentTypeException('"useCursor" option', $options['useCursor'], 'boolean');
}
......@@ -132,6 +142,10 @@ class Aggregate implements Executable
throw new InvalidArgumentException('"batchSize" option should not be used if "useCursor" is false');
}
if (isset($options['typeMap']) && ! $options['useCursor']) {
throw new InvalidArgumentException('"typeMap" option should not be used if "useCursor" is false');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->pipeline = $pipeline;
......@@ -154,6 +168,13 @@ class Aggregate implements Executable
$cursor = $server->executeCommand($this->databaseName, $command, $readPreference);
if ($isCursorSupported && $this->options['useCursor']) {
/* The type map can only be applied to command cursors until
* https://jira.mongodb.org/browse/PHPC-314 is implemented.
*/
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return $cursor;
}
......
......@@ -79,6 +79,9 @@ class Find implements Executable
* "$orderby" also exists in the modifiers document, this option will
* take precedence.
*
* * typeMap (array): Type map for BSON deserialization. This will be
* applied to the returned Cursor (it is not sent to the server).
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
......@@ -155,6 +158,10 @@ class Find implements Executable
throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw new InvalidArgumentTypeException('"typeMap" option', $options['typeMap'], 'array');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->filter = $filter;
......@@ -172,7 +179,13 @@ class Find implements Executable
{
$readPreference = isset($this->options['readPreference']) ? $this->options['readPreference'] : null;
return $server->executeQuery($this->databaseName . '.' . $this->collectionName, $this->createQuery(), $readPreference);
$cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, $this->createQuery(), $readPreference);
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return $cursor;
}
/**
......
......@@ -59,10 +59,6 @@ class FindOne implements Executable
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw new InvalidArgumentTypeException('"typeMap" option', $options['typeMap'], 'array');
}
$this->find = new Find(
$databaseName,
$collectionName,
......@@ -83,11 +79,6 @@ class FindOne implements Executable
public function execute(Server $server)
{
$cursor = $this->find->execute($server);
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
$document = current($cursor->toArray());
return ($document === false) ? null : $document;
......
......@@ -2,17 +2,85 @@
namespace MongoDB\Tests\Operation;
use MongoDB\Driver\BulkWrite;
use MongoDB\Operation\Aggregate;
class AggregateFunctionalTest extends FunctionalTestCase
{
private static $wireVersionForCursor = 2;
/**
* @expectedException MongoDB\Driver\Exception\RuntimeException
*/
public function testUnrecognizedPipelineState()
{
$server = $this->getPrimaryServer();
$operation = new Aggregate($this->getDatabaseName(), $this->getCollectionName(), [['$foo' => 1]]);
$operation->execute($server);
$operation->execute($this->getPrimaryServer());
}
/**
* @dataProvider provideTypeMapOptionsAndExpectedDocument
*/
public function testTypeMapOption(array $typeMap, array $expectedDocuments)
{
if ( ! \MongoDB\server_supports_feature($this->getPrimaryServer(), self::$wireVersionForCursor)) {
$this->markTestSkipped('Command cursor is not supported');
}
$this->createFixtures(3);
$pipeline = [['$match' => ['_id' => ['$ne' => 2]]]];
$operation = new Aggregate($this->getDatabaseName(), $this->getCollectionName(), $pipeline, ['typeMap' => $typeMap]);
$cursor = $operation->execute($this->getPrimaryServer());
$this->assertEquals($expectedDocuments, $cursor->toArray());
}
public function provideTypeMapOptionsAndExpectedDocument()
{
return [
[
['root' => 'array', 'document' => 'array'],
[
['_id' => 1, 'x' => ['foo' => 'bar']],
['_id' => 3, 'x' => ['foo' => 'bar']],
],
],
[
['root' => 'object', 'document' => 'array'],
[
(object) ['_id' => 1, 'x' => ['foo' => 'bar']],
(object) ['_id' => 3, 'x' => ['foo' => 'bar']],
],
],
[
['root' => 'array', 'document' => 'stdClass'],
[
['_id' => 1, 'x' => (object) ['foo' => 'bar']],
['_id' => 3, 'x' => (object) ['foo' => 'bar']],
],
],
];
}
/**
* Create data fixtures.
*
* @param integer $n
*/
private function createFixtures($n)
{
$bulkWrite = new BulkWrite(['ordered' => true]);
for ($i = 1; $i <= $n; $i++) {
$bulkWrite->insert([
'_id' => $i,
'x' => (object) ['foo' => 'bar'],
]);
}
$result = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite);
$this->assertEquals($n, $result->getInsertedCount());
}
}
......@@ -61,6 +61,10 @@ class AggregateTest extends TestCase
$options[][] = ['readPreference' => $value];
}
foreach ($this->getInvalidArrayValues() as $value) {
$options[][] = ['typeMap' => $value];
}
foreach ($this->getInvalidBooleanValues() as $value) {
$options[][] = ['useCursor' => $value];
}
......@@ -81,4 +85,18 @@ class AggregateTest extends TestCase
['batchSize' => 100, 'useCursor' => false]
);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @expectedExceptionMessage "typeMap" option should not be used if "useCursor" is false
*/
public function testConstructorTypeMapOptionRequiresUseCursor()
{
new Aggregate(
$this->getDatabaseName(),
$this->getCollectionName(),
[['$match' => ['x' => 1]]],
['typeMap' => ['root' => 'array'], 'useCursor' => false]
);
}
}
<?php
namespace MongoDB\Tests\Operation;
use MongoDB\Driver\BulkWrite;
use MongoDB\Operation\Find;
class FindFunctionalTest extends FunctionalTestCase
{
/**
* @dataProvider provideTypeMapOptionsAndExpectedDocuments
*/
public function testTypeMapOption(array $typeMap, array $expectedDocuments)
{
$this->createFixtures(3);
$operation = new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['typeMap' => $typeMap]);
$cursor = $operation->execute($this->getPrimaryServer());
$this->assertEquals($expectedDocuments, $cursor->toArray());
}
public function provideTypeMapOptionsAndExpectedDocuments()
{
return [
[
['root' => 'array', 'document' => 'array'],
[
['_id' => 1, 'x' => ['foo' => 'bar']],
['_id' => 2, 'x' => ['foo' => 'bar']],
['_id' => 3, 'x' => ['foo' => 'bar']],
],
],
[
['root' => 'object', 'document' => 'array'],
[
(object) ['_id' => 1, 'x' => ['foo' => 'bar']],
(object) ['_id' => 2, 'x' => ['foo' => 'bar']],
(object) ['_id' => 3, 'x' => ['foo' => 'bar']],
],
],
[
['root' => 'array', 'document' => 'stdClass'],
[
['_id' => 1, 'x' => (object) ['foo' => 'bar']],
['_id' => 2, 'x' => (object) ['foo' => 'bar']],
['_id' => 3, 'x' => (object) ['foo' => 'bar']],
],
],
];
}
/**
* Create data fixtures.
*
* @param integer $n
*/
private function createFixtures($n)
{
$bulkWrite = new BulkWrite(['ordered' => true]);
for ($i = 1; $i <= $n; $i++) {
$bulkWrite->insert([
'_id' => $i,
'x' => (object) ['foo' => 'bar'],
]);
}
$result = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite);
$this->assertEquals($n, $result->getInsertedCount());
}
}
<?php
namespace MongoDB\Tests\Operation;
use MongoDB\Operation\FindOne;
class FindOneTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @dataProvider provideInvalidConstructorTypeMapOptions
*/
public function testConstructorTypeMapOption($typeMap)
{
new FindOne($this->getDatabaseName(), $this->getCollectionName(), [], ['typeMap' => $typeMap]);
}
public function provideInvalidConstructorTypeMapOptions()
{
return $this->wrapValuesForDataProvider($this->getInvalidArrayValues());
}
}
......@@ -80,6 +80,10 @@ class FindTest extends TestCase
$options[][] = ['sort' => $value];
}
foreach ($this->getInvalidArrayValues() as $value) {
$options[][] = ['typeMap' => $value];
}
return $options;
}
......
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