Skip to content

Commit fb0d387

Browse files
committed
PHPLIB-130: Support readConcern option on read operations
1 parent e468266 commit fb0d387

File tree

13 files changed

+150
-4
lines changed

13 files changed

+150
-4
lines changed

src/Collection.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,20 @@ public function __toString()
141141
*/
142142
public function aggregate(array $pipeline, array $options = [])
143143
{
144+
$hasOutStage = \MongoDB\is_last_pipeline_operator_out($pipeline);
145+
146+
/* A "majority" read concern is not compatible with the $out stage, so
147+
* avoid providing the Collection's read concern if it would conflict.
148+
*/
149+
if ( ! isset($options['readConcern']) && ! ($hasOutStage && $this->readConcern->getLevel() === ReadConcern::MAJORITY)) {
150+
$options['readConcern'] = $this->readConcern;
151+
}
152+
144153
if ( ! isset($options['readPreference'])) {
145154
$options['readPreference'] = $this->readPreference;
146155
}
147156

148-
if (\MongoDB\is_last_pipeline_operator_out($pipeline)) {
157+
if ($hasOutStage) {
149158
$options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
150159
}
151160

@@ -185,6 +194,10 @@ public function bulkWrite(array $operations, array $options = [])
185194
*/
186195
public function count($filter = [], array $options = [])
187196
{
197+
if ( ! isset($options['readConcern'])) {
198+
$options['readConcern'] = $this->readConcern;
199+
}
200+
188201
if ( ! isset($options['readPreference'])) {
189202
$options['readPreference'] = $this->readPreference;
190203
}
@@ -293,6 +306,10 @@ public function deleteOne($filter, array $options = [])
293306
*/
294307
public function distinct($fieldName, $filter = [], array $options = [])
295308
{
309+
if ( ! isset($options['readConcern'])) {
310+
$options['readConcern'] = $this->readConcern;
311+
}
312+
296313
if ( ! isset($options['readPreference'])) {
297314
$options['readPreference'] = $this->readPreference;
298315
}
@@ -361,6 +378,10 @@ public function dropIndexes()
361378
*/
362379
public function find($filter = [], array $options = [])
363380
{
381+
if ( ! isset($options['readConcern'])) {
382+
$options['readConcern'] = $this->readConcern;
383+
}
384+
364385
if ( ! isset($options['readPreference'])) {
365386
$options['readPreference'] = $this->readPreference;
366387
}
@@ -382,6 +403,10 @@ public function find($filter = [], array $options = [])
382403
*/
383404
public function findOne($filter = [], array $options = [])
384405
{
406+
if ( ! isset($options['readConcern'])) {
407+
$options['readConcern'] = $this->readConcern;
408+
}
409+
385410
if ( ! isset($options['readPreference'])) {
386411
$options['readPreference'] = $this->readPreference;
387412
}

src/Operation/Aggregate.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace MongoDB\Operation;
44

55
use MongoDB\Driver\Command;
6+
use MongoDB\Driver\ReadConcern;
67
use MongoDB\Driver\ReadPreference;
78
use MongoDB\Driver\Server;
89
use MongoDB\Exception\InvalidArgumentException;
@@ -50,6 +51,9 @@ class Aggregate implements Executable
5051
* * maxTimeMS (integer): The maximum amount of time to allow the query to
5152
* run.
5253
*
54+
* * readConcern (MongoDB\Driver\ReadConcern): Read concern. Note that a
55+
* "majority" read concern is not compatible with the $out stage.
56+
*
5357
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
5458
*
5559
* * useCursor (boolean): Indicates whether the command will request that
@@ -108,6 +112,10 @@ public function __construct($databaseName, $collectionName, array $pipeline, arr
108112
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
109113
}
110114

115+
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
116+
throw new InvalidArgumentTypeException('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
117+
}
118+
111119
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
112120
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
113121
}
@@ -183,6 +191,10 @@ private function createCommand(Server $server, $isCursorSupported)
183191
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
184192
}
185193

194+
if (isset($this->options['readConcern'])) {
195+
$cmd['readConcern'] = \MongoDB\read_concern_as_document($this->options['readConcern']);
196+
}
197+
186198
if ($this->options['useCursor']) {
187199
$cmd['cursor'] = isset($this->options["batchSize"])
188200
? ['batchSize' => $this->options["batchSize"]]

src/Operation/Count.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace MongoDB\Operation;
44

55
use MongoDB\Driver\Command;
6+
use MongoDB\Driver\ReadConcern;
67
use MongoDB\Driver\ReadPreference;
78
use MongoDB\Driver\Server;
89
use MongoDB\Exception\InvalidArgumentException;
@@ -36,6 +37,8 @@ class Count implements Executable
3637
* * maxTimeMS (integer): The maximum amount of time to allow the query to
3738
* run.
3839
*
40+
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
41+
*
3942
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
4043
*
4144
* * skip (integer): The number of documents to skip before returning the
@@ -71,6 +74,10 @@ public function __construct($databaseName, $collectionName, $filter = [], array
7174
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
7275
}
7376

77+
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
78+
throw new InvalidArgumentTypeException('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
79+
}
80+
7481
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
7582
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
7683
}
@@ -126,6 +133,10 @@ private function createCommand()
126133
}
127134
}
128135

136+
if (isset($this->options['readConcern'])) {
137+
$cmd['readConcern'] = \MongoDB\read_concern_as_document($this->options['readConcern']);
138+
}
139+
129140
return new Command($cmd);
130141
}
131142
}

