Skip to content

Commit

Permalink
[doctrineGH-4052] Reintroduce MasterSlaveConnectionTest and fix some …
Browse files Browse the repository at this point in the history
…errors in BC layer.
  • Loading branch information
beberlei committed Jun 9, 2020
1 parent cc1d46d commit 7788fff
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 4 deletions.
6 changes: 3 additions & 3 deletions lib/Doctrine/DBAL/Connections/MasterSlaveConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ public function __construct(array $params, Driver $driver, ?Configuration $confi
if (isset($params['slaves'])) {
$this->deprecated('Params key "slaves"', '"replica"');

$params['replica'] = $params['slave'];
unset($params['slave']);
$params['replica'] = $params['slaves'];
unset($params['slaves']);
}

if (isset($params['keepSlave'])) {
Expand Down Expand Up @@ -81,7 +81,7 @@ public function connect($connectionName = null)
$this->deprecated('connect("slave")', 'ensureConnectedToReplica()');
}

return parent::connect($connectionName);
return $this->performConnect($connectionName);
}

private function deprecated(string $thing, string $instead): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public function connect($connectionName = null)
return $this->performConnect();
}

private function performConnect(?string $connectionName = null): bool
protected function performConnect(?string $connectionName = null): bool
{
$requestedConnectionChange = ($connectionName !== null);
$connectionName = $connectionName ?: 'replica';
Expand Down
243 changes: 243 additions & 0 deletions tests/Doctrine/Tests/DBAL/Functional/MasterSlaveConnectionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
<?php

namespace Doctrine\Tests\DBAL\Functional;

use Doctrine\DBAL\Connections\MasterSlaveConnection;
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 MasterSlaveConnectionTest 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('master_slave_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 master_slave_table');
$this->connection->insert('master_slave_table', ['test_int' => 1]);
}

private function createMasterSlaveConnection(bool $keepSlave = false): MasterSlaveConnection
{
return DriverManager::getConnection($this->createMasterSlaveConnectionParams($keepSlave));
}

/**
* @return mixed[]
*/
private function createMasterSlaveConnectionParams(bool $keepSlave = false): array
{
$params = $this->connection->getParams();
$params['master'] = $params;
$params['slaves'] = [$params, $params];
$params['keepSlave'] = $keepSlave;
$params['wrapperClass'] = MasterSlaveConnection::class;

return $params;
}

public function testInheritCharsetFromMaster(): void
{
$charsets = [
'utf8',
'latin1',
];

foreach ($charsets as $charset) {
$params = $this->createMasterSlaveConnectionParams();
$params['master']['charset'] = $charset;

foreach ($params['slaves'] as $index => $slaveParams) {
if (! isset($slaveParams['charset'])) {
continue;
}

unset($params['slaves'][$index]['charset']);
}

$conn = DriverManager::getConnection($params);
self::assertInstanceOf(MasterSlaveConnection::class, $conn);
$conn->connect('slave');

self::assertFalse($conn->isConnectedToMaster());

$clientCharset = $conn->fetchColumn('select @@character_set_client as c');

self::assertSame(
$charset,
substr(strtolower($clientCharset), 0, strlen($charset))
);
}
}

public function testMasterOnConnect(): void
{
$conn = $this->createMasterSlaveConnection();

self::assertFalse($conn->isConnectedToMaster());
$conn->connect('slave');
self::assertFalse($conn->isConnectedToMaster());
$conn->connect('master');
self::assertTrue($conn->isConnectedToMaster());
}

public function testNoMasterOnExecuteQuery(): void
{
$conn = $this->createMasterSlaveConnection();

$sql = 'SELECT count(*) as num FROM master_slave_table';
$data = $conn->fetchAll($sql);
$data[0] = array_change_key_case($data[0], CASE_LOWER);

self::assertEquals(1, $data[0]['num']);
self::assertFalse($conn->isConnectedToMaster());
}

public function testMasterOnWriteOperation(): void
{
$conn = $this->createMasterSlaveConnection();
$conn->insert('master_slave_table', ['test_int' => 30]);

self::assertTrue($conn->isConnectedToMaster());

$sql = 'SELECT count(*) as num FROM master_slave_table';
$data = $conn->fetchAll($sql);
$data[0] = array_change_key_case($data[0], CASE_LOWER);

self::assertEquals(2, $data[0]['num']);
self::assertTrue($conn->isConnectedToMaster());
}

/**
* @group DBAL-335
*/
public function testKeepSlaveBeginTransactionStaysOnMaster(): void
{
$conn = $this->createMasterSlaveConnection($keepSlave = true);
$conn->connect('slave');

$conn->beginTransaction();
$conn->insert('master_slave_table', ['test_int' => 30]);
$conn->commit();

self::assertTrue($conn->isConnectedToMaster());

$conn->connect();
self::assertTrue($conn->isConnectedToMaster());

$conn->connect('slave');
self::assertFalse($conn->isConnectedToMaster());
}

/**
* @group DBAL-335
*/
public function testKeepSlaveInsertStaysOnMaster(): void
{
$conn = $this->createMasterSlaveConnection($keepSlave = true);
$conn->connect('slave');

$conn->insert('master_slave_table', ['test_int' => 30]);

self::assertTrue($conn->isConnectedToMaster());

$conn->connect();
self::assertTrue($conn->isConnectedToMaster());

$conn->connect('slave');
self::assertFalse($conn->isConnectedToMaster());
}

public function testMasterSlaveConnectionCloseAndReconnect(): void
{
$conn = $this->createMasterSlaveConnection();
$conn->connect('master');
self::assertTrue($conn->isConnectedToMaster());

$conn->close();
self::assertFalse($conn->isConnectedToMaster());

$conn->connect('master');
self::assertTrue($conn->isConnectedToMaster());
}

public function testQueryOnMaster(): void
{
$conn = $this->createMasterSlaveConnection();

$query = 'SELECT count(*) as num FROM master_slave_table';

$statement = $conn->query($query);

self::assertInstanceOf(Statement::class, $statement);

//Query must be executed only on Master
self::assertTrue($conn->isConnectedToMaster());

$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 testQueryOnSlave(): void
{
$conn = $this->createMasterSlaveConnection();
$conn->connect('slave');

$query = 'SELECT count(*) as num FROM master_slave_table';

$statement = $conn->query($query);

self::assertInstanceOf(Statement::class, $statement);

//Query must be executed only on Master, even when we connect to the slave
self::assertTrue($conn->isConnectedToMaster());

$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']);
}
}

0 comments on commit 7788fff

Please sign in to comment.