Unverified Commit a2fcaeef authored by Grégoire Paris's avatar Grégoire Paris

Merge remote-tracking branch 'origin/2.11.x' into 3.0.x

parents be5e93e9 0c8528ec
......@@ -175,6 +175,48 @@ Please use other database client applications for import, e.g.:
# Upgrade to 2.11
## Deprecated `Portability\Connection::PORTABILITY_{PLATFORM}` constants`
The platform-specific portability mode flags are meant to be used only by the portability layer internally to optimize
the user-provided mode for the current database platform.
## 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.
This diff is collapsed.
......@@ -139,17 +139,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);
}
}
self::_checkParams($params);
$className = $params['driverClass'] ?? self::$_driverMap[$params['driver']];
......
......@@ -25,6 +25,10 @@ class Connection extends \Doctrine\DBAL\Connection
public const PORTABILITY_EMPTY_TO_NULL = 4;
public const PORTABILITY_FIX_CASE = 8;
/**#@+
*
* @deprecated Will be removed as internal implementation details.
*/
public const PORTABILITY_DB2 = 13;
public const PORTABILITY_ORACLE = 9;
public const PORTABILITY_POSTGRESQL = 13;
......@@ -32,6 +36,7 @@ class Connection extends \Doctrine\DBAL\Connection
public const PORTABILITY_OTHERVENDORS = 12;
public const PORTABILITY_SQLANYWHERE = 13;
public const PORTABILITY_SQLSRV = 13;
/**#@-*/
/** @var Converter */
private $converter;
......@@ -47,21 +52,10 @@ class Connection extends \Doctrine\DBAL\Connection
$portability = self::PORTABILITY_NONE;
if (isset($params['portability'])) {
if ($this->getDatabasePlatform()->getName() === 'oracle') {
$portability = $params['portability'] & self::PORTABILITY_ORACLE;
} elseif ($this->getDatabasePlatform()->getName() === 'postgresql') {
$portability = $params['portability'] & self::PORTABILITY_POSTGRESQL;
} elseif ($this->getDatabasePlatform()->getName() === 'sqlite') {
$portability = $params['portability'] & self::PORTABILITY_SQLITE;
} elseif ($this->getDatabasePlatform()->getName() === 'sqlanywhere') {
$portability = $params['portability'] & self::PORTABILITY_SQLANYWHERE;
} elseif ($this->getDatabasePlatform()->getName() === 'db2') {
$portability = $params['portability'] & self::PORTABILITY_DB2;
} elseif ($this->getDatabasePlatform()->getName() === 'mssql') {
$portability = $params['portability'] & self::PORTABILITY_SQLSRV;
} else {
$portability = $params['portability'] & self::PORTABILITY_OTHERVENDORS;
}
$portability = $params['portability'] = (new OptimizeFlags())(
$this->getDatabasePlatform(),
$params['portability']
);
}
$case = null;
......
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Portability;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Platforms\SQLAnywhere16Platform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
final class OptimizeFlags
{
/**
* Platform-specific portability flags that need to be excluded from the user-provided mode
* since the platform already operates in this mode to avoid unnecessary conversion overhead.
*
* @var array<string,int>
*/
private static $platforms = [
DB2Platform::class => 0,
OraclePlatform::class => Connection::PORTABILITY_EMPTY_TO_NULL,
PostgreSQL94Platform::class => 0,
SQLAnywhere16Platform::class => 0,
SqlitePlatform::class => 0,
SQLServer2012Platform::class => 0,
];
public function __invoke(AbstractPlatform $platform, int $flags): int
{
foreach (self::$platforms as $class => $mask) {
if ($platform instanceof $class) {
$flags &= ~$mask;
break;
}
}
return $flags;
}
}
......@@ -3,7 +3,7 @@
namespace Doctrine\DBAL\Tests;
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\PDOMySql\Driver as PDOMySQLDriver;
......@@ -99,15 +99,15 @@ class DriverManagerTest extends TestCase
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);
......@@ -123,12 +123,12 @@ class DriverManagerTest extends TestCase
];
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']);
}
/**
......
<?php
namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
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 FunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$platformName = $this->connection->getDatabasePlatform()->getName();
// This is a MySQL specific test, skip other vendors.
if ($platformName !== 'mysql') {
self::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->fetchOne('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->fetchAllAssociative($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->fetchAllAssociative($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';
$result = $conn->query($query);
//Query must be executed only on Primary
self::assertTrue($conn->isConnectedToPrimary());
$data = $result->fetchAllAssociative();
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';
$result = $conn->query($query);
//Query must be executed only on Primary, even when we connect to the replica
self::assertTrue($conn->isConnectedToPrimary());
$data = $result->fetchAllAssociative();
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']);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\Tests\DBAL\Portability;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Portability\Connection;
use Doctrine\DBAL\Portability\OptimizeFlags;
use PHPUnit\Framework\TestCase;
class OptimizeFlagsTest extends TestCase
{
/** @var OptimizeFlags */
private $optimizeFlags;
protected function setUp(): void
{
$this->optimizeFlags = new OptimizeFlags();
}
public function testOracle(): void
{
$flags = ($this->optimizeFlags)(new OraclePlatform(), Connection::PORTABILITY_ALL);
self::assertSame(0, $flags & Connection::PORTABILITY_EMPTY_TO_NULL);
}
public function testAnotherPlatform(): void
{
$flags = ($this->optimizeFlags)(new SqlitePlatform(), Connection::PORTABILITY_ALL);
self::assertSame(Connection::PORTABILITY_ALL, $flags);
}
}
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