Completed
Push — master ( 1b1935...af38e2 )
by Sergei
17:55 queued 17:50
created

MasterSlaveConnectionTest::testQueryOnMaster()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 22
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Doctrine\Tests\DBAL\Functional;
4
5
use Doctrine\DBAL\Connections\MasterSlaveConnection;
6
use Doctrine\DBAL\Driver\Statement;
7
use Doctrine\DBAL\DriverManager;
8
use Doctrine\DBAL\Schema\Table;
9
use Doctrine\Tests\DbalFunctionalTestCase;
10
use Throwable;
11
use const CASE_LOWER;
12
use function array_change_key_case;
13
use function sprintf;
14
use function strlen;
15
use function strtolower;
16
use function substr;
17
18
/**
19
 * @group DBAL-20
20
 */
21
class MasterSlaveConnectionTest extends DbalFunctionalTestCase
22
{
23
    protected function setUp()
24
    {
25
        parent::setUp();
26
27
        $platformName = $this->connection->getDatabasePlatform()->getName();
28
29
        // This is a MySQL specific test, skip other vendors.
30
        if ($platformName !== 'mysql') {
31
            $this->markTestSkipped(sprintf('Test does not work on %s.', $platformName));
32
        }
33
34
        try {
35
            $table = new Table('master_slave_table');
36
            $table->addColumn('test_int', 'integer');
37
            $table->setPrimaryKey(['test_int']);
38
39
            $sm = $this->connection->getSchemaManager();
40
            $sm->createTable($table);
41
        } catch (Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by Benjamin Eberlei
Consider adding a comment why this CATCH block is empty.
Loading history...
42
        }
43
44
        $this->connection->executeUpdate('DELETE FROM master_slave_table');
45
        $this->connection->insert('master_slave_table', ['test_int' => 1]);
46
    }
47
48
    private function createMasterSlaveConnection(bool $keepSlave = false) : MasterSlaveConnection
49
    {
50
        return DriverManager::getConnection($this->createMasterSlaveConnectionParams($keepSlave));
0 ignored issues
show
Bug Best Practice introduced by Maximilian Ruta
The expression return Doctrine\DBAL\Dri...tionParams($keepSlave)) returns the type Doctrine\DBAL\Connection which includes types incompatible with the type-hinted return Doctrine\DBAL\Connections\MasterSlaveConnection.
Loading history...
51
    }
52
53
    /**
54
     * @return mixed[]
55
     */
56
    private function createMasterSlaveConnectionParams(bool $keepSlave = false) : array
57
    {
58
        $params                 = $this->connection->getParams();
59
        $params['master']       = $params;
60
        $params['slaves']       = [$params, $params];
61
        $params['keepSlave']    = $keepSlave;
62
        $params['wrapperClass'] = MasterSlaveConnection::class;
63
64
        return $params;
65
    }
66
67
    public function testInheritCharsetFromMaster() : void
68
    {
69
        $charsets = [
70
            'utf8',
71
            'latin1',
72
        ];
73
74
        foreach ($charsets as $charset) {
75
            $params                      = $this->createMasterSlaveConnectionParams();
76
            $params['master']['charset'] = $charset;
77
78
            foreach ($params['slaves'] as $index => $slaveParams) {
79
                if (! isset($slaveParams['charset'])) {
80
                    continue;
81
                }
82
83
                unset($params['slaves'][$index]['charset']);
84
            }
85
86
            /** @var MasterSlaveConnection $conn */
87
            $conn = DriverManager::getConnection($params);
88
            $conn->connect('slave');
89
90
            self::assertFalse($conn->isConnectedToMaster());
91
92
            $clientCharset = $conn->fetchColumn('select @@character_set_client as c');
93
94
            self::assertSame(
95
                $charset,
96
                substr(strtolower($clientCharset), 0, strlen($charset))
0 ignored issues
show
Bug introduced by Maximilian Ruta
It seems like $clientCharset can also be of type false; however, parameter $str of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
                substr(strtolower(/** @scrutinizer ignore-type */ $clientCharset), 0, strlen($charset))
Loading history...
97
            );
98
        }
99
    }
