diff options
author | Christoph Wurst <ChristophWurst@users.noreply.github.com> | 2024-05-28 11:44:09 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-28 11:44:09 +0200 |
commit | 4a3bb050385e402cda14b27fc83c0546a3c2b73a (patch) | |
tree | 60a1cb4b7f8ca8540738f0178898319c1c887cd7 | |
parent | f0ec5489a444ced8d8794b93abf42a3de973cf54 (diff) | |
parent | 3bfba2042c0397452193e9b6f1fa4bd1596b17a6 (diff) | |
download | nextcloud-server-4a3bb050385e402cda14b27fc83c0546a3c2b73a.tar.gz nextcloud-server-4a3bb050385e402cda14b27fc83c0546a3c2b73a.zip |
Merge pull request #45013 from nextcloud/fix/db/two-primary-connections-transaction-isolation-level
fix(db): Prevent two connections for single node databases
-rw-r--r-- | lib/private/DB/Connection.php | 12 | ||||
-rw-r--r-- | tests/lib/DB/ConnectionTest.php | 101 |
2 files changed, 112 insertions, 1 deletions
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index b748312d156..8d300123a40 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -31,6 +31,7 @@ use OCP\Profiler\IProfiler; use OCP\Server; use Psr\Clock\ClockInterface; use Psr\Log\LoggerInterface; +use function count; use function in_array; class Connection extends PrimaryReadReplicaConnection { @@ -73,7 +74,7 @@ class Connection extends PrimaryReadReplicaConnection { * @throws \Exception */ public function __construct( - array $params, + private array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null @@ -138,6 +139,15 @@ class Connection extends PrimaryReadReplicaConnection { } } + protected function performConnect(?string $connectionName = null): bool { + if (($connectionName ?? 'replica') === 'replica' + && count($this->params['replica']) === 1 + && $this->params['primary'] === $this->params['replica'][0]) { + return parent::performConnect('primary'); + } + return parent::performConnect($connectionName); + } + public function getStats(): array { return [ 'built' => $this->queriesBuilt, diff --git a/tests/lib/DB/ConnectionTest.php b/tests/lib/DB/ConnectionTest.php new file mode 100644 index 00000000000..3f06082ff0c --- /dev/null +++ b/tests/lib/DB/ConnectionTest.php @@ -0,0 +1,101 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\DB; + +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Driver\Connection as DriverConnection; +use Doctrine\DBAL\Platforms\MySQLPlatform; +use OC\DB\Adapter; +use OC\DB\Connection; +use Test\TestCase; + +/** + * @group DB + */ +class ConnectionTest extends TestCase { + + public function testSingleNodeConnectsToPrimaryOnly(): void { + $connectionParams = [ + 'user' => 'test', + 'password' => 'topsecret', + 'host' => 'test', + ]; + $adapter = $this->createMock(Adapter::class); + $driver = $this->createMock(Driver::class); + $configuration = $this->createMock(Configuration::class); + $connection = $this->getMockBuilder(Connection::class) + ->onlyMethods(['connectTo']) + ->setConstructorArgs([ + [ + 'adapter' => $adapter, + 'platform' => new MySQLPlatform(), + 'tablePrefix' => 'nctest', + 'primary' => $connectionParams, + 'replica' => [ + $connectionParams, + ], + ], + $driver, + $configuration, + ]) + ->getMock(); + $driverConnection = $this->createMock(DriverConnection::class); + $connection->expects(self::once()) + ->method('connectTo') + ->with('primary') + ->willReturn($driverConnection); + + $connection->ensureConnectedToReplica(); + $connection->ensureConnectedToPrimary(); + $connection->ensureConnectedToReplica(); + } + + public function testClusterConnectsToPrimaryAndReplica(): void { + $connectionParamsPrimary = [ + 'user' => 'test', + 'password' => 'topsecret', + 'host' => 'testprimary', + ]; + $connectionParamsReplica = [ + 'user' => 'test', + 'password' => 'topsecret', + 'host' => 'testreplica', + ]; + $adapter = $this->createMock(Adapter::class); + $driver = $this->createMock(Driver::class); + $configuration = $this->createMock(Configuration::class); + $connection = $this->getMockBuilder(Connection::class) + ->onlyMethods(['connectTo']) + ->setConstructorArgs([ + [ + 'adapter' => $adapter, + 'platform' => new MySQLPlatform(), + 'tablePrefix' => 'nctest', + 'primary' => $connectionParamsPrimary, + 'replica' => [ + $connectionParamsReplica, + ], + ], + $driver, + $configuration, + ]) + ->getMock(); + $driverConnection = $this->createMock(DriverConnection::class); + $connection->expects(self::exactly(2)) + ->method('connectTo') + ->willReturn($driverConnection); + + $connection->ensureConnectedToReplica(); + $connection->ensureConnectedToPrimary(); + $connection->ensureConnectedToReplica(); + } + +} |