[BUGFIX] Add missing namespace parts
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Database / ConnectionTest.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Tests\Unit\Database;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Doctrine\DBAL\Driver\Mysqli\MysqliConnection;
19 use Doctrine\DBAL\Statement;
20 use Prophecy\Prophecy\ObjectProphecy;
21 use TYPO3\CMS\Core\Database\Connection;
22 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
23 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
24 use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26
27 /**
28 * Test case
29 *
30 */
31 class ConnectionTest extends \TYPO3\Components\TestingFramework\Core\Unit\UnitTestCase
32 {
33 /**
34 * @var Connection|\PHPUnit_Framework_MockObject_MockObject
35 */
36 protected $connection;
37
38 /**
39 * @var \Doctrine\DBAL\Platforms\AbstractPlatform
40 */
41 protected $platform;
42
43 /**
44 * @var string
45 */
46 protected $testTable = 'testTable';
47
48 /**
49 * Create a new database connection mock object for every test.
50 *
51 * @return void
52 */
53 protected function setUp()
54 {
55 parent::setUp();
56
57 $this->connection = $this->getMockBuilder(Connection::class)
58 ->disableOriginalConstructor()
59 ->setMethods(
60 [
61 'connect',
62 'executeQuery',
63 'executeUpdate',
64 'getDatabasePlatform',
65 'getDriver',
66 'getExpressionBuilder',
67 'getWrappedConnection',
68 ]
69 )
70 ->getMock();
71
72 $this->connection->expects($this->any())
73 ->method('getExpressionBuilder')
74 ->will($this->returnValue(GeneralUtility::makeInstance(ExpressionBuilder::class, $this->connection)));
75
76 $this->connection->expects($this->any())
77 ->method('connect');
78
79 $this->connection->expects($this->any())
80 ->method('getDatabasePlatform')
81 ->will($this->returnValue(new MockPlatform()));
82 }
83
84 /**
85 * @test
86 */
87 public function createQueryBuilderReturnsInstanceOfTypo3QueryBuilder()
88 {
89 $this->assertInstanceOf(QueryBuilder::class, $this->connection->createQueryBuilder());
90 }
91
92 /**
93 * @return array
94 */
95 public function quoteIdentifierDataProvider()
96 {
97 return [
98 'SQL star' => [
99 '*',
100 '*',
101 ],
102 'fieldname' => [
103 'aField',
104 '"aField"',
105 ],
106 'whitespace' => [
107 'with blanks',
108 '"with blanks"',
109 ],
110 'double quotes' => [
111 '"double" quotes',
112 '"""double"" quotes"',
113 ],
114 'single quotes' => [
115 "'single'",
116 '"\'single\'"',
117
118 ],
119 'multiple double quotes' => [
120 '""multiple""',
121 '"""""multiple"""""',
122 ],
123 'multiple single quotes' => [
124 "''multiple''",
125 '"\'\'multiple\'\'"',
126 ],
127 'backticks' => [
128 '`backticks`',
129 '"`backticks`"',
130 ],
131 'slashes' => [
132 '/slashes/',
133 '"/slashes/"',
134 ],
135 'backslashes' => [
136 '\\backslashes\\',
137 '"\\backslashes\\"',
138 ],
139 ];
140 }
141
142 /**
143 * @test
144 * @dataProvider quoteIdentifierDataProvider
145 * @param string $input
146 * @param string $expected
147 */
148 public function quoteIdentifier(string $input, string $expected)
149 {
150 $this->assertSame($expected, $this->connection->quoteIdentifier($input));
151 }
152
153 /**
154 * @test
155 */
156 public function quoteIdentifiers()
157 {
158 $input = [
159 'aField',
160 'anotherField',
161 ];
162
163 $expected = [
164 '"aField"',
165 '"anotherField"',
166 ];
167
168 $this->assertSame($expected, $this->connection->quoteIdentifiers($input));
169 }
170
171 /**
172 * @return array
173 */
174 public function insertQueriesDataProvider()
175 {
176 return [
177 'single value' => [
178 ['aTestTable', ['aField' => 'aValue']],
179 'INSERT INTO "aTestTable" ("aField") VALUES (?)',
180 ['aValue'],
181 [],
182 ],
183 'multiple values' => [
184 ['aTestTable', ['aField' => 'aValue', 'bField' => 'bValue']],
185 'INSERT INTO "aTestTable" ("aField", "bField") VALUES (?, ?)',
186 ['aValue', 'bValue'],
187 [],
188 ],
189 'with types' => [
190 ['aTestTable', ['aField' => 'aValue', 'bField' => 'bValue'], [Connection::PARAM_STR, Connection::PARAM_STR]],
191 'INSERT INTO "aTestTable" ("aField", "bField") VALUES (?, ?)',
192 ['aValue', 'bValue'],
193 [Connection::PARAM_STR, Connection::PARAM_STR],
194 ],
195 'with types for field' => [
196 [
197 'aTestTable',
198 ['aField' => 123, 'bField' => 'bValue'],
199 ['aField' => Connection::PARAM_INT, 'bField' => Connection::PARAM_LOB]
200 ],
201 'INSERT INTO "aTestTable" ("aField", "bField") VALUES (?, ?)',
202 [123, 'bValue'],
203 [Connection::PARAM_INT, Connection::PARAM_LOB],
204 ],
205 ];
206 }
207
208 /**
209 * @test
210 * @dataProvider insertQueriesDataProvider
211 * @param array $args
212 * @param string $expectedQuery
213 * @param array $expectedValues
214 * @param array $expectedTypes
215 */
216 public function insertQueries(array $args, string $expectedQuery, array $expectedValues, array $expectedTypes)
217 {
218 $this->connection->expects($this->once())
219 ->method('executeUpdate')
220 ->with($expectedQuery, $expectedValues, $expectedTypes)
221 ->will($this->returnValue(1));
222
223 $this->connection->insert(...$args);
224 }
225
226 /**
227 * @test
228 */
229 public function bulkInsert()
230 {
231 $this->connection->expects($this->once())
232 ->method('executeUpdate')
233 ->with('INSERT INTO "aTestTable" ("aField") VALUES (?), (?)', ['aValue', 'anotherValue'])
234 ->will($this->returnValue(2));
235
236 $this->connection->bulkInsert('aTestTable', [['aField' => 'aValue'], ['aField' => 'anotherValue']], ['aField']);
237 }
238
239 /**
240 * @return array
241 */
242 public function updateQueriesDataProvider()
243 {
244 return [
245 'single value' => [
246 ['aTestTable', ['aField' => 'aValue'], ['uid' => 1]],
247 'UPDATE "aTestTable" SET "aField" = ? WHERE "uid" = ?',
248 ['aValue', 1],
249 [],
250 ],
251 'multiple values' => [
252 ['aTestTable', ['aField' => 'aValue', 'bField' => 'bValue'], ['uid' => 1]],
253 'UPDATE "aTestTable" SET "aField" = ?, "bField" = ? WHERE "uid" = ?',
254 ['aValue', 'bValue', 1],
255 [],
256 ],
257 'with types' => [
258 ['aTestTable', ['aField' => 'aValue'], ['uid' => 1], [Connection::PARAM_STR]],
259 'UPDATE "aTestTable" SET "aField" = ? WHERE "uid" = ?',
260 ['aValue', 1],
261 [Connection::PARAM_STR],
262 ],
263 'with types for field' => [
264 ['aTestTable', ['aField' => 'aValue'], ['uid' => 1], ['aField' => Connection::PARAM_LOB]],
265 'UPDATE "aTestTable" SET "aField" = ? WHERE "uid" = ?',
266 ['aValue', 1],
267 [0 => Connection::PARAM_LOB, 1 => Connection::PARAM_STR],
268 ],
269 ];
270 }
271
272 /**
273 * @test
274 * @dataProvider updateQueriesDataProvider
275 * @param array $args
276 * @param string $expectedQuery
277 * @param array $expectedValues
278 * @param array $expectedTypes
279 */
280 public function updateQueries(array $args, string $expectedQuery, array $expectedValues, array $expectedTypes)
281 {
282 $this->connection->expects($this->once())
283 ->method('executeUpdate')
284 ->with($expectedQuery, $expectedValues, $expectedTypes)
285 ->will($this->returnValue(1));
286
287 $this->connection->update(...$args);
288 }
289
290 /**
291 * @return array
292 */
293 public function deleteQueriesDataProvider()
294 {
295 return [
296 'single condition' => [
297 ['aTestTable', ['aField' => 'aValue']],
298 'DELETE FROM "aTestTable" WHERE "aField" = ?',
299 ['aValue'],
300 [],
301 ],
302 'multiple conditions' => [
303 ['aTestTable', ['aField' => 'aValue', 'bField' => 'bValue']],
304 'DELETE FROM "aTestTable" WHERE "aField" = ? AND "bField" = ?',
305 ['aValue', 'bValue'],
306 [],
307 ],
308 'with types' => [
309 ['aTestTable', ['aField' => 'aValue'], [Connection::PARAM_STR]],
310 'DELETE FROM "aTestTable" WHERE "aField" = ?',
311 ['aValue'],
312 [Connection::PARAM_STR],
313 ],
314 'with types for field' => [
315 ['aTestTable', ['aField' => 'aValue'], ['aField' => Connection::PARAM_STR]],
316 'DELETE FROM "aTestTable" WHERE "aField" = ?',
317 ['aValue'],
318 [Connection::PARAM_STR],
319 ],
320 ];
321 }
322
323 /**
324 * @test
325 * @dataProvider deleteQueriesDataProvider
326 * @param array $args
327 * @param string $expectedQuery
328 * @param array $expectedValues
329 * @param array $expectedTypes
330 */
331 public function deleteQueries(array $args, string $expectedQuery, array $expectedValues, array $expectedTypes)
332 {
333 $this->connection->expects($this->once())
334 ->method('executeUpdate')
335 ->with($expectedQuery, $expectedValues, $expectedTypes)
336 ->will($this->returnValue(1));
337
338 $this->connection->delete(...$args);
339 }
340
341 /**
342 * Data provider for select query tests
343 *
344 * Each array item consists of
345 * - array of parameters for select call
346 * - expected SQL string
347 * - expected named parameter values
348 *
349 * @return array
350 */
351 public function selectQueriesDataProvider()
352 {
353 return [
354 'all columns' => [
355 [['*'], 'aTable'],
356 'SELECT * FROM "aTable"',
357 [],
358 ],
359 'subset of columns' => [
360 [['aField', 'anotherField'], 'aTable'],
361 'SELECT "aField", "anotherField" FROM "aTable"',
362 [],
363 ],
364 'conditions' => [
365 [['*'], 'aTable', ['aField' => 'aValue']],
366 'SELECT * FROM "aTable" WHERE "aField" = :dcValue1',
367 ['dcValue1' => 'aValue'],
368 ],
369 'grouping' => [
370 [['*'], 'aTable', [], ['aField']],
371 'SELECT * FROM "aTable" GROUP BY "aField"',
372 [],
373 ],
374 'ordering' => [
375 [['*'], 'aTable', [], [], ['aField' => 'ASC']],
376 'SELECT * FROM "aTable" ORDER BY "aField" ASC',
377 [],
378 ],
379 'limit' => [
380 [['*'], 'aTable', [], [], [], 1],
381 'SELECT * FROM "aTable" LIMIT 1 OFFSET 0',
382 [],
383 ],
384 'offset' => [
385 [['*'], 'aTable', [], [], [], 1, 10],
386 'SELECT * FROM "aTable" LIMIT 1 OFFSET 10',
387 [],
388 ],
389 'everything' => [
390 [
391 ['aField', 'anotherField'],
392 'aTable',
393 ['aField' => 'aValue'],
394 ['anotherField'],
395 ['aField' => 'ASC'],
396 1,
397 10,
398 ],
399 'SELECT "aField", "anotherField" FROM "aTable" WHERE "aField" = :dcValue1 ' .
400 'GROUP BY "anotherField" ORDER BY "aField" ASC LIMIT 1 OFFSET 10',
401 ['dcValue1' => 'aValue'],
402 ],
403 ];
404 }
405
406 /**
407 * @test
408 * @dataProvider selectQueriesDataProvider
409 * @param array $args
410 * @param string $expectedQuery
411 * @param array $expectedParameters
412 */
413 public function selectQueries(array $args, string $expectedQuery, array $expectedParameters)
414 {
415 $resultStatement = $this->createMock(Statement::class);
416
417 $this->connection->expects($this->once())
418 ->method('executeQuery')
419 ->with($expectedQuery, $expectedParameters)
420 ->will($this->returnValue($resultStatement));
421
422 $this->connection->select(...$args);
423 }
424
425 /**
426 * Data provider for select query tests
427 *
428 * Each array item consists of
429 * - array of parameters for select call
430 * - expected SQL string
431 * - expected named parameter values
432 *
433 * @return array
434 */
435 public function countQueriesDataProvider()
436 {
437 return [
438 'all columns' => [
439 ['*', 'aTable', []],
440 'SELECT COUNT(*) FROM "aTable"',
441 [],
442 ],
443 'specified columns' => [
444 ['aField', 'aTable', []],
445 'SELECT COUNT("aField") FROM "aTable"',
446 [],
447 ],
448 'conditions' => [
449 ['aTable.aField', 'aTable', ['aField' => 'aValue']],
450 'SELECT COUNT("aTable"."aField") FROM "aTable" WHERE "aField" = :dcValue1',
451 ['dcValue1' => 'aValue'],
452 ],
453 ];
454 }
455
456 /**
457 * @test
458 * @dataProvider countQueriesDataProvider
459 * @param array $args
460 * @param string $expectedQuery
461 * @param array $expectedParameters
462 */
463 public function countQueries(array $args, string $expectedQuery, array $expectedParameters)
464 {
465 $resultStatement = $this->createMock(Statement::class);
466
467 $resultStatement->expects($this->once())
468 ->method('fetchColumn')
469 ->with(0)
470 ->will($this->returnValue(0));
471
472 $this->connection->expects($this->once())
473 ->method('executeQuery')
474 ->with($expectedQuery, $expectedParameters)
475 ->will($this->returnValue($resultStatement));
476
477 $this->connection->count(...$args);
478 }
479
480 /**
481 * @test
482 */
483 public function truncateQuery()
484 {
485 $this->connection->expects($this->once())
486 ->method('executeUpdate')
487 ->with('TRUNCATE "aTestTable"')
488 ->will($this->returnValue(0));
489
490 $this->connection->truncate('aTestTable', false);
491 }
492
493 /**
494 * @test
495 */
496 public function getServerVersionReportsPlatformVersion()
497 {
498 /** @var MysqliConnection|ObjectProphecy $driverProphet */
499 $driverProphet = $this->prophesize(\Doctrine\DBAL\Driver\Mysqli\Driver::class);
500 $driverProphet->willImplement(\Doctrine\DBAL\VersionAwarePlatformDriver::class);
501
502 /** @var MysqliConnection|ObjectProphecy $wrappedConnectionProphet */
503 $wrappedConnectionProphet = $this->prophesize(\Doctrine\DBAL\Driver\Mysqli\MysqliConnection::class);
504 $wrappedConnectionProphet->willImplement(\Doctrine\DBAL\Driver\ServerInfoAwareConnection::class);
505 $wrappedConnectionProphet->requiresQueryForServerVersion()->willReturn(false);
506 $wrappedConnectionProphet->getServerVersion()->willReturn('5.7.11');
507
508 $this->connection->expects($this->any())
509 ->method('getDriver')
510 ->willReturn($driverProphet->reveal());
511 $this->connection->expects($this->any())
512 ->method('getWrappedConnection')
513 ->willReturn($wrappedConnectionProphet->reveal());
514
515 $this->assertSame('mock 5.7.11', $this->connection->getServerVersion());
516 }
517 }