Skip to content

Commit 837847c

Browse files
committed
PHPLIB-476: Consider transaction readPreference in select_server
This also refactors the conditionals in extract_session_from_options and extract_read_preference_from_options to improve readability. select_server() previously did not consider the read preference of an active transaction. This isn't very significant, as transactions require a primary read preference, but it is correct to do so.
1 parent 74d19f8 commit 837847c

File tree

2 files changed

+72
-11
lines changed

2 files changed

+72
-11
lines changed

src/functions.php

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -544,11 +544,11 @@ function with_transaction(Session $session, callable $callback, array $transacti
544544
*/
545545
function extract_session_from_options(array $options): ?Session
546546
{
547-
if (! isset($options['session']) || ! $options['session'] instanceof Session) {
548-
return null;
547+
if (isset($options['session']) && $options['session'] instanceof Session) {
548+
return $options['session'];
549549
}
550550

551-
return $options['session'];
551+
return null;
552552
}
553553

554554
/**
@@ -558,33 +558,43 @@ function extract_session_from_options(array $options): ?Session
558558
*/
559559
function extract_read_preference_from_options(array $options): ?ReadPreference
560560
{
561-
if (! isset($options['readPreference']) || ! $options['readPreference'] instanceof ReadPreference) {
562-
return null;
561+
if (isset($options['readPreference']) && $options['readPreference'] instanceof ReadPreference) {
562+
return $options['readPreference'];
563563
}
564564

565-
return $options['readPreference'];
565+
return null;
566566
}
567567

568568
/**
569-
* Performs server selection, respecting the readPreference and session options
570-
* (if given)
569+
* Performs server selection, respecting the readPreference and session options.
570+
*
571+
* The pinned server for an active transaction takes priority, followed by an
572+
* operation-level read preference, followed by an active transaction's read
573+
* preference, followed by a primary read preference.
571574
*
572575
* @internal
573576
*/
574577
function select_server(Manager $manager, array $options): Server
575578
{
576579
$session = extract_session_from_options($options);
577580
$server = $session instanceof Session ? $session->getServer() : null;
581+
582+
// Pinned server for an active transaction takes priority
578583
if ($server !== null) {
579584
return $server;
580585
}
581586

587+
// Operation read preference takes priority
582588
$readPreference = extract_read_preference_from_options($options);
583-
if (! $readPreference instanceof ReadPreference) {
584-
// TODO: PHPLIB-476: Read transaction read preference once PHPC-1439 is implemented
585-
$readPreference = new ReadPreference(ReadPreference::PRIMARY);
589+
590+
// Read preference for an active transaction takes priority
591+
if ($readPreference === null && $session instanceof Session && $session->isInTransaction()) {
592+
/* Session::getTransactionOptions() should always return an array if the
593+
* session is in a transaction, but we can be defensive. */
594+
$readPreference = extract_read_preference_from_options($session->getTransactionOptions() ?? []);
586595
}
587596

597+
// Manager::selectServer() defaults to a primary read preference
588598
return $manager->selectServer($readPreference);
589599
}
590600

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\Functions;
4+
5+
use MongoDB\Driver\ReadPreference;
6+
use MongoDB\Driver\Server;
7+
use MongoDB\Tests\FunctionalTestCase;
8+
9+
use function MongoDB\select_server;
10+
11+
class SelectServerFunctionalTest extends FunctionalTestCase
12+
{
13+
/** @dataProvider providePinnedOptions */
14+
public function testSelectServerPrefersPinnedServer(array $options): void
15+
{
16+
$this->skipIfTransactionsAreNotSupported();
17+
18+
if (! $this->isShardedCluster()) {
19+
$this->markTestSkipped('Pinning requires a sharded cluster');
20+
}
21+
22+
if ($this->isLoadBalanced()) {
23+
$this->markTestSkipped('libmongoc does not pin for load-balanced topology');
24+
}
25+
26+
/* By default, the Manager under test is created with a single-mongos
27+
* URI. Explicitly create a Client with multiple mongoses. */
28+
$client = static::createTestClient(static::getUri(true));
29+
30+
// Collection must be created before the transaction starts
31+
$this->createCollection($this->getDatabaseName(), $this->getCollectionName());
32+
33+
$session = $client->startSession();
34+
$session->startTransaction();
35+
36+
$collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName());
37+
$collection->find([], ['session' => $session]);
38+
39+
$this->assertTrue($session->isInTransaction());
40+
$this->assertInstanceOf(Server::class, $session->getServer(), 'Session is pinned');
41+
$this->assertEquals($session->getServer(), select_server($client->getManager(), ['session' => $session]));
42+
}
43+
44+
public static function providePinnedOptions(): array
45+
{
46+
return [
47+
[['readPreference' => new ReadPreference(ReadPreference::PRIMARY_PREFERRED)]],
48+
[[]],
49+
];
50+
}
51+
}

0 commit comments

Comments
 (0)