Unverified Commit 7047798f authored by Benjamin Eberlei's avatar Benjamin Eberlei Committed by GitHub

Merge pull request #4054 from beberlei/GH-4052-PrimaryReplicaConnection

[GH-4052] Deprecate MasterSlaveConnection and rename to PrimaryReplicaConnection
parents 3cfb57d4 071824e7
# Upgrade to 2.11
## Deprecated `MasterSlaveConnection` use `PrimaryReadReplicaConnection`
The `Doctrine\DBAL\Connections\MasterSlaveConnection` class is renamed to `Doctrine\DBAL\Connections\PrimaryReadReplicaConnection`.
In addition its configuration parameters `master`, `slaves` and `keepSlave` are renamed to `primary`, `replica` and `keepReplica`.
Before:
$connection = DriverManager::getConnection(
'wrapperClass' => 'Doctrine\DBAL\Connections\MasterSlaveConnection',
'driver' => 'pdo_mysql',
'master' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''),
'slaves' => array(
array('user' => 'replica1', 'password', 'host' => '', 'dbname' => ''),
array('user' => 'replica2', 'password', 'host' => '', 'dbname' => ''),
),
'keepSlave' => true,
));
$connection->connect('slave');
$connection->connect('master');
$connection->isConnectedToMaster();
After:
$connection = DriverManager::getConnection(array(
'wrapperClass' => 'Doctrine\DBAL\Connections\PrimaryReadReplicaConnection',
'driver' => 'pdo_mysql',
'primary' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''),
'replica' => array(
array('user' => 'replica1', 'password', 'host' => '', 'dbname' => ''),
array('user' => 'replica2', 'password', 'host' => '', 'dbname' => ''),
)
'keepReplica' => true,
));
$connection->ensureConnectedToReplica();
$connection->ensureConnectedToPrimary();
$connection->isConnectedToPrimary();
## Deprecated `ArrayStatement` and `ResultCacheStatement` classes.
The `ArrayStatement` and `ResultCacheStatement` classes are deprecated. In a future major release they will be renamed and marked internal as implementation details of the caching layer.
......
This diff is collapsed.
......@@ -142,17 +142,29 @@ final class DriverManager
$params = self::parseDatabaseUrl($params);
// URL support for MasterSlaveConnection
// @todo: deprecated, notice thrown by connection constructor
if (isset($params['master'])) {
$params['master'] = self::parseDatabaseUrl($params['master']);
}
// @todo: deprecated, notice thrown by connection constructor
if (isset($params['slaves'])) {
foreach ($params['slaves'] as $key => $slaveParams) {
$params['slaves'][$key] = self::parseDatabaseUrl($slaveParams);
}
}
// URL support for PrimaryReplicaConnection
if (isset($params['primary'])) {
$params['primary'] = self::parseDatabaseUrl($params['primary']);
}
if (isset($params['replica'])) {
foreach ($params['replica'] as $key => $replicaParams) {
$params['replica'][$key] = self::parseDatabaseUrl($replicaParams);
}
}
// URL support for PoolingShardConnection
if (isset($params['global'])) {
$params['global'] = self::parseDatabaseUrl($params['global']);
......
......@@ -3,7 +3,7 @@
namespace Doctrine\Tests\DBAL;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Connections\MasterSlaveConnection;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver as DrizzlePDOMySqlDriver;
......@@ -139,15 +139,15 @@ class DriverManagerTest extends DbalTestCase
self::assertInstanceOf(PDOMySQLDriver::class, $conn->getDriver());
}
public function testDatabaseUrlMasterSlave(): void
public function testDatabaseUrlPrimaryReplica(): void
{
$options = [
'driver' => 'pdo_mysql',
'master' => ['url' => 'mysql://foo:bar@localhost:11211/baz'],
'slaves' => [
'slave1' => ['url' => 'mysql://foo:bar@localhost:11211/baz_slave'],
'primary' => ['url' => 'mysql://foo:bar@localhost:11211/baz'],
'replica' => [
'replica1' => ['url' => 'mysql://foo:bar@localhost:11211/baz_replica'],
],
'wrapperClass' => MasterSlaveConnection::class,
'wrapperClass' => PrimaryReadReplicaConnection::class,
];
$conn = DriverManager::getConnection($options);
......@@ -163,12 +163,12 @@ class DriverManagerTest extends DbalTestCase
];
foreach ($expected as $key => $value) {
self::assertEquals($value, $params['master'][$key]);
self::assertEquals($value, $params['slaves']['slave1'][$key]);
self::assertEquals($value, $params['primary'][$key]);
self::assertEquals($value, $params['replica']['replica1'][$key]);
}
self::assertEquals('baz', $params['master']['dbname']);
self::assertEquals('baz_slave', $params['slaves']['slave1']['dbname']);
self::assertEquals('baz', $params['primary']['dbname']);
self::assertEquals('baz_replica', $params['replica']['replica1']['dbname']);
}
public function testDatabaseUrlShard(): void
......
<?php
namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Schema\Table;
use Doctrine\Tests\DbalFunctionalTestCase;
use Throwable;
use function array_change_key_case;
use function sprintf;
use function strlen;
use function strtolower;
use function substr;
use const CASE_LOWER;
/**
* @group DBAL-20
*/
class PrimaryReadReplicaConnectionTest extends DbalFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$platformName = $this->connection->getDatabasePlatform()->getName();
// This is a MySQL specific test, skip other vendors.
if ($platformName !== 'mysql') {
$this->markTestSkipped(sprintf('Test does not work on %s.', $platformName));
}
try {
$table = new Table('primary_replica_table');
$table->addColumn('test_int', 'integer');
$table->setPrimaryKey(['test_int']);
$sm = $this->connection->getSchemaManager();
$sm->createTable($table);
} catch (Throwable $e) {
}
$this->connection->executeUpdate('DELETE FROM primary_replica_table');
$this->connection->insert('primary_replica_table', ['test_int' => 1]);
}
private function createPrimaryReadReplicaConnection(bool $keepReplica = false): PrimaryReadReplicaConnection
{
return DriverManager::getConnection($this->createPrimaryReadReplicaConnectionParams($keepReplica));
}
/**
* @return mixed[]
*/
private function createPrimaryReadReplicaConnectionParams(bool $keepReplica = false): array
{
$params = $this->connection->getParams();
$params['primary'] = $params;
$params['replica'] = [$params, $params];
$params['keepReplica'] = $keepReplica;
$params['wrapperClass'] = PrimaryReadReplicaConnection::class;
return $params;
}
public function testInheritCharsetFromPrimary(): void
{
$charsets = [
'utf8',
'latin1',
];
foreach ($charsets as $charset) {
$params = $this->createPrimaryReadReplicaConnectionParams();
$params['primary']['charset'] = $charset;
foreach ($params['replica'] as $index => $replicaParams) {
if (! isset($replicaParams['charset'])) {
continue;
}
unset($params['replica'][$index]['charset']);
}
$conn = DriverManager::getConnection($params);
self::assertInstanceOf(PrimaryReadReplicaConnection::class, $conn);
$conn->ensureConnectedToReplica();
self::assertFalse($conn->isConnectedToPrimary());
$clientCharset = $conn->fetchColumn('select @@character_set_client as c');
self::assertSame(
$charset,
substr(strtolower($clientCharset), 0, strlen($charset))
);
}
}
public function testPrimaryOnConnect(): void
{
$conn = $this->createPrimaryReadReplicaConnection();
self::assertFalse($conn->isConnectedToPrimary());
$conn->ensureConnectedToReplica();
self::assertFalse($conn->isConnectedToPrimary());
$conn->ensureConnectedToPrimary();
self::assertTrue($conn->isConnectedToPrimary());
}
public function testNoPrimaryrOnExecuteQuery(): void
{
$conn = $this->createPrimaryReadReplicaConnection();
$sql = 'SELECT count(*) as num FROM primary_replica_table';
$data = $conn->fetchAll($sql);
$data[0] = array_change_key_case($data[0], CASE_LOWER);
self::assertEquals(1, $data[0]['num']);
self::assertFalse($conn->isConnectedToPrimary());
}
public function testPrimaryOnWriteOperation(): void
{
$conn = $this->createPrimaryReadReplicaConnection();
$conn->insert('primary_replica_table', ['test_int' => 30]);
self::assertTrue($conn->isConnectedToPrimary());
$sql = 'SELECT count(*) as num FROM primary_replica_table';
$data = $conn->fetchAll($sql);
$data[0] = array_change_key_case($data[0], CASE_LOWER);
self::assertEquals(2, $data[0]['num']);
self::assertTrue($conn->isConnectedToPrimary());
}
/**
* @group DBAL-335
*/
public function testKeepReplicaBeginTransactionStaysOnPrimary(): void
{
$conn = $this->createPrimaryReadReplicaConnection($keepReplica = true);
$conn->ensureConnectedToReplica();
$conn->beginTransaction();
$conn->insert('primary_replica_table', ['test_int' => 30]);
$conn->commit();
self::assertTrue($conn->isConnectedToPrimary());
$conn->connect();
self::assertTrue($conn->isConnectedToPrimary());
$conn->ensureConnectedToReplica();
self::assertFalse($conn->isConnectedToPrimary());
}
/**
* @group DBAL-335
*/
public function testKeepReplicaInsertStaysOnPrimary(): void
{
$conn = $this->createPrimaryReadReplicaConnection($keepReplica = true);
$conn->ensureConnectedToReplica();
$conn->insert('primary_replica_table', ['test_int' => 30]);
self::assertTrue($conn->isConnectedToPrimary());
$conn->connect();
self::assertTrue($conn->isConnectedToPrimary());
$conn->ensureConnectedToReplica();
self::assertFalse($conn->isConnectedToPrimary());
}
public function testPrimaryReadReplicaConnectionCloseAndReconnect(): void
{
$conn = $this->createPrimaryReadReplicaConnection();
$conn->ensureConnectedToPrimary();
self::assertTrue($conn->isConnectedToPrimary());
$conn->close();
self::assertFalse($conn->isConnectedToPrimary());
$conn->ensureConnectedToPrimary();
self::assertTrue($conn->isConnectedToPrimary());
}
public function testQueryOnPrimary(): void
{
$conn = $this->createPrimaryReadReplicaConnection();
$query = 'SELECT count(*) as num FROM primary_replica_table';
$statement = $conn->query($query);
self::assertInstanceOf(Statement::class, $statement);
//Query must be executed only on Primary
self::assertTrue($conn->isConnectedToPrimary());
$data = $statement->fetchAll();
//Default fetchmode is FetchMode::ASSOCIATIVE
self::assertArrayHasKey(0, $data);
self::assertArrayHasKey('num', $data[0]);
//Could be set in other fetchmodes
self::assertArrayNotHasKey(0, $data[0]);
self::assertEquals(1, $data[0]['num']);
}
public function testQueryOnReplica(): void
{
$conn = $this->createPrimaryReadReplicaConnection();
$conn->ensureConnectedToReplica();
$query = 'SELECT count(*) as num FROM primary_replica_table';
$statement = $conn->query($query);
self::assertInstanceOf(Statement::class, $statement);
//Query must be executed only on Primary, even when we connect to the replica
self::assertTrue($conn->isConnectedToPrimary());
$data = $statement->fetchAll();
//Default fetchmode is FetchMode::ASSOCIATIVE
self::assertArrayHasKey(0, $data);
self::assertArrayHasKey('num', $data[0]);
//Could be set in other fetchmodes
self::assertArrayNotHasKey(0, $data[0]);
self::assertEquals(1, $data[0]['num']);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment