|
19 | 19 |
|
20 | 20 | use Exception;
|
21 | 21 | use MongoDB\BSON\Serializable;
|
| 22 | +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; |
22 | 23 | use MongoDB\Driver\Manager;
|
23 | 24 | use MongoDB\Driver\ReadPreference;
|
24 | 25 | use MongoDB\Driver\Server;
|
|
42 | 43 | use function reset;
|
43 | 44 | use function substr;
|
44 | 45 |
|
| 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 | + |
45 | 72 | /**
|
46 | 73 | * Applies a type map to a document.
|
47 | 74 | *
|
@@ -459,3 +486,49 @@ function select_server(Manager $manager, array $options): Server
|
459 | 486 |
|
460 | 487 | return $manager->selectServer($readPreference);
|
461 | 488 | }
|
| 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