Commit e19dfe30 authored by Jeremy Mikola's avatar Jeremy Mikola

Merge pull request #300

parents 1b207079 3b6b96ea
......@@ -49,11 +49,6 @@ source:
source:
file: apiargs-MongoDBCollection-common-option.yaml
ref: typeMap
post: |
.. note::
This is not supported for inline aggregation results (i.e. ``useCursor``
option is ``false`` or the server version is < 2.6).
---
arg_name: option
name: useCursor
......
......@@ -14,6 +14,12 @@ source:
file: apiargs-common-option.yaml
ref: maxTimeMS
---
source:
file: apiargs-MongoDBCollection-common-option.yaml
ref: typeMap
post: |
This will be used for the returned result document.
---
source:
file: apiargs-MongoDBCollection-common-option.yaml
ref: writeConcern
......
......@@ -31,6 +31,12 @@ interface: phpmethod
operation: ~
optional: true
---
source:
file: apiargs-MongoDBCollection-common-option.yaml
ref: typeMap
post: |
This will be used for the returned result document.
---
source:
file: apiargs-MongoDBCollection-common-option.yaml
ref: upsert
......
......@@ -31,6 +31,12 @@ interface: phpmethod
operation: ~
optional: true
---
source:
file: apiargs-MongoDBCollection-common-option.yaml
ref: typeMap
post: |
This will be used for the returned result document.
---
source:
file: apiargs-MongoDBCollection-common-option.yaml
ref: upsert
......
ref: _bson-deserialization
content: |
.. note::
{{method}} does not yet support a ``typeMap`` option for BSON
deserialization of the returned document.
Classes implementing
:php:`MongoDB\\BSON\\Persistable <class.mongodb-bson-persistable>`
are deserialized according to the :php:`persistance
<mongodb.persistance>` specification.
...
ref: bson-deserialization-findOneAndDelete
source:
file: extracts-bson-deserialization-base.yaml
ref: _bson-deserialization
replacement:
method: ":phpmethod:`MongoDB\\Collection::findOneAndDelete()`"
---
ref: bson-deserialization-findOneAndReplace
source:
file: extracts-bson-deserialization-base.yaml
ref: _bson-deserialization
replacement:
method: ":phpmethod:`MongoDB\\Collection::findOneAndReplace()`"
---
ref: bson-deserialization-findOneAndUpdate
source:
file: extracts-bson-deserialization-base.yaml
ref: _bson-deserialization
replacement:
method: ":phpmethod:`MongoDB\\Collection::findOneAndUpdate()`"
...
......@@ -58,14 +58,6 @@ MongoDB server version and whether the ``useCursor`` option is specified. If
``result`` array from the command response document. In both cases, the return
value will be :php:`Traversable <traversable>`.
.. note::
BSON deserialization of inline aggregation results (i.e. not using a command
cursor) does not yet support a ``typeMap`` option. Classes implementing
:php:`MongoDB\\BSON\\Persistable <mongodb-bson-persistable>` will still be
deserialized according to the
:php:`Persistence <mongodb.persistence.deserialization>` specification.
.. todo: add examples
See Also
......
......@@ -29,13 +29,11 @@ Definition
.. include:: /includes/apiargs/MongoDBCollection-method-findOneAndDelete-option.rst
.. include:: /includes/extracts/bson-deserialization-findOneAndDelete.rst
Return Values
-------------
An object for the document that was deleted, or ``null`` if no document matched
the query.
An array or object for the document that was deleted, or ``null`` if no document
matched the query. The return type will depend on the ``typeMap`` option.
Errors/Exceptions
-----------------
......
......@@ -29,14 +29,13 @@ Definition
.. include:: /includes/apiargs/MongoDBCollection-method-findOneAndReplace-option.rst
.. include:: /includes/extracts/bson-deserialization-findOneAndReplace.rst
Return Values
-------------
An object for either the original or the replaced document, depending on the
specified value of the ``returnDocument`` option. By default, the original
An array object for either the original or the replaced document, depending on
the specified value of the ``returnDocument`` option. By default, the original
document is returned. If no document matched the query, ``null`` is returned.
The return type will depend on the ``typeMap`` option.
Errors/Exceptions
-----------------
......
......@@ -29,14 +29,13 @@ Definition
.. include:: /includes/apiargs/MongoDBCollection-method-findOneAndUpdate-option.rst
.. include:: /includes/extracts/bson-deserialization-findOneAndUpdate.rst
Return Values
-------------
An object for either the original or the updated document, depending on the
specified value of the ``returnDocument`` option. By default, the original
An array or object for either the original or the updated document, depending on
the specified value of the ``returnDocument`` option. By default, the original
document is returned. If no document matched the query, ``null`` is returned.
The return type will depend on the ``typeMap`` option.
Errors/Exceptions
-----------------
......
......@@ -153,10 +153,6 @@ 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
......@@ -553,14 +549,11 @@ class Collection
*
* The document to return may be null if no document matched the filter.
*
* 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
* @param array $options Command options
* @return object|null
* @return array|object|null
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
......@@ -574,6 +567,10 @@ class Collection
$options['writeConcern'] = $this->writeConcern;
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new FindOneAndDelete($this->databaseName, $this->collectionName, $filter, $options);
return $operation->execute($server);
......@@ -588,15 +585,12 @@ class Collection
* 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
* @param array|object $replacement Replacement document
* @param array $options Command options
* @return object|null
* @return array|object|null
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
......@@ -610,6 +604,10 @@ class Collection
$options['writeConcern'] = $this->writeConcern;
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new FindOneAndReplace($this->databaseName, $this->collectionName, $filter, $replacement, $options);
return $operation->execute($server);
......@@ -624,15 +622,12 @@ class Collection
* 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
* @param array|object $update Update to apply to the matched document
* @param array $options Command options
* @return object|null
* @return array|object|null
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
......@@ -646,6 +641,10 @@ class Collection
$options['writeConcern'] = $this->writeConcern;
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new FindOneAndUpdate($this->databaseName, $this->collectionName, $filter, $update, $options);
return $operation->execute($server);
......
......@@ -282,7 +282,7 @@ class Bucket
$file = $this->getRawFileDocumentForStream($stream);
// Filter the raw document through the specified type map
return \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($file), $this->typeMap);
return \MongoDB\apply_type_map_to_document($file, $this->typeMap);
}
/**
......@@ -302,7 +302,7 @@ class Bucket
* the root type so we can reliably access the ID.
*/
$typeMap = ['root' => 'stdClass'] + $this->typeMap;
$file = \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($file), $typeMap);
$file = \MongoDB\apply_type_map_to_document($file, $typeMap);
if ( ! isset($file->_id) && ! property_exists($file, '_id')) {
throw new CorruptFileException('file._id does not exist');
......
<?php
namespace MongoDB\Model;
use ArrayIterator;
/**
* Iterator for applying a type map to documents in inline command results.
*
* This iterator may be used to apply a type map to an array of documents
* returned by a database command (e.g. aggregate on servers < 2.6) and allows
* for functional equivalence with commands that return their results via a
* cursor (e.g. aggregate on servers >= 2.6).
*
* @internal
*/
class TypeMapArrayIterator extends ArrayIterator
{
private $typeMap;
/**
* Constructor.
*
* @param array $documents
* @param array $typeMap
*/
public function __construct(array $documents = [], array $typeMap)
{
parent::__construct($documents);
$this->typeMap = $typeMap;
}
/**
* Return the current element with the type map applied to it.
*
* @see http://php.net/arrayiterator.current
* @return array|object
*/
public function current()
{
return \MongoDB\apply_type_map_to_document(parent::current(), $this->typeMap);
}
}
......@@ -11,6 +11,7 @@ use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\TypeMapArrayIterator;
use ArrayIterator;
use stdClass;
use Traversable;
......@@ -72,9 +73,6 @@ class Aggregate implements Executable
* * 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 version is < 2.6).
*
* * useCursor (boolean): Indicates whether the command will request that
* the server provide results using a cursor. The default is true.
*
......@@ -206,9 +204,6 @@ 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']);
}
......@@ -222,6 +217,10 @@ class Aggregate implements Executable
throw new UnexpectedValueException('aggregate command did not return a "result" array');
}
if (isset($this->options['typeMap'])) {
return new TypeMapArrayIterator($result->result, $this->options['typeMap']);
}
return new ArrayIterator($result->result);
}
......
......@@ -60,6 +60,8 @@ class FindAndModify implements Executable
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * typeMap (array): Type map for BSON deserialization.
*
* * update (document): Update or replacement to apply to the matched
* document. This option cannot be set if the remove option is true.
*
......@@ -117,6 +119,10 @@ class FindAndModify implements Executable
throw InvalidArgumentException::invalidType('"sort" option', $options['sort'], 'array or object');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['update']) && ! is_array($options['update']) && ! is_object($options['update'])) {
throw InvalidArgumentException::invalidType('"update" option', $options['update'], 'array or object');
}
......@@ -143,7 +149,7 @@ class FindAndModify implements Executable
*
* @see Executable::execute()
* @param Server $server
* @return object|null
* @return array|object|null
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if collation or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
......@@ -180,6 +186,10 @@ class FindAndModify implements Executable
throw new UnexpectedValueException('findAndModify command did not return a "value" document');
}
if (isset($this->options['typeMap'])) {
return \MongoDB\apply_type_map_to_document($result->value, $this->options['typeMap']);
}
return $result->value;
}
......
......@@ -37,6 +37,8 @@ class FindOneAndDelete implements Executable
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * typeMap (array): Type map for BSON deserialization.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* This is not supported for server versions < 3.2 and will result in an
......@@ -76,7 +78,7 @@ class FindOneAndDelete implements Executable
*
* @see Executable::execute()
* @param Server $server
* @return object|null
* @return array|object|null
* @throws UnsupportedException if collation or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
......
......@@ -49,6 +49,8 @@ class FindOneAndReplace implements Executable
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * typeMap (array): Type map for BSON deserialization.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
......@@ -116,7 +118,7 @@ class FindOneAndReplace implements Executable
*
* @see Executable::execute()
* @param Server $server
* @return object|null
* @return array|object|null
* @throws UnsupportedException if collation or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
......
......@@ -49,6 +49,8 @@ class FindOneAndUpdate implements Executable
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * typeMap (array): Type map for BSON deserialization.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
......@@ -116,7 +118,7 @@ class FindOneAndUpdate implements Executable
*
* @see Executable::execute()
* @param Server $server
* @return object|null
* @return array|object|null
* @throws UnsupportedException if collation or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
......
......@@ -9,6 +9,28 @@ use MongoDB\Driver\WriteConcern;
use MongoDB\Exception\InvalidArgumentException;
use stdClass;
/**
* Applies a type map to a document.
*
* This function is used by operations where it is not possible to apply a type
* map to the cursor directly because the root document is a command response
* (e.g. findAndModify).
*
* @internal
* @param array|object $document Document to which the type map will be applied
* @param array $typeMap Type map for BSON deserialization.
* @return array|object
* @throws InvalidArgumentException
*/
function apply_type_map_to_document($document, array $typeMap)
{
if ( ! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
return \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($document), $typeMap);
}
/**
* Extracts an ID from an inserted document.
*
......
......@@ -2,6 +2,7 @@
namespace MongoDB\Tests;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\WriteConcern;
......@@ -11,6 +12,54 @@ use MongoDB\Driver\WriteConcern;
*/
class FunctionsTest extends TestCase
{
/**
* @dataProvider provideDocumentAndTypeMap
*/
public function testApplyTypeMapToDocument($document, array $typeMap, $expectedDocument)
{
$this->assertEquals($expectedDocument, \MongoDB\apply_type_map_to_document($document, $typeMap));
}
public function provideDocumentAndTypeMap()
{
return [
[
[
'x' => 1,
'y' => (object) ['foo' => 'bar'],
'z' => [1, 2, 3],
],
[
'root' => 'object',
'document' => 'stdClass',
'array' => 'array',
],
(object) [
'x' => 1,
'y' => (object) ['foo' => 'bar'],
'z' => [1, 2, 3],
],
],
[
[
'x' => 1,
'y' => (object) ['foo' => 'bar'],
'z' => [1, 2, 3],
],
[
'root' => 'MongoDB\Model\BSONDocument',
'document' => 'MongoDB\Model\BSONDocument',
'array' => 'MongoDB\Model\BSONArray',
],
new BSONDocument([
'x' => 1,
'y' => new BSONDocument(['foo' => 'bar']),
'z' => new BSONArray([1, 2, 3]),
]),
],
];
}
/**
* @dataProvider provideIndexSpecificationDocumentsAndGeneratedNames
*/
......
......@@ -21,24 +21,27 @@ class AggregateFunctionalTest extends FunctionalTestCase
/**
* @dataProvider provideTypeMapOptionsAndExpectedDocument
*/
public function testTypeMapOption(array $typeMap, array $expectedDocuments)
public function testTypeMapOption(array $typeMap = null, 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());
$results = iterator_to_array($operation->execute($this->getPrimaryServer()));
$this->assertEquals($expectedDocuments, $cursor->toArray());
$this->assertEquals($expectedDocuments, $results);
}
public function provideTypeMapOptionsAndExpectedDocument()
{
return [
[
null,
[
(object) ['_id' => 1, 'x' => (object) ['foo' => 'bar']],
(object) ['_id' => 3, 'x' => (object) ['foo' => 'bar']],
],
],
[
['root' => 'array', 'document' => 'array'],
[
......
<?php
namespace MongoDB\Tests\Operation;
use MongoDB\Driver\BulkWrite;
use MongoDB\Model\BSONDocument;
use MongoDB\Operation\FindAndModify;
class FindAndModifyFunctionalTest extends FunctionalTestCase
{
/**
* @dataProvider provideTypeMapOptionsAndExpectedDocument
*/
public function testTypeMapOption(array $typeMap = null, $expectedDocument)
{
$this->createFixtures(1);
$operation = new FindAndModify(
$this->getDatabaseName(),
$this->getCollectionName(),
[
'remove' => true,
'typeMap' => $typeMap,
]
);
$document = $operation->execute($this->getPrimaryServer());
$this->assertEquals($expectedDocument, $document);
}
public function provideTypeMapOptionsAndExpectedDocument()
{
return [
[
null,
(object) ['_id' => 1, 'x' => (object) ['foo' => 'bar']],
],
[
['root' => 'array', 'document' => 'array'],
['_id' => 1, 'x' => ['foo' => 'bar']],
],
[
['root' => 'object', 'document' => 'array'],
(object) ['_id' => 1, 'x' => ['foo' => 'bar']],
],
[
['root' => 'array', 'document' => 'stdClass'],
['_id' => 1, 'x' => (object) ['foo' => 'bar']],
],
[
['root' => 'MongoDB\Model\BSONDocument', 'document' => 'object'],
new BSONDocument(['_id' => 1, '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());
}
}
......@@ -51,6 +51,10 @@ class FindAndModifyTest extends TestCase
$options[][] = ['sort' => $value];
}
foreach ($this->getInvalidArrayValues() as $value) {
$options[][] = ['typeMap' => $value];
}
foreach ($this->getInvalidDocumentValues() as $value) {
$options[][] = ['update' => $value];
}
......
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