100
101
    public function testMasterOnConnect()
102
    {
103
        $conn = $this->createMasterSlaveConnection();
104
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
    public function testNoMasterOnExecuteQuery()
113
    {
114
        $conn = $this->createMasterSlaveConnection();
115
116
        $sql     = 'SELECT count(*) as num FROM master_slave_table';
117
        $data    = $conn->fetchAll($sql);
118
        $data[0] = array_change_key_case($data[0], CASE_LOWER);
119
120
        self::assertEquals(1, $data[0]['num']);
121
        self::assertFalse($conn->isConnectedToMaster());
122
    }
123
124
    public function testMasterOnWriteOperation()
125
    {
126
        $conn = $this->createMasterSlaveConnection();
127
        $conn->insert('master_slave_table', ['test_int' => 30]);
128
129
        self::assertTrue($conn->isConnectedToMaster());
130
131
        $sql     = 'SELECT count(*) as num FROM master_slave_table';
132
        $data    = $conn->fetchAll($sql);
133
        $data[0] = array_change_key_case($data[0], CASE_LOWER);
134
135
        self::assertEquals(2, $data[0]['num']);
136
        self::assertTrue($conn->isConnectedToMaster());
137
    }
138
139
    /**
140
     * @group DBAL-335
141
     */
142
    public function testKeepSlaveBeginTransactionStaysOnMaster()
143
    {
144
        $conn = $this->createMasterSlaveConnection($keepSlave = true);
145
        $conn->connect('slave');
146
147
        $conn->beginTransaction();
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
     * @group DBAL-335
162
     */
163
    public function testKeepSlaveInsertStaysOnMaster()
164
    {
165
        $conn = $this->createMasterSlaveConnection($keepSlave = true);
166
        $conn->connect('slave');
167
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
    public function testMasterSlaveConnectionCloseAndReconnect()
180
    {
181
        $conn = $this->createMasterSlaveConnection();
182
        $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
    public function testQueryOnMaster()
193
    {
194
        $conn = $this->createMasterSlaveConnection();
195
196
        $query = 'SELECT count(*) as num FROM master_slave_table';
197
198
        $statement = $conn->query($query);
199
200
        self::assertInstanceOf(Statement::class, $statement);
201
202
        //Query must be executed only on Master
203
        self::assertTrue($conn->isConnectedToMaster());
204
205
        $data = $statement->fetchAll();
206
207
        //Default fetchmode is FetchMode::ASSOCIATIVE
208
        self::assertArrayHasKey(0, $data);
209
        self::assertArrayHasKey('num', $data[0]);
210
211
        //Could be set in other fetchmodes
212
        self::assertArrayNotHasKey(0, $data[0]);
213
        self::assertEquals(1, $data[0]['num']);
214
    }
215
216
    public function testQueryOnSlave()
217
    {
218
        $conn = $this->createMasterSlaveConnection();
219
        $conn->connect('slave');
220
221
        $query = 'SELECT count(*) as num FROM master_slave_table';
222
223
        $statement = $conn->query($query);
224
225
        self::assertInstanceOf(Statement::class, $statement);
226
227
        //Query must be executed only on Master, even when we connect to the slave
228
        self::assertTrue($conn->isConnectedToMaster());
229
230
        $data = $statement->fetchAll();
231
232
        //Default fetchmode is FetchMode::ASSOCIATIVE
233
        self::assertArrayHasKey(0, $data);
234
        self::assertArrayHasKey('num', $data[0]);
235
236
        //Could be set in other fetchmodes
237
        self::assertArrayNotHasKey(0, $data[0]);
238
239
        self::assertEquals(1, $data[0]['num']);
240
    }
241
}
242