c80a4dca0c3de33fca4deac1c4c50ebac97ebcea
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Database / Query / QueryBuilderTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Core\Tests\Unit\Database\Query;
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\Platforms\AbstractPlatform;
19 use Doctrine\DBAL\Platforms\MySqlPlatform;
20 use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
21 use Doctrine\DBAL\Platforms\SQLServerPlatform;
22 use Prophecy\Argument;
23 use TYPO3\CMS\Core\Database\Connection;
24 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
25 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
26 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
27 use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
30
31 class QueryBuilderTest extends UnitTestCase
32 {
33 /**
34 * Subject is not notice free, disable E_NOTICES
35 */
36 protected static $suppressNotices = true;
37
38 /**
39 * @var Connection|\Prophecy\Prophecy\ObjectProphecy
40 */
41 protected $connection;
42
43 /**
44 * @var AbstractPlatform
45 */
46 protected $platform;
47
48 /**
49 * @var QueryBuilder
50 */
51 protected $subject;
52
53 /**
54 * @var \Doctrine\DBAL\Query\QueryBuilder|\Prophecy\Prophecy\ObjectProphecy
55 */
56 protected $concreteQueryBuilder;
57
58 /**
59 * Create a new database connection mock object for every test.
60 */
61 protected function setUp()
62 {
63 parent::setUp();
64
65 $this->concreteQueryBuilder = $this->prophesize(\Doctrine\DBAL\Query\QueryBuilder::class);
66
67 $this->connection = $this->prophesize(Connection::class);
68 $this->connection->getDatabasePlatform()->willReturn(new MockPlatform());
69
70 $this->subject = GeneralUtility::makeInstance(
71 QueryBuilder::class,
72 $this->connection->reveal(),
73 null,
74 $this->concreteQueryBuilder->reveal()
75 );
76 }
77
78 /**
79 * @test
80 */
81 public function exprReturnsExpressionBuilderForConnection()
82 {
83 $this->connection->getExpressionBuilder()
84 ->shouldBeCalled()
85 ->willReturn(GeneralUtility::makeInstance(ExpressionBuilder::class, $this->connection->reveal()));
86
87 $this->subject->expr();
88 }
89
90 /**
91 * @test
92 */
93 public function getTypeDelegatesToConcreteQueryBuilder()
94 {
95 $this->concreteQueryBuilder->getType()
96 ->shouldBeCalled()
97 ->willReturn(\Doctrine\DBAL\Query\QueryBuilder::INSERT);
98
99 $this->subject->getType();
100 }
101
102 /**
103 * @test
104 */
105 public function getStateDelegatesToConcreteQueryBuilder()
106 {
107 $this->concreteQueryBuilder->getState()
108 ->shouldBeCalled()
109 ->willReturn(\Doctrine\DBAL\Query\QueryBuilder::STATE_CLEAN);
110
111 $this->subject->getState();
112 }
113
114 /**
115 * @test
116 */
117 public function getSQLDelegatesToConcreteQueryBuilder()
118 {
119 $this->concreteQueryBuilder->getSQL()
120 ->shouldBeCalled()
121 ->willReturn('UPDATE aTable SET pid = 7');
122 $this->concreteQueryBuilder->getType()
123 ->willReturn(2); // Update Type
124
125 $this->subject->getSQL();
126 }
127
128 /**
129 * @test
130 */
131 public function setParameterDelegatesToConcreteQueryBuilder()
132 {
133 $this->concreteQueryBuilder->setParameter(Argument::exact('aField'), Argument::exact(5), Argument::cetera())
134 ->shouldBeCalled()
135 ->willReturn($this->subject);
136
137 $this->subject->setParameter('aField', 5);
138 }
139
140 /**
141 * @test
142 */
143 public function setParametersDelegatesToConcreteQueryBuilder()
144 {
145 $this->concreteQueryBuilder->setParameters(Argument::exact(['aField' => 'aValue']), Argument::exact([]))
146 ->shouldBeCalled()
147 ->willReturn($this->subject);
148
149 $this->subject->setParameters(['aField' => 'aValue']);
150 }
151
152 /**
153 * @test
154 */
155 public function getParametersDelegatesToConcreteQueryBuilder()
156 {
157 $this->concreteQueryBuilder->getParameters()
158 ->shouldBeCalled()
159 ->willReturn(['aField' => 'aValue']);
160
161 $this->subject->getParameters();
162 }
163
164 /**
165 * @test
166 */
167 public function getParameterDelegatesToConcreteQueryBuilder()
168 {
169 $this->concreteQueryBuilder->getParameter(Argument::exact('aField'))
170 ->shouldBeCalled()
171 ->willReturn('aValue');
172
173 $this->subject->getParameter('aField');
174 }
175
176 /**
177 * @test
178 */
179 public function getParameterTypesDelegatesToConcreteQueryBuilder()
180 {
181 $this->concreteQueryBuilder->getParameterTypes()
182 ->shouldBeCalled()
183 ->willReturn([]);
184
185 $this->subject->getParameterTypes();
186 }
187
188 /**
189 * @test
190 */
191 public function getParameterTypeDelegatesToConcreteQueryBuilder()
192 {
193 $this->concreteQueryBuilder->getParameterType(Argument::exact('aField'))
194 ->shouldBeCalled()
195 ->willReturn(Connection::PARAM_STR);
196
197 $this->subject->getParameterType('aField');
198 }
199
200 /**
201 * @test
202 */
203 public function setFirstResultDelegatesToConcreteQueryBuilder()
204 {
205 $this->concreteQueryBuilder->setFirstResult(Argument::cetera())
206 ->shouldBeCalled()
207 ->willReturn($this->subject);
208
209 $this->subject->setFirstResult(1);
210 }
211
212 /**
213 * @test
214 */
215 public function getFirstResultDelegatesToConcreteQueryBuilder()
216 {
217 $this->concreteQueryBuilder->getFirstResult()
218 ->shouldBeCalled()
219 ->willReturn(1);
220
221 $this->subject->getFirstResult();
222 }
223
224 /**
225 * @test
226 */
227 public function setMaxResultsDelegatesToConcreteQueryBuilder()
228 {
229 $this->concreteQueryBuilder->setMaxResults(Argument::cetera())
230 ->shouldBeCalled()
231 ->willReturn($this->subject);
232
233 $this->subject->setMaxResults(1);
234 }
235
236 /**
237 * @test
238 */
239 public function getMaxResultsDelegatesToConcreteQueryBuilder()
240 {
241 $this->concreteQueryBuilder->getMaxResults()
242 ->shouldBeCalled()
243 ->willReturn(1);
244
245 $this->subject->getMaxResults();
246 }
247
248 /**
249 * @test
250 */
251 public function addDelegatesToConcreteQueryBuilder()
252 {
253 $this->concreteQueryBuilder->add(Argument::exact('select'), Argument::exact('aField'), Argument::cetera())
254 ->shouldBeCalled()
255 ->willReturn($this->subject);
256
257 $this->subject->add('select', 'aField');
258 }
259
260 /**
261 * @test
262 */
263 public function countBuildsExpressionAndCallsSelect()
264 {
265 $this->concreteQueryBuilder->select(Argument::exact('COUNT(*)'))
266 ->shouldBeCalled()
267 ->willReturn($this->subject);
268
269 $this->subject->count('*');
270 }
271
272 /**
273 * @test
274 */
275 public function selectQuotesIdentifiersAndDelegatesToConcreteQueryBuilder()
276 {
277 $this->connection->quoteIdentifier('aField')
278 ->shouldBeCalled()
279 ->willReturnArgument(0);
280 $this->connection->quoteIdentifier('anotherField')
281 ->shouldBeCalled()
282 ->willReturnArgument(0);
283 $this->concreteQueryBuilder->select(Argument::exact('aField'), Argument::exact('anotherField'))
284 ->shouldBeCalled()
285 ->willReturn($this->subject);
286
287 $this->subject->select('aField', 'anotherField');
288 }
289
290 /**
291 * @return array
292 */
293 public function quoteIdentifiersForSelectDataProvider()
294 {
295 return [
296 'fieldName' => [
297 'fieldName',
298 '"fieldName"',
299 ],
300 'tableName.fieldName' => [
301 'tableName.fieldName',
302 '"tableName"."fieldName"',
303 ],
304 'tableName.*' => [
305 'tableName.*',
306 '"tableName".*',
307 ],
308 '*' => [
309 '*',
310 '*',
311 ],
312 'fieldName AS anotherFieldName' => [
313 'fieldName AS anotherFieldName',
314 '"fieldName" AS "anotherFieldName"',
315 ],
316 'tableName.fieldName AS anotherFieldName' => [
317 'tableName.fieldName AS anotherFieldName',
318 '"tableName"."fieldName" AS "anotherFieldName"',
319 ],
320 'tableName.fieldName AS anotherTable.anotherFieldName' => [
321 'tableName.fieldName AS anotherTable.anotherFieldName',
322 '"tableName"."fieldName" AS "anotherTable"."anotherFieldName"',
323 ],
324 'fieldName as anotherFieldName' => [
325 'fieldName as anotherFieldName',
326 '"fieldName" AS "anotherFieldName"',
327 ],
328 'tableName.fieldName as anotherFieldName' => [
329 'tableName.fieldName as anotherFieldName',
330 '"tableName"."fieldName" AS "anotherFieldName"',
331 ],
332 'tableName.fieldName as anotherTable.anotherFieldName' => [
333 'tableName.fieldName as anotherTable.anotherFieldName',
334 '"tableName"."fieldName" AS "anotherTable"."anotherFieldName"',
335 ],
336 'fieldName aS anotherFieldName' => [
337 'fieldName aS anotherFieldName',
338 '"fieldName" AS "anotherFieldName"',
339 ],
340 'tableName.fieldName aS anotherFieldName' => [
341 'tableName.fieldName aS anotherFieldName',
342 '"tableName"."fieldName" AS "anotherFieldName"',
343 ],
344 'tableName.fieldName aS anotherTable.anotherFieldName' => [
345 'tableName.fieldName aS anotherTable.anotherFieldName',
346 '"tableName"."fieldName" AS "anotherTable"."anotherFieldName"',
347 ],
348 ];
349 }
350
351 /**
352 * @test
353 * @dataProvider quoteIdentifiersForSelectDataProvider
354 * @param string $identifier
355 * @param string $expectedResult
356 */
357 public function quoteIdentifiersForSelect($identifier, $expectedResult)
358 {
359 $this->connection->quoteIdentifier(Argument::cetera())->will(
360 function ($args) {
361 $platform = new MockPlatform();
362
363 return $platform->quoteIdentifier($args[0]);
364 }
365 );
366
367 $this->assertSame([$expectedResult], $this->subject->quoteIdentifiersForSelect([$identifier]));
368 }
369
370 /**
371 * @test
372 */
373 public function quoteIdentifiersForSelectWithInvalidAlias()
374 {
375 $this->expectException(\InvalidArgumentException::class);
376 $this->expectExceptionCode(1461170686);
377
378 $this->connection->quoteIdentifier(Argument::cetera())->will(
379 function ($args) {
380 $platform = new MockPlatform();
381
382 return $platform->quoteIdentifier($args[0]);
383 }
384 );
385 $this->subject->quoteIdentifiersForSelect(['aField AS anotherField,someField AS someThing']);
386 }
387
388 /**
389 * @test
390 */
391 public function selectDoesNotQuoteStarPlaceholder()
392 {
393 $this->connection->quoteIdentifier('aField')
394 ->shouldBeCalled()
395 ->willReturnArgument(0);
396 $this->connection->quoteIdentifier('*')
397 ->shouldNotBeCalled();
398 $this->concreteQueryBuilder->select(Argument::exact('aField'), Argument::exact('*'))
399 ->shouldBeCalled()
400 ->willReturn($this->subject);
401
402 $this->subject->select('aField', '*');
403 }
404
405 /**
406 * @test
407 */
408 public function addSelectQuotesIdentifiersAndDelegatesToConcreteQueryBuilder()
409 {
410 $this->connection->quoteIdentifier('aField')
411 ->shouldBeCalled()
412 ->willReturnArgument(0);
413 $this->connection->quoteIdentifier('anotherField')
414 ->shouldBeCalled()
415 ->willReturnArgument(0);
416 $this->concreteQueryBuilder->addSelect(Argument::exact('aField'), Argument::exact('anotherField'))
417 ->shouldBeCalled()
418 ->willReturn($this->subject);
419
420 $this->subject->addSelect('aField', 'anotherField');
421 }
422
423 /**
424 * @test
425 */
426 public function addSelectDoesNotQuoteStarPlaceholder()
427 {
428 $this->connection->quoteIdentifier('aField')
429 ->shouldBeCalled()
430 ->willReturnArgument(0);
431 $this->connection->quoteIdentifier('*')
432 ->shouldNotBeCalled();
433 $this->concreteQueryBuilder->addSelect(Argument::exact('aField'), Argument::exact('*'))
434 ->shouldBeCalled()
435 ->willReturn($this->subject);
436
437 $this->subject->addSelect('aField', '*');
438 }
439
440 /**
441 * @test
442 */
443 public function selectLiteralDirectlyDelegatesToConcreteQueryBuilder()
444 {
445 $this->connection->quoteIdentifier(Argument::cetera())
446 ->shouldNotBeCalled();
447 $this->concreteQueryBuilder->select(Argument::exact('MAX(aField) AS anAlias'))
448 ->shouldBeCalled()
449 ->willReturn($this->subject);
450
451 $this->subject->selectLiteral('MAX(aField) AS anAlias');
452 }
453
454 /**
455 * @test
456 */
457 public function addSelectLiteralDirectlyDelegatesToConcreteQueryBuilder()
458 {
459 $this->connection->quoteIdentifier(Argument::cetera())
460 ->shouldNotBeCalled();
461 $this->concreteQueryBuilder->addSelect(Argument::exact('MAX(aField) AS anAlias'))
462 ->shouldBeCalled()
463 ->willReturn($this->subject);
464
465 $this->subject->addSelectLiteral('MAX(aField) AS anAlias');
466 }
467
468 /**
469 * @test
470 * @todo: Test with alias
471 */
472 public function deleteQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
473 {
474 $this->connection->quoteIdentifier('aTable')
475 ->shouldBeCalled()
476 ->willReturnArgument(0);
477 $this->concreteQueryBuilder->delete(Argument::exact('aTable'), Argument::cetera())
478 ->shouldBeCalled()
479 ->willReturn($this->subject);
480
481 $this->subject->delete('aTable');
482 }
483
484 /**
485 * @test
486 * @todo: Test with alias
487 */
488 public function updateQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
489 {
490 $this->connection->quoteIdentifier('aTable')
491 ->shouldBeCalled()
492 ->willReturnArgument(0);
493 $this->concreteQueryBuilder->update(Argument::exact('aTable'), Argument::cetera())
494 ->shouldBeCalled()
495 ->willReturn($this->subject);
496
497 $this->subject->update('aTable');
498 }
499
500 /**
501 * @test
502 */
503 public function insertQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
504 {
505 $this->connection->quoteIdentifier('aTable')
506 ->shouldBeCalled()
507 ->willReturnArgument(0);
508 $this->concreteQueryBuilder->insert(Argument::exact('aTable'))
509 ->shouldBeCalled()
510 ->willReturn($this->subject);
511
512 $this->subject->insert('aTable');
513 }
514
515 /**
516 * @test
517 * @todo: Test with alias
518 */
519 public function fromQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
520 {
521 $this->connection->quoteIdentifier('aTable')
522 ->shouldBeCalled()
523 ->willReturnArgument(0);
524 $this->concreteQueryBuilder->from(Argument::exact('aTable'), Argument::cetera())
525 ->shouldBeCalled()
526 ->willReturn($this->subject);
527
528 $this->subject->from('aTable');
529 }
530
531 /**
532 * @test
533 */
534 public function joinQuotesIdentifiersAndDelegatesToConcreteQueryBuilder()
535 {
536 $this->connection->quoteIdentifier('fromAlias')
537 ->shouldBeCalled()
538 ->willReturnArgument(0);
539 $this->connection->quoteIdentifier('join')
540 ->shouldBeCalled()
541 ->willReturnArgument(0);
542 $this->connection->quoteIdentifier('alias')
543 ->shouldBeCalled()
544 ->willReturnArgument(0);
545 $this->concreteQueryBuilder->innerJoin('fromAlias', 'join', 'alias', null)
546 ->shouldBeCalled()
547 ->willReturn($this->subject);
548
549 $this->subject->join('fromAlias', 'join', 'alias');
550 }
551
552 /**
553 * @test
554 */
555 public function innerJoinQuotesIdentifiersAndDelegatesToConcreteQueryBuilder()
556 {
557 $this->connection->quoteIdentifier('fromAlias')
558 ->shouldBeCalled()
559 ->willReturnArgument(0);
560 $this->connection->quoteIdentifier('join')
561 ->shouldBeCalled()
562 ->willReturnArgument(0);
563 $this->connection->quoteIdentifier('alias')
564 ->shouldBeCalled()
565 ->willReturnArgument(0);
566 $this->concreteQueryBuilder->innerJoin('fromAlias', 'join', 'alias', null)
567 ->shouldBeCalled()
568 ->willReturn($this->subject);
569
570 $this->subject->innerJoin('fromAlias', 'join', 'alias');
571 }
572
573 /**
574 * @test
575 */
576 public function leftJoinQuotesIdentifiersAndDelegatesToConcreteQueryBuilder()
577 {
578 $this->connection->quoteIdentifier('fromAlias')
579 ->shouldBeCalled()
580 ->willReturnArgument(0);
581 $this->connection->quoteIdentifier('join')
582 ->shouldBeCalled()
583 ->willReturnArgument(0);
584 $this->connection->quoteIdentifier('alias')
585 ->shouldBeCalled()
586 ->willReturnArgument(0);
587 $this->concreteQueryBuilder->leftJoin('fromAlias', 'join', 'alias', null)
588 ->shouldBeCalled()
589 ->willReturn($this->subject);
590
591 $this->subject->leftJoin('fromAlias', 'join', 'alias');
592 }
593
594 /**
595 * @test
596 */
597 public function rightJoinQuotesIdentifiersAndDelegatesToConcreteQueryBuilder()
598 {
599 $this->connection->quoteIdentifier('fromAlias')
600 ->shouldBeCalled()
601 ->willReturnArgument(0);
602 $this->connection->quoteIdentifier('join')
603 ->shouldBeCalled()
604 ->willReturnArgument(0);
605 $this->connection->quoteIdentifier('alias')
606 ->shouldBeCalled()
607 ->willReturnArgument(0);
608 $this->concreteQueryBuilder->rightJoin('fromAlias', 'join', 'alias', null)
609 ->shouldBeCalled()
610 ->willReturn($this->subject);
611
612 $this->subject->rightJoin('fromAlias', 'join', 'alias');
613 }
614
615 /**
616 * @test
617 */
618 public function setQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
619 {
620 $this->connection->quoteIdentifier('aField')
621 ->shouldBeCalled()
622 ->willReturnArgument(0);
623 $this->concreteQueryBuilder->createNamedParameter('aValue', Argument::cetera())
624 ->shouldBeCalled()
625 ->willReturn(':dcValue1');
626 $this->concreteQueryBuilder->set('aField', ':dcValue1')
627 ->shouldBeCalled()
628 ->willReturn($this->subject);
629
630 $this->subject->set('aField', 'aValue');
631 }
632
633 /**
634 * @test
635 */
636 public function setWithoutNamedParameterQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
637 {
638 $this->connection->quoteIdentifier('aField')
639 ->shouldBeCalled()
640 ->willReturnArgument(0);
641 $this->concreteQueryBuilder->createNamedParameter(Argument::cetera())->shouldNotBeCalled();
642 $this->concreteQueryBuilder->set('aField', 'aValue')
643 ->shouldBeCalled()
644 ->willReturn($this->subject);
645
646 $this->subject->set('aField', 'aValue', false);
647 }
648
649 /**
650 * @test
651 */
652 public function whereDelegatesToConcreteQueryBuilder()
653 {
654 $this->concreteQueryBuilder->where('uid=1', 'type=9')
655 ->shouldBeCalled()
656 ->willReturn($this->subject);
657
658 $this->subject->where('uid=1', 'type=9');
659 }
660
661 /**
662 * @test
663 */
664 public function andWhereDelegatesToConcreteQueryBuilder()
665 {
666 $this->concreteQueryBuilder->andWhere('uid=1', 'type=9')
667 ->shouldBeCalled()
668 ->willReturn($this->subject);
669
670 $this->subject->andWhere('uid=1', 'type=9');
671 }
672
673 /**
674 * @test
675 */
676 public function orWhereDelegatesToConcreteQueryBuilder()
677 {
678 $this->concreteQueryBuilder->orWhere('uid=1', 'type=9')
679 ->shouldBeCalled()
680 ->willReturn($this->subject);
681
682 $this->subject->orWhere('uid=1', 'type=9');
683 }
684
685 /**
686 * @test
687 */
688 public function groupByQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
689 {
690 $this->connection->quoteIdentifiers(['aField', 'anotherField'])
691 ->shouldBeCalled()
692 ->willReturnArgument(0);
693 $this->concreteQueryBuilder->groupBy('aField', 'anotherField')
694 ->shouldBeCalled()
695 ->willReturn($this->subject);
696
697 $this->subject->groupBy('aField', 'anotherField');
698 }
699
700 /**
701 * @test
702 */
703 public function addGroupByQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
704 {
705 $this->connection->quoteIdentifiers(['aField', 'anotherField'])
706 ->shouldBeCalled()
707 ->willReturnArgument(0);
708 $this->concreteQueryBuilder->addGroupBy('aField', 'anotherField')
709 ->shouldBeCalled()
710 ->willReturn($this->subject);
711
712 $this->subject->addGroupBy('aField', 'anotherField');
713 }
714
715 /**
716 * @test
717 */
718 public function setValueQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
719 {
720 $this->connection->quoteIdentifier('aField')
721 ->shouldBeCalled()
722 ->willReturnArgument(0);
723 $this->concreteQueryBuilder->createNamedParameter('aValue', Argument::cetera())
724 ->shouldBeCalled()
725 ->willReturn(':dcValue1');
726 $this->concreteQueryBuilder->setValue('aField', ':dcValue1')
727 ->shouldBeCalled()
728 ->willReturn($this->subject);
729
730 $this->subject->setValue('aField', 'aValue');
731 }
732
733 /**
734 * @test
735 */
736 public function setValueWithoudNamedParameterQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
737 {
738 $this->connection->quoteIdentifier('aField')
739 ->shouldBeCalled()
740 ->willReturnArgument(0);
741 $this->concreteQueryBuilder->setValue('aField', 'aValue')
742 ->shouldBeCalled()
743 ->willReturn($this->subject);
744
745 $this->subject->setValue('aField', 'aValue', false);
746 }
747
748 /**
749 * @test
750 */
751 public function valuesQuotesIdentifiersAndDelegatesToConcreteQueryBuilder()
752 {
753 $this->connection->quoteColumnValuePairs(['aField' => ':dcValue1', 'aValue' => ':dcValue2'])
754 ->shouldBeCalled()
755 ->willReturnArgument(0);
756 $this->concreteQueryBuilder->createNamedParameter(1, Argument::cetera())
757 ->shouldBeCalled()
758 ->willReturn(':dcValue1');
759 $this->concreteQueryBuilder->createNamedParameter(2, Argument::cetera())
760 ->shouldBeCalled()
761 ->willReturn(':dcValue2');
762 $this->concreteQueryBuilder->values(['aField' => ':dcValue1', 'aValue' => ':dcValue2'])
763 ->shouldBeCalled()
764 ->willReturn($this->subject);
765
766 $this->subject->values(['aField' => 1, 'aValue' => 2]);
767 }
768
769 /**
770 * @test
771 */
772 public function valuesWithoutNamedParametersQuotesIdentifiersAndDelegatesToConcreteQueryBuilder()
773 {
774 $this->connection->quoteColumnValuePairs(['aField' => 1, 'aValue' => 2])
775 ->shouldBeCalled()
776 ->willReturnArgument(0);
777 $this->concreteQueryBuilder->values(['aField' => 1, 'aValue' => 2])
778 ->shouldBeCalled()
779 ->willReturn($this->subject);
780
781 $this->subject->values(['aField' => 1, 'aValue' => 2], false);
782 }
783
784 /**
785 * @test
786 */
787 public function havingDelegatesToConcreteQueryBuilder()
788 {
789 $this->concreteQueryBuilder->having('uid=1', 'type=9')
790 ->shouldBeCalled()
791 ->willReturn($this->subject);
792
793 $this->subject->having('uid=1', 'type=9');
794 }
795
796 /**
797 * @test
798 */
799 public function andHavingDelegatesToConcreteQueryBuilder()
800 {
801 $this->concreteQueryBuilder->andHaving('uid=1', 'type=9')
802 ->shouldBeCalled()
803 ->willReturn($this->subject);
804
805 $this->subject->andHaving('uid=1', 'type=9');
806 }
807
808 /**
809 * @test
810 */
811 public function orHavingDelegatesToConcreteQueryBuilder()
812 {
813 $this->concreteQueryBuilder->orHaving('uid=1', 'type=9')
814 ->shouldBeCalled()
815 ->willReturn($this->subject);
816
817 $this->subject->orHaving('uid=1', 'type=9');
818 }
819
820 /**
821 * @test
822 */
823 public function orderByQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
824 {
825 $this->connection->quoteIdentifier('aField')
826 ->shouldBeCalled()
827 ->willReturnArgument(0);
828 $this->concreteQueryBuilder->orderBy('aField', null)
829 ->shouldBeCalled()
830 ->willReturn($this->subject);
831
832 $this->subject->orderBy('aField');
833 }
834
835 /**
836 * @test
837 */
838 public function addOrderByQuotesIdentifierAndDelegatesToConcreteQueryBuilder()
839 {
840 $this->connection->quoteIdentifier('aField')
841 ->shouldBeCalled()
842 ->willReturnArgument(0);
843 $this->concreteQueryBuilder->addOrderBy('aField', 'DESC')
844 ->shouldBeCalled()
845 ->willReturn($this->subject);
846
847 $this->subject->addOrderBy('aField', 'DESC');
848 }
849
850 /**
851 * @test
852 */
853 public function getQueryPartDelegatesToConcreteQueryBuilder()
854 {
855 $this->concreteQueryBuilder->getQueryPart('from')
856 ->shouldBeCalled()
857 ->willReturn('aTable');
858
859 $this->subject->getQueryPart('from');
860 }
861
862 /**
863 * @test
864 */
865 public function getQueryPartsDelegatesToConcreteQueryBuilder()
866 {
867 $this->concreteQueryBuilder->getQueryParts()
868 ->shouldBeCalled()
869 ->willReturn([]);
870
871 $this->subject->getQueryParts();
872 }
873
874 /**
875 * @test
876 */
877 public function resetQueryPartsDelegatesToConcreteQueryBuilder()
878 {
879 $this->concreteQueryBuilder->resetQueryParts(['select', 'from'])
880 ->shouldBeCalled()
881 ->willReturn($this->subject);
882
883 $this->subject->resetQueryParts(['select', 'from']);
884 }
885
886 /**
887 * @test
888 */
889 public function resetQueryPartDelegatesToConcreteQueryBuilder()
890 {
891 $this->concreteQueryBuilder->resetQueryPart('select')
892 ->shouldBeCalled()
893 ->willReturn($this->subject);
894
895 $this->subject->resetQueryPart('select');
896 }
897
898 /**
899 * @test
900 */
901 public function createNamedParameterDelegatesToConcreteQueryBuilder()
902 {
903 $this->concreteQueryBuilder->createNamedParameter(5, Argument::cetera())
904 ->shouldBeCalled()
905 ->willReturn(':dcValue1');
906
907 $this->subject->createNamedParameter(5);
908 }
909
910 /**
911 * @test
912 */
913 public function createPositionalParameterDelegatesToConcreteQueryBuilder()
914 {
915 $this->concreteQueryBuilder->createPositionalParameter(5, Argument::cetera())
916 ->shouldBeCalled()
917 ->willReturn('?');
918
919 $this->subject->createPositionalParameter(5);
920 }
921
922 /**
923 * @test
924 */
925 public function queryRestrictionsAreAddedForSelectOnExecute()
926 {
927 $GLOBALS['TCA']['pages']['ctrl'] = [
928 'tstamp' => 'tstamp',
929 'versioningWS' => true,
930 'delete' => 'deleted',
931 'crdate' => 'crdate',
932 'enablecolumns' => [
933 'disabled' => 'hidden',
934 ],
935 ];
936
937 $this->connection->quoteIdentifier(Argument::cetera())
938 ->willReturnArgument(0);
939 $this->connection->quoteIdentifiers(Argument::cetera())
940 ->willReturnArgument(0);
941
942 $connectionBuilder = GeneralUtility::makeInstance(
943 \Doctrine\DBAL\Query\QueryBuilder::class,
944 $this->connection->reveal()
945 );
946
947 $expressionBuilder = GeneralUtility::makeInstance(ExpressionBuilder::class, $this->connection->reveal());
948 $this->connection->getExpressionBuilder()
949 ->willReturn($expressionBuilder);
950
951 $subject = GeneralUtility::makeInstance(
952 QueryBuilder::class,
953 $this->connection->reveal(),
954 null,
955 $connectionBuilder
956 );
957
958 $subject->select('*')
959 ->from('pages')
960 ->where('uid=1');
961
962 $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
963 $this->connection->executeQuery($expectedSQL, Argument::cetera())
964 ->shouldBeCalled();
965
966 $subject->execute();
967 }
968
969 /**
970 * @test
971 */
972 public function queryRestrictionsAreAddedForCountOnExecute()
973 {
974 $GLOBALS['TCA']['pages']['ctrl'] = [
975 'tstamp' => 'tstamp',
976 'versioningWS' => true,
977 'delete' => 'deleted',
978 'crdate' => 'crdate',
979 'enablecolumns' => [
980 'disabled' => 'hidden',
981 ],
982 ];
983
984 $this->connection->quoteIdentifier(Argument::cetera())
985 ->willReturnArgument(0);
986 $this->connection->quoteIdentifiers(Argument::cetera())
987 ->willReturnArgument(0);
988
989 $connectionBuilder = GeneralUtility::makeInstance(
990 \Doctrine\DBAL\Query\QueryBuilder::class,
991 $this->connection->reveal()
992 );
993
994 $expressionBuilder = GeneralUtility::makeInstance(ExpressionBuilder::class, $this->connection->reveal());
995 $this->connection->getExpressionBuilder()
996 ->willReturn($expressionBuilder);
997
998 $subject = GeneralUtility::makeInstance(
999 QueryBuilder::class,
1000 $this->connection->reveal(),
1001 null,
1002 $connectionBuilder
1003 );
1004
1005 $subject->count('uid')
1006 ->from('pages')
1007 ->where('uid=1');
1008
1009 $expectedSQL = 'SELECT COUNT(uid) FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
1010 $this->connection->executeQuery($expectedSQL, Argument::cetera())
1011 ->shouldBeCalled();
1012
1013 $subject->execute();
1014 }
1015
1016 /**
1017 * @test
1018 */
1019 public function queryRestrictionsAreReevaluatedOnSettingsChangeForGetSQL()
1020 {
1021 $GLOBALS['TCA']['pages']['ctrl'] = [
1022 'tstamp' => 'tstamp',
1023 'versioningWS' => true,
1024 'delete' => 'deleted',
1025 'crdate' => 'crdate',
1026 'enablecolumns' => [
1027 'disabled' => 'hidden',
1028 ],
1029 ];
1030
1031 $this->connection->quoteIdentifier(Argument::cetera())
1032 ->willReturnArgument(0);
1033 $this->connection->quoteIdentifiers(Argument::cetera())
1034 ->willReturnArgument(0);
1035 $this->connection->getExpressionBuilder()
1036 ->willReturn(GeneralUtility::makeInstance(ExpressionBuilder::class, $this->connection->reveal()));
1037
1038 $concreteQueryBuilder = GeneralUtility::makeInstance(
1039 \Doctrine\DBAL\Query\QueryBuilder::class,
1040 $this->connection->reveal()
1041 );
1042
1043 $subject = GeneralUtility::makeInstance(
1044 QueryBuilder::class,
1045 $this->connection->reveal(),
1046 null,
1047 $concreteQueryBuilder
1048 );
1049
1050 $subject->select('*')
1051 ->from('pages')
1052 ->where('uid=1');
1053
1054 $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
1055 $this->assertSame($expectedSQL, $subject->getSQL());
1056
1057 $subject->getRestrictions()->removeAll()->add(new DeletedRestriction());
1058
1059 $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND (pages.deleted = 0)';
1060 $this->assertSame($expectedSQL, $subject->getSQL());
1061 }
1062
1063 /**
1064 * @test
1065 */
1066 public function queryRestrictionsAreReevaluatedOnSettingsChangeForExecute()
1067 {
1068 $GLOBALS['TCA']['pages']['ctrl'] = [
1069 'tstamp' => 'tstamp',
1070 'versioningWS' => true,
1071 'delete' => 'deleted',
1072 'crdate' => 'crdate',
1073 'enablecolumns' => [
1074 'disabled' => 'hidden',
1075 ],
1076 ];
1077
1078 $this->connection->quoteIdentifier(Argument::cetera())
1079 ->willReturnArgument(0);
1080 $this->connection->quoteIdentifiers(Argument::cetera())
1081 ->willReturnArgument(0);
1082 $this->connection->getExpressionBuilder()
1083 ->willReturn(GeneralUtility::makeInstance(ExpressionBuilder::class, $this->connection->reveal()));
1084
1085 $concreteQueryBuilder = GeneralUtility::makeInstance(
1086 \Doctrine\DBAL\Query\QueryBuilder::class,
1087 $this->connection->reveal()
1088 );
1089
1090 $subject = GeneralUtility::makeInstance(
1091 QueryBuilder::class,
1092 $this->connection->reveal(),
1093 null,
1094 $concreteQueryBuilder
1095 );
1096
1097 $subject->select('*')
1098 ->from('pages')
1099 ->where('uid=1');
1100
1101 $subject->getRestrictions()->removeAll()->add(new DeletedRestriction());
1102
1103 $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND (pages.deleted = 0)';
1104 $this->connection->executeQuery($expectedSQL, Argument::cetera())
1105 ->shouldBeCalled();
1106
1107 $subject->execute();
1108
1109 $subject->resetRestrictions();
1110
1111 $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
1112 $this->connection->executeQuery($expectedSQL, Argument::cetera())
1113 ->shouldBeCalled();
1114
1115 $subject->execute();
1116 }
1117
1118 /**
1119 * @test
1120 */
1121 public function getQueriedTablesReturnsSameTableTwiceForInnerJoin()
1122 {
1123 $this->concreteQueryBuilder->getQueryPart('from')
1124 ->shouldBeCalled()
1125 ->willReturn([
1126 [
1127 'table' => 'aTable',
1128 ],
1129 ]);
1130 $this->concreteQueryBuilder->getQueryPart('join')
1131 ->shouldBeCalled()
1132 ->willReturn([
1133 'aTable' => [
1134 [
1135 'joinType' => 'inner',
1136 'joinTable' => 'aTable',
1137 'joinAlias' => 'aTable_alias'
1138 ]
1139 ]
1140 ]);
1141 $result = $this->callInaccessibleMethod($this->subject, 'getQueriedTables');
1142
1143 $expected = [
1144 'aTable' => 'aTable',
1145 'aTable_alias' => 'aTable'
1146 ];
1147 $this->assertEquals($expected, $result);
1148 }
1149
1150 /**
1151 * @return array
1152 */
1153 public function unquoteSingleIdentifierUnquotesCorrectlyOnDifferentPlatformsDataProvider()
1154 {
1155 return [
1156 'mysql' => [
1157 'platform' => MySqlPlatform::class,
1158 'quoteChar' => '`',
1159 'input' => '`anIdentifier`',
1160 'expected' => 'anIdentifier',
1161 ],
1162 'mysql with spaces' => [
1163 'platform' => MySqlPlatform::class,
1164 'quoteChar' => '`',
1165 'input' => ' `anIdentifier` ',
1166 'expected' => 'anIdentifier',
1167 ],
1168 'postgres' => [
1169 'platform' => PostgreSqlPlatform::class,
1170 'quoteChar' => '"',
1171 'input' => '"anIdentifier"',
1172 'expected' => 'anIdentifier',
1173 ],
1174 'mssql' => [
1175 'platform' => SQLServerPlatform::class,
1176 'quoteChar' => '', // no single quote character, but [ and ]
1177 'input' => '[anIdentifier]',
1178 'expected' => 'anIdentifier',
1179 ],
1180 ];
1181 }
1182
1183 /**
1184 * @test
1185 * @dataProvider unquoteSingleIdentifierUnquotesCorrectlyOnDifferentPlatformsDataProvider
1186 */
1187 public function unquoteSingleIdentifierUnquotesCorrectlyOnDifferentPlatforms($platform, $quoteChar, $input, $expected)
1188 {
1189 $connectionProphecy = $this->prophesize(Connection::class);
1190 $databasePlatformProphecy = $this->prophesize($platform);
1191 $databasePlatformProphecy->getIdentifierQuoteCharacter()->willReturn($quoteChar);
1192 $connectionProphecy->getDatabasePlatform()->willReturn($databasePlatformProphecy);
1193 $subject = GeneralUtility::makeInstance(QueryBuilder::class, $connectionProphecy->reveal());
1194 $result = $this->callInaccessibleMethod($subject, 'unquoteSingleIdentifier', $input);
1195 $this->assertEquals($expected, $result);
1196 }
1197
1198 /**
1199 * @test
1200 */
1201 public function cloningQueryBuilderClonesConcreteQueryBuilder()
1202 {
1203 $clonedQueryBuilder = clone $this->subject;
1204 self::assertNotSame($this->subject->getConcreteQueryBuilder(), $clonedQueryBuilder->getConcreteQueryBuilder());
1205 }
1206
1207 /**
1208 * @test
1209 */
1210 public function changingClonedQueryBuilderDoesNotInfluenceSourceOne()
1211 {
1212 $GLOBALS['TCA']['pages']['ctrl'] = [
1213 'tstamp' => 'tstamp',
1214 'versioningWS' => true,
1215 'delete' => 'deleted',
1216 'crdate' => 'crdate',
1217 'enablecolumns' => [
1218 'disabled' => 'hidden',
1219 ],
1220 ];
1221
1222 $this->connection->quoteIdentifier(Argument::cetera())
1223 ->willReturnArgument(0);
1224 $this->connection->quoteIdentifiers(Argument::cetera())
1225 ->willReturnArgument(0);
1226 $this->connection->getExpressionBuilder()
1227 ->willReturn(GeneralUtility::makeInstance(ExpressionBuilder::class, $this->connection->reveal()));
1228
1229 $concreteQueryBuilder = GeneralUtility::makeInstance(
1230 \Doctrine\DBAL\Query\QueryBuilder::class,
1231 $this->connection->reveal()
1232 );
1233
1234 $subject = GeneralUtility::makeInstance(
1235 QueryBuilder::class,
1236 $this->connection->reveal(),
1237 null,
1238 $concreteQueryBuilder
1239 );
1240
1241 $subject->select('*')
1242 ->from('pages')
1243 ->where('uid=1');
1244
1245 $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
1246 $this->assertSame($expectedSQL, $subject->getSQL());
1247
1248 $clonedQueryBuilder = clone $subject;
1249 //just after cloning both query builders should return the same sql
1250 $this->assertSame($expectedSQL, $clonedQueryBuilder->getSQL());
1251
1252 //change cloned QueryBuilder
1253 $clonedQueryBuilder->count('*');
1254 $expectedCountSQL = 'SELECT COUNT(*) FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
1255 $this->assertSame($expectedCountSQL, $clonedQueryBuilder->getSQL());
1256
1257 //check if the original QueryBuilder has not changed
1258 $this->assertSame($expectedSQL, $subject->getSQL());
1259
1260 //change restrictions in the original QueryBuilder and check if cloned has changed
1261 $subject->getRestrictions()->removeAll()->add(new DeletedRestriction());
1262 $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND (pages.deleted = 0)';
1263 $this->assertSame($expectedSQL, $subject->getSQL());
1264
1265 $this->assertSame($expectedCountSQL, $clonedQueryBuilder->getSQL());
1266 }
1267 }