Skip to content

Commit 9e0da59

Browse files
authored
PHPLIB-760: Force primary for $out/$merge if any servers are pre-5.0 (#876)
1 parent f5133ad commit 9e0da59

File tree

3 files changed

+79
-36
lines changed

3 files changed

+79
-36
lines changed

Diff for: src/Collection.php

+3-18
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,6 @@ class Collection
8787
/** @var integer */
8888
private static $wireVersionForReadConcernWithWriteStage = 8;
8989

90-
/** @var integer */
91-
private static $wireVersionForSecondarySupportsWriteStage = 13;
92-
9390
/** @var string */
9491
private $collectionName;
9592

@@ -228,21 +225,9 @@ public function aggregate(array $pipeline, array $options = [])
228225
$options['readPreference'] = $this->readPreference;
229226
}
230227

231-
$server = select_server($this->manager, $options);
232-
233-
/* If a write stage is being used with a read preference (explicit or
234-
* inherited), check that the wire version supports it. If not, force a
235-
* primary read preference and select a new server if necessary. */
236-
if (
237-
$hasWriteStage && isset($options['readPreference']) &&
238-
! server_supports_feature($server, self::$wireVersionForSecondarySupportsWriteStage)
239-
) {
240-
$options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
241-
242-
if ($server->isSecondary()) {
243-
$server = select_server($this->manager, $options);
244-
}
245-
}
228+
$server = $hasWriteStage
229+
? select_server_for_aggregate_write_stage($this->manager, $options)
230+
: select_server($this->manager, $options);
246231

247232
/* MongoDB 4.2 and later supports a read concern when an $out stage is
248233
* being used, but earlier versions do not.

Diff for: src/Database.php

+3-18
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,6 @@ class Database
6464
/** @var integer */
6565
private static $wireVersionForReadConcernWithWriteStage = 8;
6666

67-
/** @var integer */
68-
private static $wireVersionForSecondarySupportsWriteStage = 13;
69-
7067
/** @var string */
7168
private $databaseName;
7269

@@ -209,21 +206,9 @@ public function aggregate(array $pipeline, array $options = [])
209206
$options['readPreference'] = $this->readPreference;
210207
}
211208

212-
$server = select_server($this->manager, $options);
213-
214-
/* If a write stage is being used with a read preference (explicit or
215-
* inherited), check that the wire version supports it. If not, force a
216-
* primary read preference and select a new server if necessary. */
217-
if (
218-
$hasWriteStage && isset($options['readPreference']) &&
219-
! server_supports_feature($server, self::$wireVersionForSecondarySupportsWriteStage)
220-
) {
221-
$options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
222-
223-
if ($server->isSecondary()) {
224-
$server = select_server($this->manager, $options);
225-
}
226-
}
209+
$server = $hasWriteStage
210+
? select_server_for_aggregate_write_stage($this->manager, $options)
211+
: select_server($this->manager, $options);
227212

228213
/* MongoDB 4.2 and later supports a read concern when an $out stage is
229214
* being used, but earlier versions do not.

Diff for: src/functions.php

+73
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
use Exception;
2121
use MongoDB\BSON\Serializable;
22+
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
2223
use MongoDB\Driver\Manager;
2324
use MongoDB\Driver\ReadPreference;
2425
use MongoDB\Driver\Server;
@@ -42,6 +43,32 @@
4243
use function reset;
4344
use function substr;
4445

46+
/**
47+
* Check whether all servers support executing a write stage on a secondary.
48+
*
49+
* @internal
50+
* @param Server[] $servers
51+
*/
52+
function all_servers_support_write_stage_on_secondary(array $servers): bool
53+
{
54+
/* Write stages on secondaries are technically supported by FCV 4.4, but the
55+
* CRUD spec requires all 5.0+ servers since FCV is not tracked by SDAM. */
56+
static $wireVersionForWriteStageOnSecondary = 13;
57+
58+
foreach ($servers as $server) {
59+
// We can assume that load balancers only front 5.0+ servers
60+
if ($server->getType() === Server::TYPE_LOAD_BALANCER) {
61+
continue;
62+
}
63+
64+
if (! server_supports_feature($server, $wireVersionForWriteStageOnSecondary)) {
65+
return false;
66+
}
67+
}
68+
69+
return true;
70+
}
71+
4572
/**
4673
* Applies a type map to a document.
4774
*
@@ -459,3 +486,49 @@ function select_server(Manager $manager, array $options): Server
459486

460487
return $manager->selectServer($readPreference);
461488
}
489+
490+
/**
491+
* Performs server selection for an aggregate operation with a write stage. The
492+
* $options parameter may be modified by reference if a primary read preference
493+
* must be forced due to the existence of pre-5.0 servers in the topology.
494+
*
495+
* @internal
496+
* @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#aggregation-pipelines-with-write-stages
497+
*/
498+
function select_server_for_aggregate_write_stage(Manager $manager, array &$options): Server
499+
{
500+
$readPreference = extract_read_preference_from_options($options);
501+
502+
/* If there is either no read preference or a primary read preference, there
503+
* is no special server selection logic to apply. */
504+
if ($readPreference === null || $readPreference->getMode() === ReadPreference::RP_PRIMARY) {
505+
return select_server($manager, $options);
506+
}
507+
508+
$server = null;
509+
$serverSelectionError = null;
510+
511+
try {
512+
$server = select_server($manager, $options);
513+
} catch (DriverRuntimeException $serverSelectionError) {
514+
}
515+
516+
/* If any pre-5.0 servers exist in the topology, force a primary read
517+
* preference and repeat server selection if it previously failed or
518+
* selected a secondary. */
519+
if (! all_servers_support_write_stage_on_secondary($manager->getServers())) {
520+
$options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
521+
522+
if ($server === null || $server->isSecondary()) {
523+
return select_server($manager, $options);
524+
}
525+
}
526+
527+
/* If the topology only contains 5.0+ servers, we should either return the
528+
* previously selected server or propagate the server selection error. */
529+
if ($serverSelectionError !== null) {
530+
throw $serverSelectionError;
531+
}
532+
533+
return $server;
534+
}

0 commit comments

Comments
 (0)