src/Operation/Distinct.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace MongoDB\Operation;
44

55
use MongoDB\Driver\Command;
6+
use MongoDB\Driver\ReadConcern;
67
use MongoDB\Driver\ReadPreference;
78
use MongoDB\Driver\Server;
89
use MongoDB\Exception\InvalidArgumentException;
@@ -32,6 +33,8 @@ class Distinct implements Executable
3233
* * maxTimeMS (integer): The maximum amount of time to allow the query to
3334
* run.
3435
*
36+
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
37+
*
3538
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
3639
*
3740
* @param string $databaseName Database name
@@ -51,6 +54,10 @@ public function __construct($databaseName, $collectionName, $fieldName, $filter
5154
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
5255
}
5356

57+
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
58+
throw new InvalidArgumentTypeException('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
59+
}
60+
5461
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
5562
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
5663
}
@@ -103,6 +110,10 @@ private function createCommand()
103110
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
104111
}
105112

113+
if (isset($this->options['readConcern'])) {
114+
$cmd['readConcern'] = \MongoDB\read_concern_as_document($this->options['readConcern']);
115+
}
116+
106117
return new Command($cmd);
107118
}
108119
}

src/Operation/Find.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace MongoDB\Operation;
44

55
use MongoDB\Driver\Query;
6+
use MongoDB\Driver\ReadConcern;
67
use MongoDB\Driver\ReadPreference;
78
use MongoDB\Driver\Server;
89
use MongoDB\Exception\InvalidArgumentException;
@@ -65,6 +66,8 @@ class Find implements Executable
6566
* * projection (document): Limits the fields to return for the matching
6667
* document.
6768
*
69+
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
70+
*
6871
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
6972
*
7073
* * skip (integer): The number of documents to skip before returning.
@@ -133,6 +136,10 @@ public function __construct($databaseName, $collectionName, $filter, array $opti
133136
throw new InvalidArgumentTypeException('"projection" option', $options['projection'], 'array or object');
134137
}
135138

139+
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
140+
throw new InvalidArgumentTypeException('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
141+
}
142+
136143
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
137144
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
138145
}
@@ -188,7 +195,7 @@ private function createQuery()
188195
}
189196
}
190197

