Skip to content

Commit fa01580

Browse files
committed
PHPLIB-130: Support readConcern option on read operations
1 parent 523ca61 commit fa01580

File tree

13 files changed

+155
-4
lines changed

13 files changed

+155
-4
lines changed

src/Collection.php

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

150-
if (\MongoDB\is_last_pipeline_operator_out($pipeline)) {
159+
if ($hasOutStage) {
151160
$options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
152161
}
153162

@@ -187,6 +196,10 @@ public function bulkWrite(array $operations, array $options = [])
187196
*/
188197
public function count($filter = [], array $options = [])
189198
{
199+
if ( ! isset($options['readConcern'])) {
200+
$options['readConcern'] = $this->readConcern;
201+
}
202+
190203
if ( ! isset($options['readPreference'])) {
191204
$options['readPreference'] = $this->readPreference;
192205
}
@@ -295,6 +308,10 @@ public function deleteOne($filter, array $options = [])
295308
*/
296309
public function distinct($fieldName, $filter = [], array $options = [])
297310
{
311+
if ( ! isset($options['readConcern'])) {
312+
$options['readConcern'] = $this->readConcern;
313+
}
314+
298315
if ( ! isset($options['readPreference'])) {
299316
$options['readPreference'] = $this->readPreference;
300317
}
@@ -363,6 +380,10 @@ public function dropIndexes()
363380
*/
364381
public function find($filter = [], array $options = [])
365382
{
383+
if ( ! isset($options['readConcern'])) {
384+
$options['readConcern'] = $this->readConcern;
385+
}
386+
366387
if ( ! isset($options['readPreference'])) {
367388
$options['readPreference'] = $this->readPreference;
368389
}
@@ -384,6 +405,10 @@ public function find($filter = [], array $options = [])
384405
*/
385406
public function findOne($filter = [], array $options = [])
386407
{
408+
if ( ! isset($options['readConcern'])) {
409+
$options['readConcern'] = $this->readConcern;
410+
}
411+
387412
if ( ! isset($options['readPreference'])) {
388413
$options['readPreference'] = $this->readPreference;
389414
}

src/Operation/Aggregate.php

Lines changed: 13 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;
@@ -23,6 +24,7 @@ class Aggregate implements Executable
2324
{
2425
private static $wireVersionForCursor = 2;
2526
private static $wireVersionForDocumentLevelValidation = 4;
27+
private static $wireVersionForReadConcern = 4;
2628

2729
private $databaseName;
2830
private $collectionName;
@@ -50,6 +52,9 @@ class Aggregate implements Executable
5052
* * maxTimeMS (integer): The maximum amount of time to allow the query to
5153
* run.
5254
*
55+
* * readConcern (MongoDB\Driver\ReadConcern): Read concern. Note that a
56+
* "majority" read concern is not compatible with the $out stage.
57+
*
5358
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
5459
*
5560
* * useCursor (boolean): Indicates whether the command will request that
@@ -108,6 +113,10 @@ public function __construct($databaseName, $collectionName, array $pipeline, arr
108113
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
109114
}
110115

116+
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
117+
throw new InvalidArgumentTypeException('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
118+
}
119+
111120
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
112121
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
113122
}
@@ -183,6 +192,10 @@ private function createCommand(Server $server, $isCursorSupported)
183192
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
184193
}
185194

195+
if (isset($this->options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
196+
$cmd['readConcern'] = \MongoDB\read_concern_as_document($this->options['readConcern']);
197+
}
198+
186199
if ($this->options['useCursor']) {
187200
$cmd['cursor'] = isset($this->options["batchSize"])
188201
? ['batchSize' => $this->options["batchSize"]]

src/Operation/Count.php

Lines changed: 13 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;
@@ -18,6 +19,8 @@
1819
*/
1920
class Count implements Executable
2021
{
22+
private static $wireVersionForReadConcern = 4;
23+
2124
private $databaseName;
2225
private $collectionName;
2326
private $filter;
@@ -36,6 +39,8 @@ class Count implements Executable
3639
* * maxTimeMS (integer): The maximum amount of time to allow the query to
3740
* run.
3841
*
42+
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
43+
*
3944
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
4045
*
4146
* * skip (integer): The number of documents to skip before returning the
@@ -71,6 +76,10 @@ public function __construct($databaseName, $collectionName, $filter = [], array
7176
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
7277
}
7378

79+
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
80+
throw new InvalidArgumentTypeException('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
81+
}
82+
7483
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
7584
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
7685
}
@@ -126,6 +135,10 @@ private function createCommand()
126135
}
127136
}
128137

138+
if (isset($this->options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
139+
$cmd['readConcern'] = \MongoDB\read_concern_as_document($this->options['readConcern']);
140+
}
141+
129142
return new Command($cmd);
130143
}
131144
}

src/Operation/Distinct.php

Lines changed: 13 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;
@@ -18,6 +19,8 @@
1819
*/
1920
class Distinct implements Executable
2021
{
22+
private static $wireVersionForReadConcern = 4;
23+
2124
private $databaseName;
2225
private $collectionName;
2326
private $fieldName;
@@ -32,6 +35,8 @@ class Distinct implements Executable
3235
* * maxTimeMS (integer): The maximum amount of time to allow the query to
3336
* run.
3437
*
38+
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
39+
*
3540
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
3641
*
3742
* @param string $databaseName Database name
@@ -51,6 +56,10 @@ public function __construct($databaseName, $collectionName, $fieldName, $filter
5156
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
5257
}
5358

59+
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
60+
throw new InvalidArgumentTypeException('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
61+
}
62+
5463
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
5564
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
5665
}
@@ -103,6 +112,10 @@ private function createCommand()
103112
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
104113
}
105114

115+
if (isset($this->options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
116+
$cmd['readConcern'] = \MongoDB\read_concern_as_document($this->options['readConcern']);
117+
}
118+
106119
return new Command($cmd);
107120
}
108121
}

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
}

0 commit comments

Comments
 (0)