MasterSlaveConnectionTest.php 7.08 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\Tests\DBAL\Functional;

5
use Doctrine\DBAL\Connections\MasterSlaveConnection;
6
use Doctrine\DBAL\Driver\Statement;
7
use Doctrine\DBAL\DriverManager;
Sergei Morozov's avatar
Sergei Morozov committed
8
use Doctrine\DBAL\Schema\Table;
jeroendedauw's avatar
jeroendedauw committed
9
use Doctrine\Tests\DbalFunctionalTestCase;
Sergei Morozov's avatar
Sergei Morozov committed
10
use Throwable;
11 12 13 14 15 16
use const CASE_LOWER;
use function array_change_key_case;
use function sprintf;
use function strlen;
use function strtolower;
use function substr;
17 18 19 20 21 22

/**
 * @group DBAL-20
 */
class MasterSlaveConnectionTest extends DbalFunctionalTestCase
{
23
    protected function setUp() : void
24 25 26
    {
        parent::setUp();

Sergei Morozov's avatar
Sergei Morozov committed
27
        $platformName = $this->connection->getDatabasePlatform()->getName();
28 29

        // This is a MySQL specific test, skip other vendors.
Sergei Morozov's avatar
Sergei Morozov committed
30
        if ($platformName !== 'mysql') {
31
            $this->markTestSkipped(sprintf('Test does not work on %s.', $platformName));
32 33 34
        }

        try {
Sergei Morozov's avatar
Sergei Morozov committed
35
            $table = new Table('master_slave_table');
36
            $table->addColumn('test_int', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
37
            $table->setPrimaryKey(['test_int']);
38

Sergei Morozov's avatar
Sergei Morozov committed
39
            $sm = $this->connection->getSchemaManager();
40
            $sm->createTable($table);
Sergei Morozov's avatar
Sergei Morozov committed
41
        } catch (Throwable $e) {
42
        }
43

Sergei Morozov's avatar
Sergei Morozov committed
44 45
        $this->connection->executeUpdate('DELETE FROM master_slave_table');
        $this->connection->insert('master_slave_table', ['test_int' => 1]);
46 47
    }

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

Sergei Morozov's avatar
Sergei Morozov committed
53 54 55
    /**
     * @return mixed[]
     */
56
    private function createMasterSlaveConnectionParams(bool $keepSlave = false) : array
57
    {
Sergei Morozov's avatar
Sergei Morozov committed
58
        $params                 = $this->connection->getParams();
59
        $params['master']       = $params;
Sergei Morozov's avatar
Sergei Morozov committed
60
        $params['slaves']       = [$params, $params];
61
        $params['keepSlave']    = $keepSlave;
62 63 64 65 66 67 68 69 70
        $params['wrapperClass'] = MasterSlaveConnection::class;

        return $params;
    }

    public function testInheritCharsetFromMaster() : void
    {
        $charsets = [
            'utf8',
Sergei Morozov's avatar
Sergei Morozov committed
71
            'latin1',
72
        ];
73

74
        foreach ($charsets as $charset) {
Sergei Morozov's avatar
Sergei Morozov committed
75
            $params                      = $this->createMasterSlaveConnectionParams();
76 77 78
            $params['master']['charset'] = $charset;

            foreach ($params['slaves'] as $index => $slaveParams) {
Sergei Morozov's avatar
Sergei Morozov committed
79 80
                if (! isset($slaveParams['charset'])) {
                    continue;
81
                }
Sergei Morozov's avatar
Sergei Morozov committed
82 83

                unset($params['slaves'][$index]['charset']);
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
            }

            /** @var MasterSlaveConnection $conn */
            $conn = DriverManager::getConnection($params);
            $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))
            );
        }
99 100 101 102 103 104
    }

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

105
        self::assertFalse($conn->isConnectedToMaster());
106
        $conn->connect('slave');
107
        self::assertFalse($conn->isConnectedToMaster());
108
        $conn->connect('master');
109
        self::assertTrue($conn->isConnectedToMaster());
110 111 112 113 114 115
    }

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

Sergei Morozov's avatar
Sergei Morozov committed
116 117
        $sql     = 'SELECT count(*) as num FROM master_slave_table';
        $data    = $conn->fetchAll($sql);
118 119
        $data[0] = array_change_key_case($data[0], CASE_LOWER);

120 121
        self::assertEquals(1, $data[0]['num']);
        self::assertFalse($conn->isConnectedToMaster());
122 123 124 125 126
    }

    public function testMasterOnWriteOperation()
    {
        $conn = $this->createMasterSlaveConnection();
Sergei Morozov's avatar
Sergei Morozov committed
127
        $conn->insert('master_slave_table', ['test_int' => 30]);
128

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

Sergei Morozov's avatar
Sergei Morozov committed
131 132
        $sql     = 'SELECT count(*) as num FROM master_slave_table';
        $data    = $conn->fetchAll($sql);
133 134
        $data[0] = array_change_key_case($data[0], CASE_LOWER);

135 136
        self::assertEquals(2, $data[0]['num']);
        self::assertTrue($conn->isConnectedToMaster());
137
    }
138 139 140 141 142 143 144 145

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

147
        $conn->beginTransaction();
Sergei Morozov's avatar
Sergei Morozov committed
148
        $conn->insert('master_slave_table', ['test_int' => 30]);
149
        $conn->commit();
150

151
        self::assertTrue($conn->isConnectedToMaster());
152 153

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

        $conn->connect('slave');
157
        self::assertFalse($conn->isConnectedToMaster());
158
    }
159 160 161 162 163 164 165 166 167

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

Sergei Morozov's avatar
Sergei Morozov committed
168
        $conn->insert('master_slave_table', ['test_int' => 30]);
169

170
        self::assertTrue($conn->isConnectedToMaster());
171 172

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

        $conn->connect('slave');
176
        self::assertFalse($conn->isConnectedToMaster());
177
    }
178 179 180 181 182

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

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

        $conn->connect('master');
189
        self::assertTrue($conn->isConnectedToMaster());
190
    }
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240

    public function testQueryOnMaster()
    {
        $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()
    {
        $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']);
    }
241
}