191-
foreach (['batchSize', 'limit', 'skip', 'sort', 'noCursorTimeout', 'oplogReplay', 'projection'] as $option) {
198+
foreach (['batchSize', 'limit', 'skip', 'sort', 'noCursorTimeout', 'oplogReplay', 'projection', 'readConcern'] as $option) {
192199
if (isset($this->options[$option])) {
193200
$options[$option] = $this->options[$option];
194201
}

src/Operation/FindOne.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class FindOne implements Executable
3636
* * projection (document): Limits the fields to return for the matching
3737
* document.
3838
*
39+
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
40+
*
3941
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
4042
*
4143
* * skip (integer): The number of documents to skip before returning.

src/functions.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace MongoDB;
44

5+
use MongoDB\Driver\ReadConcern;
56
use MongoDB\Driver\Server;
67
use MongoDB\Exception\InvalidArgumentTypeException;
8+
use stdClass;
79

810
/**
911
* Return whether the first key in the document starts with a "$" character.
@@ -81,6 +83,25 @@ function generate_index_name($document)
8183
return $name;
8284
}
8385

86+
/**
87+
* Converts a ReadConcern instance to a stdClass for use in a BSON document.
88+
*
89+
* @internal
90+
* @see https://jira.mongodb.org/browse/PHPC-498
91+
* @param ReadConcern $readConcern Read concern
92+
* @return stdClass
93+
*/
94+
function read_concern_as_document(ReadConcern $readConcern)
95+
{
96+
$document = [];
97+
98+
if ($readConcern->getLevel() !== null) {
99+
$document['level'] = $readConcern->getLevel();
100+
}
101+
102+
return (object) $document;
103+
}
104+
84105
/**
85106
* Return whether the server supports a particular feature.
86107
*

tests/FunctionsTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace MongoDB\Tests;
4+
5+
use MongoDB\Driver\ReadConcern;
6+
7+
/**
8+
* Unit tests for utility functions.
9+
*/
10+
class FunctionsTest extends \PHPUnit_Framework_TestCase
11+
{
12+
/**
13+
* @dataProvider provideReadConcernsAndDocuments
14+
*/
15+
public function testReadConcernAsDocument(ReadConcern $readConcern, $expectedDocument)
16+
{
17+
$this->assertEquals($expectedDocument, \MongoDB\read_concern_as_document($readConcern));
18+
}
19+
20+
public function provideReadConcernsAndDocuments()
21+
{
22+
return [
23+
[ new ReadConcern, (object) [] ],
24+
[ new ReadConcern(ReadConcern::LOCAL), (object) ['level' => ReadConcern::LOCAL] ],
25+
[ new ReadConcern(ReadConcern::MAJORITY), (object) ['level' => ReadConcern::MAJORITY] ],
26+
];
27+
}
28+
}

tests/Operation/AggregateTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ public function provideInvalidConstructorOptions()
5353
$options[][] = ['maxTimeMS' => $value];
5454
}
5555

56+
foreach ($this->getInvalidReadConcernValues() as $value) {
57+
$options[][] = ['readConcern' => $value];
58+
}
59+
5660
foreach ($this->getInvalidReadPreferenceValues() as $value) {
5761
$options[][] = ['readPreference' => $value];
5862
}

tests/Operation/CountTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ public function provideInvalidConstructorOptions()
4040
$options[][] = ['maxTimeMS' => $value];
4141
}
4242

43+
foreach ($this->getInvalidReadConcernValues() as $value) {
44+
$options[][] = ['readConcern' => $value];
45+
}
46+
4347
foreach ($this->getInvalidReadPreferenceValues() as $value) {
4448
$options[][] = ['readPreference' => $value];
4549
}

tests/Operation/DistinctTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ public function provideInvalidConstructorOptions()
3232
$options[][] = ['maxTimeMS' => $value];
3333
}
3434

35+
foreach ($this->getInvalidReadConcernValues() as $value) {
36+
$options[][] = ['readConcern' => $value];
37+
}
38+
3539
foreach ($this->getInvalidReadPreferenceValues() as $value) {
3640
$options[][] = ['readPreference' => $value];
3741
}

tests/Operation/FindTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public function provideInvalidConstructorOptions()
6464
$options[][] = ['projection' => $value];
6565
}
6666

67+
foreach ($this->getInvalidReadConcernValues() as $value) {
68+
$options[][] = ['readConcern' => $value];
69+
}
70+
6771
foreach ($this->getInvalidReadPreferenceValues() as $value) {
6872
$options[][] = ['readPreference' => $value];
6973
}

tests/TestCase.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace MongoDB\Tests;
44

5+
use MongoDB\Driver\ReadConcern;
6+
use MongoDB\Driver\ReadPreference;
7+
use MongoDB\Driver\WriteConcern;
58
use ReflectionClass;
69
use stdClass;
710

@@ -74,14 +77,24 @@ protected function getInvalidIntegerValues()
7477
return [3.14, 'foo', true, [], new stdClass];
7578
}
7679

80+
/**
81+
* Return a list of invalid ReadPreference values.
82+
*
83+
* @return array
84+
*/
85+
protected function getInvalidReadConcernValues()
86+
{
87+
return [123, 3.14, 'foo', true, [], new stdClass, new ReadPreference(ReadPreference::RP_PRIMARY), new WriteConcern(1)];
88+
}
89+
7790
/**
7891
* Return a list of invalid ReadPreference values.
7992
*
8093
* @return array
8194
*/
8295
protected function getInvalidReadPreferenceValues()
8396
{
84-
return [123, 3.14, 'foo', true, [], new stdClass];
97+
return [123, 3.14, 'foo', true, [], new stdClass, new ReadConcern, new WriteConcern(1)];
8598
}
8699

87100
/**
@@ -101,7 +114,7 @@ protected function getInvalidStringValues()
101114
*/
102115
protected function getInvalidWriteConcernValues()
103116
{
104-
return [123, 3.14, 'foo', true, [], new stdClass];
117+
return [123, 3.14, 'foo', true, [], new stdClass, new ReadConcern, new ReadPreference(ReadPreference::RP_PRIMARY)];
105118
}
106119

107120
/**

0 commit comments

Comments
 (0)