Unverified Commit f9493e7e authored by Andreas Braun's avatar Andreas Braun

Merge branch 'v1.5'

* v1.5:
  PHPLIB-485: Inherit collection's typeMap in distinct
  PHPLIB-481: Add functional test for pipeline-style bulk updates
  PHPLIB-481: Remove duplicated bulk write tests
  PHPLIB-481: Add the ability to specify a pipeline to an update within bulk write
parents 06a0000f 32cfb9bb
......@@ -470,6 +470,10 @@ class Collection
$options['readPreference'] = $this->readPreference;
}
if (! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = select_server($this->manager, $options);
if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
......
......@@ -33,6 +33,7 @@ use function is_bool;
use function is_object;
use function key;
use function MongoDB\is_first_key_operator;
use function MongoDB\is_pipeline;
use function MongoDB\server_supports_feature;
use function sprintf;
......@@ -255,8 +256,8 @@ class BulkWrite implements Executable
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object');
}
if (! is_first_key_operator($args[1])) {
throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is not an update operator', $i, $type));
if (! is_first_key_operator($args[1]) && ! is_pipeline($args[1])) {
throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is neither an update operator nor a pipeline', $i, $type));
}
if (! isset($args[2])) {
......
......@@ -16,7 +16,10 @@ use MongoDB\Operation\Count;
use MongoDB\Tests\CommandObserver;
use function array_filter;
use function call_user_func;
use function is_scalar;
use function json_encode;
use function strchr;
use function usort;
use function version_compare;
/**
......@@ -170,6 +173,88 @@ class CollectionFunctionalTest extends FunctionalTestCase
);
}
/**
* @dataProvider provideTypeMapOptionsAndExpectedDocuments
*/
public function testDistinctWithTypeMap(array $typeMap, array $expectedDocuments)
{
$bulkWrite = new BulkWrite(['ordered' => true]);
$bulkWrite->insert([
'x' => (object) ['foo' => 'bar'],
]);
$bulkWrite->insert(['x' => 4]);
$bulkWrite->insert([
'x' => (object) ['foo' => ['foo' => 'bar']],
]);
$this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite);
$values = $this->collection->withOptions(['typeMap' => $typeMap])->distinct('x');
/* This sort callable sorts all scalars to the front of the list. All
* non-scalar values are sorted by running json_encode on them and
* comparing their string representations.
*/
$sort = function ($a, $b) {
if (is_scalar($a) && ! is_scalar($b)) {
return -1;
}
if (! is_scalar($a)) {
if (is_scalar($b)) {
return 1;
}
$a = json_encode($a);
$b = json_encode($b);
}
return $a < $b ? -1 : 1;
};
usort($expectedDocuments, $sort);
usort($values, $sort);
$this->assertEquals($expectedDocuments, $values);
}
public function provideTypeMapOptionsAndExpectedDocuments()
{
return [
'No type map' => [
['root' => 'array', 'document' => 'array'],
[
['foo' => 'bar'],
4,
['foo' => ['foo' => 'bar']],
],
],
'array/array' => [
['root' => 'array', 'document' => 'array'],
[
['foo' => 'bar'],
4,
['foo' => ['foo' => 'bar']],
],
],
'object/array' => [
['root' => 'object', 'document' => 'array'],
[
(object) ['foo' => 'bar'],
4,
(object) ['foo' => ['foo' => 'bar']],
],
],
'array/stdClass' => [
['root' => 'array', 'document' => 'stdClass'],
[
['foo' => 'bar'],
4,
['foo' => (object) ['foo' => 'bar']],
],
],
];
}
public function testDrop()
{
$writeResult = $this->collection->insertOne(['x' => 1]);
......
......@@ -8,7 +8,6 @@ use MongoDB\Collection;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\WriteConcern;
use MongoDB\Exception\BadMethodCallException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Model\BSONDocument;
use MongoDB\Operation\BulkWrite;
use MongoDB\Tests\CommandObserver;
......@@ -226,67 +225,6 @@ class BulkWriteFunctionalTest extends FunctionalTestCase
$result->getUpsertedIds();
}
public function testUnknownOperation()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Unknown operation type "foo" in $operations[0]');
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
['foo' => [['_id' => 1]]],
]);
}
/**
* @dataProvider provideOpsWithMissingArguments
*/
public function testMissingArguments(array $ops)
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessageRegExp('/Missing (first|second) argument for \$operations\[\d+\]\["\w+\"]/');
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), $ops);
}
public function provideOpsWithMissingArguments()
{
return [
[[['insertOne' => []]]],
[[['updateOne' => []]]],
[[['updateOne' => [['_id' => 1]]]]],
[[['updateMany' => []]]],
[[['updateMany' => [['_id' => 1]]]]],
[[['replaceOne' => []]]],
[[['replaceOne' => [['_id' => 1]]]]],
[[['deleteOne' => []]]],
[[['deleteMany' => []]]],
];
}
public function testUpdateOneRequiresUpdateOperators()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('First key in $operations[0]["updateOne"][1] is not an update operator');
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
['updateOne' => [['_id' => 1], ['x' => 1]]],
]);
}
public function testUpdateManyRequiresUpdateOperators()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('First key in $operations[0]["updateMany"][1] is not an update operator');
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
['updateMany' => [['_id' => ['$gt' => 1]], ['x' => 1]]],
]);
}
public function testReplaceOneRequiresReplacementDocument()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('First key in $operations[0]["replaceOne"][1] is an update operator');
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
['replaceOne' => [['_id' => 1], ['$inc' => ['x' => 1]]]],
]);
}
public function testSessionOption()
{
if (version_compare($this->getServerVersion(), '3.6.0', '<')) {
......@@ -357,6 +295,36 @@ class BulkWriteFunctionalTest extends FunctionalTestCase
);
}
public function testBulkWriteWithPipelineUpdates()
{
if (version_compare($this->getServerVersion(), '4.2.0', '<')) {
$this->markTestSkipped('Pipeline-style updates are not supported');
}
$this->createFixtures(4);
$ops = [
['updateOne' => [['_id' => 2], [['$addFields' => ['y' => 2]]]]],
['updateMany' => [['_id' => ['$gt' => 2]], [['$addFields' => ['y' => '$_id']]]]],
];
$operation = new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), $ops);
$result = $operation->execute($this->getPrimaryServer());
$this->assertInstanceOf(BulkWriteResult::class, $result);
$this->assertSame(3, $result->getMatchedCount());
$this->assertSame(3, $result->getModifiedCount());
$expected = [
['_id' => 1, 'x' => 11],
['_id' => 2, 'x' => 22, 'y' => 2],
['_id' => 3, 'x' => 33, 'y' => 3],
['_id' => 4, 'x' => 44, 'y' => 4],
];
$this->assertSameDocuments($expected, $this->collection->find());
}
/**
* Create data fixtures.
*
......
......@@ -258,10 +258,10 @@ class BulkWriteTest extends TestCase
]);
}
public function testUpdateManyUpdateArgumentRequiresOperators()
public function testUpdateManyUpdateArgumentRequiresOperatorsOrPipeline()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('First key in $operations[0]["updateMany"][1] is not an update operator');
$this->expectExceptionMessage('First key in $operations[0]["updateMany"][1] is neither an update operator nor a pipeline');
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
[BulkWrite::UPDATE_MANY => [['_id' => ['$gt' => 1]], ['x' => 1]]],
]);
......@@ -345,10 +345,10 @@ class BulkWriteTest extends TestCase
]);
}
public function testUpdateOneUpdateArgumentRequiresOperators()
public function testUpdateOneUpdateArgumentRequiresOperatorsOrPipeline()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('First key in $operations[0]["updateOne"][1] is not an update operator');
$this->expectExceptionMessage('First key in $operations[0]["updateOne"][1] is neither an update operator nor a pipeline');
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
[BulkWrite::UPDATE_ONE => [['_id' => 1], ['x' => 1]]],
]);
......
......@@ -238,6 +238,171 @@
]
}
}
},
{
"description": "UpdateOne in bulk write using pipelines",
"operations": [
{
"name": "bulkWrite",
"arguments": {
"requests": [
{
"name": "updateOne",
"arguments": {
"filter": {
"_id": 1
},
"update": [
{
"$replaceRoot": {
"newRoot": "$t"
}
},
{
"$addFields": {
"foo": 1
}
}
]
}
}
]
},
"result": {
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test",
"updates": [
{
"q": {
"_id": 1
},
"u": [
{
"$replaceRoot": {
"newRoot": "$t"
}
},
{
"$addFields": {
"foo": 1
}
}
]
}
]
},
"command_name": "update",
"database_name": "crud-tests"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"u": {
"v": 1
},
"foo": 1
},
{
"_id": 2,
"x": 2,
"y": 1
}
]
}
}
},
{
"description": "UpdateMany in bulk write using pipelines",
"operations": [
{
"name": "bulkWrite",
"arguments": {
"requests": [
{
"name": "updateMany",
"arguments": {
"filter": {},
"update": [
{
"$project": {
"x": 1
}
},
{
"$addFields": {
"foo": 1
}
}
]
}
}
]
},
"result": {
"matchedCount": 2,
"modifiedCount": 2,
"upsertedCount": 0
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test",
"updates": [
{
"q": {},
"u": [
{
"$project": {
"x": 1
}
},
{
"$addFields": {
"foo": 1
}
}
],
"multi": true
}
]
},
"command_name": "update",
"database_name": "crud-tests"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 1,
"foo": 1
},
{
"_id": 2,
"x": 2,
"foo": 1
}
]
}
}
}
]
}
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