1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace Doctrine\DBAL\Tests\Functional; |
||||||
6 | |||||||
7 | use Doctrine\DBAL\Connections\MasterSlaveConnection; |
||||||
8 | use Doctrine\DBAL\Driver\Statement; |
||||||
9 | use Doctrine\DBAL\DriverManager; |
||||||
10 | use Doctrine\DBAL\Schema\Table; |
||||||
11 | use Doctrine\DBAL\Tests\FunctionalTestCase; |
||||||
12 | use Throwable; |
||||||
13 | use function array_change_key_case; |
||||||
14 | use function assert; |
||||||
15 | use function sprintf; |
||||||
16 | use function strlen; |
||||||
17 | use function strtolower; |
||||||
18 | use function substr; |
||||||
19 | use const CASE_LOWER; |
||||||
20 | |||||||
21 | /** |
||||||
22 | * @group DBAL-20 |
||||||
23 | */ |
||||||
24 | class MasterSlaveConnectionTest extends FunctionalTestCase |
||||||
25 | { |
||||||
26 | protected function setUp() : void |
||||||
27 | { |
||||||
28 | parent::setUp(); |
||||||
29 | |||||||
30 | $platformName = $this->connection->getDatabasePlatform()->getName(); |
||||||
31 | |||||||
32 | // This is a MySQL specific test, skip other vendors. |
||||||
33 | if ($platformName !== 'mysql') { |
||||||
34 | self::markTestSkipped(sprintf('Test does not work on %s.', $platformName)); |
||||||
35 | } |
||||||
36 | |||||||
37 | try { |
||||||
38 | $table = new Table('master_slave_table'); |
||||||
39 | $table->addColumn('test_int', 'integer'); |
||||||
40 | $table->setPrimaryKey(['test_int']); |
||||||
41 | |||||||
42 | $sm = $this->connection->getSchemaManager(); |
||||||
43 | $sm->createTable($table); |
||||||
44 | } catch (Throwable $e) { |
||||||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
![]() |
|||||||
45 | } |
||||||
46 | |||||||
47 | $this->connection->executeUpdate('DELETE FROM master_slave_table'); |
||||||
48 | $this->connection->insert('master_slave_table', ['test_int' => 1]); |
||||||
49 | } |
||||||
50 | |||||||
51 | private function createMasterSlaveConnection(bool $keepSlave = false) : MasterSlaveConnection |
||||||
52 | { |
||||||
53 | $connection = DriverManager::getConnection($this->createMasterSlaveConnectionParams($keepSlave)); |
||||||
54 | assert($connection instanceof MasterSlaveConnection); |
||||||
55 | |||||||
56 | return $connection; |
||||||
57 | } |
||||||
58 | |||||||
59 | /** |
||||||
60 | * @return mixed[] |
||||||
61 | */ |
||||||
62 | private function createMasterSlaveConnectionParams(bool $keepSlave = false) : array |
||||||
63 | { |
||||||
64 | $params = $this->connection->getParams(); |
||||||
65 | $params['master'] = $params; |
||||||
66 | $params['slaves'] = [$params, $params]; |
||||||
67 | $params['keepSlave'] = $keepSlave; |
||||||
68 | $params['wrapperClass'] = MasterSlaveConnection::class; |
||||||
69 | |||||||
70 | return $params; |
||||||
71 | } |
||||||
72 | |||||||
73 | public function testInheritCharsetFromMaster() : void |
||||||
74 | { |
||||||
75 | $charsets = [ |
||||||
76 | 'utf8', |
||||||
77 | 'latin1', |
||||||
78 | ]; |
||||||
79 | |||||||
80 | foreach ($charsets as $charset) { |
||||||
81 | $params = $this->createMasterSlaveConnectionParams(); |
||||||
82 | $params['master']['charset'] = $charset; |
||||||
83 | |||||||
84 | foreach ($params['slaves'] as $index => $slaveParams) { |
||||||
85 | if (! isset($slaveParams['charset'])) { |
||||||
86 | continue; |
||||||
87 | } |
||||||
88 | |||||||
89 | unset($params['slaves'][$index]['charset']); |
||||||
90 | } |
||||||
91 | |||||||
92 | $conn = DriverManager::getConnection($params); |
||||||
93 | self::assertInstanceOf(MasterSlaveConnection::class, $conn); |
||||||
94 | $conn->connect('slave'); |
||||||
0 ignored issues
–
show
The call to
Doctrine\DBAL\Connection::connect() has too many arguments starting with 'slave' .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
95 | |||||||
96 | self::assertFalse($conn->isConnectedToMaster()); |
||||||
0 ignored issues
–
show
The method
isConnectedToMaster() does not exist on Doctrine\DBAL\Connection . Did you maybe mean isConnected() ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
97 | |||||||
98 | $clientCharset = $conn->fetchColumn('select @@character_set_client as c'); |
||||||
99 | |||||||
100 | self::assertSame( |
||||||
101 | $charset, |
||||||
102 | substr(strtolower($clientCharset), 0, strlen($charset)) |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
103 | ); |
||||||
104 | } |
||||||
105 | } |
||||||
106 | |||||||
107 | public function testMasterOnConnect() : void |
||||||
108 | { |
||||||
109 | $conn = $this->createMasterSlaveConnection(); |
||||||
110 | |||||||
111 | self::assertFalse($conn->isConnectedToMaster()); |
||||||
112 | $conn->connect('slave'); |
||||||
113 | self::assertFalse($conn->isConnectedToMaster()); |
||||||
114 | $conn->connect('master'); |
||||||
115 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
116 | } |
||||||
117 | |||||||
118 | public function testNoMasterOnExecuteQuery() : void |
||||||
119 | { |
||||||
120 | $conn = $this->createMasterSlaveConnection(); |
||||||
121 | |||||||
122 | $sql = 'SELECT count(*) as num FROM master_slave_table'; |
||||||
123 | $data = $conn->fetchAll($sql); |
||||||
124 | $data[0] = array_change_key_case($data[0], CASE_LOWER); |
||||||
125 | |||||||
126 | self::assertEquals(1, $data[0]['num']); |
||||||
127 | self::assertFalse($conn->isConnectedToMaster()); |
||||||
128 | } |
||||||
129 | |||||||
130 | public function testMasterOnWriteOperation() : void |
||||||
131 | { |
||||||
132 | $conn = $this->createMasterSlaveConnection(); |
||||||
133 | $conn->insert('master_slave_table', ['test_int' => 30]); |
||||||
134 | |||||||
135 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
136 | |||||||
137 | $sql = 'SELECT count(*) as num FROM master_slave_table'; |
||||||
138 | $data = $conn->fetchAll($sql); |
||||||
139 | $data[0] = array_change_key_case($data[0], CASE_LOWER); |
||||||
140 | |||||||
141 | self::assertEquals(2, $data[0]['num']); |
||||||
142 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
143 | } |
||||||
144 | |||||||
145 | /** |
||||||
146 | * @group DBAL-335 |
||||||
147 | */ |
||||||
148 | public function testKeepSlaveBeginTransactionStaysOnMaster() : void |
||||||
149 | { |
||||||
150 | $conn = $this->createMasterSlaveConnection($keepSlave = true); |
||||||
151 | $conn->connect('slave'); |
||||||
152 | |||||||
153 | $conn->beginTransaction(); |
||||||
154 | $conn->insert('master_slave_table', ['test_int' => 30]); |
||||||
155 | $conn->commit(); |
||||||
156 | |||||||
157 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
158 | |||||||
159 | $conn->connect(); |
||||||
160 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
161 | |||||||
162 | $conn->connect('slave'); |
||||||
163 | self::assertFalse($conn->isConnectedToMaster()); |
||||||
164 | } |
||||||
165 | |||||||
166 | /** |
||||||
167 | * @group DBAL-335 |
||||||
168 | */ |
||||||
169 | public function testKeepSlaveInsertStaysOnMaster() : void |
||||||
170 | { |
||||||
171 | $conn = $this->createMasterSlaveConnection($keepSlave = true); |
||||||
172 | $conn->connect('slave'); |
||||||
173 | |||||||
174 | $conn->insert('master_slave_table', ['test_int' => 30]); |
||||||
175 | |||||||
176 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
177 | |||||||
178 | $conn->connect(); |
||||||
179 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
180 | |||||||
181 | $conn->connect('slave'); |
||||||
182 | self::assertFalse($conn->isConnectedToMaster()); |
||||||
183 | } |
||||||
184 | |||||||
185 | public function testMasterSlaveConnectionCloseAndReconnect() : void |
||||||
186 | { |
||||||
187 | $conn = $this->createMasterSlaveConnection(); |
||||||
188 | $conn->connect('master'); |
||||||
189 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
190 | |||||||
191 | $conn->close(); |
||||||
192 | self::assertFalse($conn->isConnectedToMaster()); |
||||||
193 | |||||||
194 | $conn->connect('master'); |
||||||
195 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
196 | } |
||||||
197 | |||||||
198 | public function testQueryOnMaster() : void |
||||||
199 | { |
||||||
200 | $conn = $this->createMasterSlaveConnection(); |
||||||
201 | |||||||
202 | $query = 'SELECT count(*) as num FROM master_slave_table'; |
||||||
203 | |||||||
204 | $statement = $conn->query($query); |
||||||
205 | |||||||
206 | self::assertInstanceOf(Statement::class, $statement); |
||||||
207 | |||||||
208 | //Query must be executed only on Master |
||||||
209 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
210 | |||||||
211 | $data = $statement->fetchAll(); |
||||||
212 | |||||||
213 | //Default fetchmode is FetchMode::ASSOCIATIVE |
||||||
214 | self::assertArrayHasKey(0, $data); |
||||||
215 | self::assertArrayHasKey('num', $data[0]); |
||||||
216 | |||||||
217 | //Could be set in other fetchmodes |
||||||
218 | self::assertArrayNotHasKey(0, $data[0]); |
||||||
219 | self::assertEquals(1, $data[0]['num']); |
||||||
220 | } |
||||||
221 | |||||||
222 | public function testQueryOnSlave() : void |
||||||
223 | { |
||||||
224 | $conn = $this->createMasterSlaveConnection(); |
||||||
225 | $conn->connect('slave'); |
||||||
226 | |||||||
227 | $query = 'SELECT count(*) as num FROM master_slave_table'; |
||||||
228 | |||||||
229 | $statement = $conn->query($query); |
||||||
230 | |||||||
231 | self::assertInstanceOf(Statement::class, $statement); |
||||||
232 | |||||||
233 | //Query must be executed only on Master, even when we connect to the slave |
||||||
234 | self::assertTrue($conn->isConnectedToMaster()); |
||||||
235 | |||||||
236 | $data = $statement->fetchAll(); |
||||||
237 | |||||||
238 | //Default fetchmode is FetchMode::ASSOCIATIVE |
||||||
239 | self::assertArrayHasKey(0, $data); |
||||||
240 | self::assertArrayHasKey('num', $data[0]); |
||||||
241 | |||||||
242 | //Could be set in other fetchmodes |
||||||
243 | self::assertArrayNotHasKey(0, $data[0]); |
||||||
244 | |||||||
245 | self::assertEquals(1, $data[0]['num']); |
||||||
246 | } |
||||||
247 | } |
||||||
248 |