Skip to content

Commit eee5799

Browse files
Merge pull request #45541 from nextcloud/backport/45013/stable29
[stable29] fix(db): Prevent two connections for single node databases
2 parents 782c7ef + e37dbaa commit eee5799

2 files changed

Lines changed: 112 additions & 1 deletion

File tree

lib/private/DB/Connection.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
use OCP\Profiler\IProfiler;
5858
use Psr\Clock\ClockInterface;
5959
use Psr\Log\LoggerInterface;
60+
use function count;
6061
use function in_array;
6162

6263
class Connection extends PrimaryReadReplicaConnection {
@@ -96,7 +97,7 @@ class Connection extends PrimaryReadReplicaConnection {
9697
* @throws \Exception
9798
*/
9899
public function __construct(
99-
array $params,
100+
private array $params,
100101
Driver $driver,
101102
?Configuration $config = null,
102103
?EventManager $eventManager = null
@@ -156,6 +157,15 @@ public function connect($connectionName = null) {
156157
}
157158
}
158159

160+
protected function performConnect(?string $connectionName = null): bool {
161+
if (($connectionName ?? 'replica') === 'replica'
162+
&& count($this->params['replica']) === 1
163+
&& $this->params['primary'] === $this->params['replica'][0]) {
164+
return parent::performConnect('primary');
165+
}
166+
return parent::performConnect($connectionName);
167+
}
168+
159169
public function getStats(): array {
160170
return [
161171
'built' => $this->queriesBuilt,

tests/lib/DB/ConnectionTest.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace Test\DB;
11+
12+
use Doctrine\DBAL\Configuration;
13+
use Doctrine\DBAL\Driver;
14+
use Doctrine\DBAL\Driver\Connection as DriverConnection;
15+
use Doctrine\DBAL\Platforms\MySQLPlatform;
16+
use OC\DB\Adapter;
17+
use OC\DB\Connection;
18+
use Test\TestCase;
19+
20+
/**
21+
* @group DB
22+
*/
23+
class ConnectionTest extends TestCase {
24+
25+
public function testSingleNodeConnectsToPrimaryOnly(): void {
26+
$connectionParams = [
27+
'user' => 'test',
28+
'password' => 'topsecret',
29+
'host' => 'test',
30+
];
31+
$adapter = $this->createMock(Adapter::class);
32+
$driver = $this->createMock(Driver::class);
33+
$configuration = $this->createMock(Configuration::class);
34+
$connection = $this->getMockBuilder(Connection::class)
35+
->onlyMethods(['connectTo'])
36+
->setConstructorArgs([
37+
[
38+
'adapter' => $adapter,
39+
'platform' => new MySQLPlatform(),
40+
'tablePrefix' => 'nctest',
41+
'primary' => $connectionParams,
42+
'replica' => [
43+
$connectionParams,
44+
],
45+
],
46+
$driver,
47+
$configuration,
48+
])
49+
->getMock();
50+
$driverConnection = $this->createMock(DriverConnection::class);
51+
$connection->expects(self::once())
52+
->method('connectTo')
53+
->with('primary')
54+
->willReturn($driverConnection);
55+
56+
$connection->ensureConnectedToReplica();
57+
$connection->ensureConnectedToPrimary();
58+
$connection->ensureConnectedToReplica();
59+
}
60+
61+
public function testClusterConnectsToPrimaryAndReplica(): void {
62+
$connectionParamsPrimary = [
63+
'user' => 'test',
64+
'password' => 'topsecret',
65+
'host' => 'testprimary',
66+
];
67+
$connectionParamsReplica = [
68+
'user' => 'test',
69+
'password' => 'topsecret',
70+
'host' => 'testreplica',
71+
];
72+
$adapter = $this->createMock(Adapter::class);
73+
$driver = $this->createMock(Driver::class);
74+
$configuration = $this->createMock(Configuration::class);
75+
$connection = $this->getMockBuilder(Connection::class)
76+
->onlyMethods(['connectTo'])
77+
->setConstructorArgs([
78+
[
79+
'adapter' => $adapter,
80+
'platform' => new MySQLPlatform(),
81+
'tablePrefix' => 'nctest',
82+
'primary' => $connectionParamsPrimary,
83+
'replica' => [
84+
$connectionParamsReplica,
85+
],
86+
],
87+
$driver,
88+
$configuration,
89+
])
90+
->getMock();
91+
$driverConnection = $this->createMock(DriverConnection::class);
92+
$connection->expects(self::exactly(2))
93+
->method('connectTo')
94+
->willReturn($driverConnection);
95+
96+
$connection->ensureConnectedToReplica();
97+
$connection->ensureConnectedToPrimary();
98+
$connection->ensureConnectedToReplica();
99+
}
100+
101+
}

0 commit comments

Comments
 (0)