Completed
Push — master ( 44ce57...d89063 )
by
unknown
15:18
created

QueryBuilder::resetQueryPart()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
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
namespace TYPO3\CMS\Core\Database\Query;
19
20
use Doctrine\DBAL\Platforms\MySqlPlatform;
21
use Doctrine\DBAL\Platforms\OraclePlatform;
22
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
23
use Doctrine\DBAL\Platforms\SqlitePlatform;
24
use Doctrine\DBAL\Platforms\SQLServerPlatform;
25
use Doctrine\DBAL\Query\Expression\CompositeExpression;
26
use TYPO3\CMS\Core\Database\Connection;
27
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
28
use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
29
use TYPO3\CMS\Core\Database\Query\Restriction\LimitToTablesRestrictionContainer;
30
use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33
/**
34
 * Object oriented approach to building SQL queries.
35
 *
36
 * It's a facade to the Doctrine DBAL QueryBuilder that implements PHP7 type hinting and automatic
37
 * quoting of table and column names.
38
 *
39
 * <code>
40
 * $query->select('aField', 'anotherField')
41
 *       ->from('aTable')
42
 *       ->where($query->expr()->eq('aField', 1))
43
 *       ->andWhere($query->expr()->gte('anotherField',10'))
44
 *       ->execute()
45
 * </code>
46
 *
47
 * Additional functionality included is support for COUNT() and TRUNCATE() statements.
48
 */
49
class QueryBuilder
50
{
51
    /**
52
     * The DBAL Connection.
53
     *
54
     * @var Connection
55
     */
56
    protected $connection;
57
58
    /**
59
     * @var \Doctrine\DBAL\Query\QueryBuilder
60
     */
61
    protected $concreteQueryBuilder;
62
63
    /**
64
     * @var QueryRestrictionContainerInterface
65
     */
66
    protected $restrictionContainer;
67
68
    /**
69
     * @var array
70
     */
71
    protected $additionalRestrictions;
72
73
    /**
74
     * Initializes a new QueryBuilder.
75
     *
76
     * @param Connection $connection The DBAL Connection.
77
     * @param QueryRestrictionContainerInterface $restrictionContainer
78
     * @param \Doctrine\DBAL\Query\QueryBuilder $concreteQueryBuilder
79
     * @param array $additionalRestrictions
80
     */
81
    public function __construct(
82
        Connection $connection,
83
        QueryRestrictionContainerInterface $restrictionContainer = null,
84
        \Doctrine\DBAL\Query\QueryBuilder $concreteQueryBuilder = null,
85
        array $additionalRestrictions = null
86
    ) {
87
        $this->connection = $connection;
88
        $this->additionalRestrictions = $additionalRestrictions ?: $GLOBALS['TYPO3_CONF_VARS']['DB']['additionalQueryRestrictions'] ?? [];
89
        $this->setRestrictions($restrictionContainer ?: GeneralUtility::makeInstance(DefaultRestrictionContainer::class));
90
        $this->concreteQueryBuilder = $concreteQueryBuilder ?: GeneralUtility::makeInstance(\Doctrine\DBAL\Query\QueryBuilder::class, $connection);
91
    }
92
93
    /**
94
     * @return QueryRestrictionContainerInterface
95
     */
96
    public function getRestrictions()
97
    {
98
        return $this->restrictionContainer;
99
    }
100
101
    /**
102
     * @param QueryRestrictionContainerInterface $restrictionContainer
103
     */
104
    public function setRestrictions(QueryRestrictionContainerInterface $restrictionContainer)
105
    {
106
        foreach ($this->additionalRestrictions as $restrictionClass => $options) {
107
            if (empty($options['disabled'])) {
108
                $restriction = GeneralUtility::makeInstance($restrictionClass);
109
                $restrictionContainer->add($restriction);
110
            }
111
        }
112
        $this->restrictionContainer = $restrictionContainer;
113
    }
114
115
    /**
116
     * Limits ALL currently active restrictions of the restriction container to the table aliases given
117
     *
118
     * @param array $tableAliases
119
     */
120
    public function limitRestrictionsToTables(array $tableAliases): void
121
    {
122
        $this->restrictionContainer = GeneralUtility::makeInstance(LimitToTablesRestrictionContainer::class)->addForTables($this->restrictionContainer, $tableAliases);
123
    }
124
125
    /**
126
     * Re-apply default restrictions
127
     */
128
    public function resetRestrictions()
129
    {
130
        $this->setRestrictions(GeneralUtility::makeInstance(DefaultRestrictionContainer::class));
131
    }
132
133
    /**
134
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
135
     * This producer method is intended for convenient inline usage. Example:
136
     *
137
     * For more complex expression construction, consider storing the expression
138
     * builder object in a local variable.
139
     *
140
     * @return ExpressionBuilder
141
     */
142
    public function expr(): ExpressionBuilder
143
    {
144
        return $this->connection->getExpressionBuilder();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection->getExpressionBuilder() returns the type Doctrine\DBAL\Query\Expression\ExpressionBuilder which is incompatible with the type-hinted return TYPO3\CMS\Core\Database\...ssion\ExpressionBuilder.
Loading history...
145
    }
146
147
    /**
148
     * Gets the type of the currently built query.
149
     *
150
     * @return int
151
     * @internal
152
     */
153
    public function getType(): int
154
    {
155
        return $this->concreteQueryBuilder->getType();
156
    }
157
158
    /**
159
     * Gets the associated DBAL Connection for this query builder.
160
     *
161
     * @return Connection
162
     */
163
    public function getConnection(): Connection
164
    {
165
        return $this->connection;
166
    }
167
168
    /**
169
     * Gets the state of this query builder instance.
170
     *
171
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
172
     * @internal
173
     */
174
    public function getState(): int
175
    {
176
        return $this->concreteQueryBuilder->getState();
177
    }
178
179
    /**
180
     * Gets the concrete implementation of the query builder
181
     *
182
     * @return \Doctrine\DBAL\Query\QueryBuilder
183
     * @internal
184
     */
185
    public function getConcreteQueryBuilder(): \Doctrine\DBAL\Query\QueryBuilder
186
    {
187
        return $this->concreteQueryBuilder;
188
    }
189
190
    /**
191
     * Executes this query using the bound parameters and their types.
192
     *
193
     * @return \Doctrine\DBAL\Driver\Statement|int
194
     */
195
    public function execute()
196
    {
197
        if ($this->getType() !== \Doctrine\DBAL\Query\QueryBuilder::SELECT) {
198
            return $this->concreteQueryBuilder->execute();
199
        }
200
201
        // Set additional query restrictions
202
        $originalWhereConditions = $this->addAdditionalWhereConditions();
203
204
        $result = $this->concreteQueryBuilder->execute();
205
206
        // Restore the original query conditions in case the user keeps
207
        // on modifying the state.
208
        $this->concreteQueryBuilder->add('where', $originalWhereConditions, false);
209
210
        return $result;
211
    }
212
213
    /**
214
     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
215
     *
216
     * If the statement is a SELECT TYPE query restrictions based on TCA settings will
217
     * automatically be applied based on the current QuerySettings.
218
     *
219
     * @return string The SQL query string.
220
     */
221
    public function getSQL(): string
222
    {
223
        if ($this->getType() !== \Doctrine\DBAL\Query\QueryBuilder::SELECT) {
224
            return $this->concreteQueryBuilder->getSQL();
225
        }
226
227
        // Set additional query restrictions
228
        $originalWhereConditions = $this->addAdditionalWhereConditions();
229
230
        $sql = $this->concreteQueryBuilder->getSQL();
231
232
        // Restore the original query conditions in case the user keeps
233
        // on modifying the state.
234
        $this->concreteQueryBuilder->add('where', $originalWhereConditions, false);
235
236
        return $sql;
237
    }
238
239
    /**
240
     * Sets a query parameter for the query being constructed.
241
     *
242
     * @param string|int $key The parameter position or name.
243
     * @param mixed $value The parameter value.
244
     * @param int|null $type One of the Connection::PARAM_* constants.
245
     *
246
     * @return QueryBuilder This QueryBuilder instance.
247
     */
248
    public function setParameter($key, $value, int $type = null): QueryBuilder
249
    {
250
        $this->concreteQueryBuilder->setParameter($key, $value, $type);
251
252
        return $this;
253
    }
254
255
    /**
256
     * Sets a collection of query parameters for the query being constructed.
257
     *
258
     * @param array $params The query parameters to set.
259
     * @param array $types The query parameters types to set.
260
     *
261
     * @return QueryBuilder This QueryBuilder instance.
262
     */
263
    public function setParameters(array $params, array $types = []): QueryBuilder
264
    {
265
        $this->concreteQueryBuilder->setParameters($params, $types);
266
267
        return $this;
268
    }
269
270
    /**
271
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
272
     *
273
     * @return array The currently defined query parameters indexed by parameter index or name.
274
     */
275
    public function getParameters(): array
276
    {
277
        return $this->concreteQueryBuilder->getParameters();
278
    }
279
280
    /**
281
     * Gets a (previously set) query parameter of the query being constructed.
282
     *
283
     * @param string|int $key The key (index or name) of the bound parameter.
284
     *
285
     * @return mixed The value of the bound parameter.
286
     */
287
    public function getParameter($key)
288
    {
289
        return $this->concreteQueryBuilder->getParameter($key);
290
    }
291
292
    /**
293
     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
294
     *
295
     * @return array The currently defined query parameter types indexed by parameter index or name.
296
     */
297
    public function getParameterTypes(): array
298
    {
299
        return $this->concreteQueryBuilder->getParameterTypes();
300
    }
301
302
    /**
303
     * Gets a (previously set) query parameter type of the query being constructed.
304
     *
305
     * @param string|int $key The key (index or name) of the bound parameter type.
306
     *
307
     * @return mixed The value of the bound parameter type.
308
     */
309
    public function getParameterType($key)
310
    {
311
        return $this->concreteQueryBuilder->getParameterType($key);
312
    }
313
314
    /**
315
     * Sets the position of the first result to retrieve (the "offset").
316
     *
317
     * @param int $firstResult The first result to return.
318
     *
319
     * @return QueryBuilder This QueryBuilder instance.
320
     */
321
    public function setFirstResult(int $firstResult): QueryBuilder
322
    {
323
        $this->concreteQueryBuilder->setFirstResult($firstResult);
324
325
        return $this;
326
    }
327
328
    /**
329
     * Gets the position of the first result the query object was set to retrieve (the "offset").
330
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
331
     *
332
     * @return int The position of the first result.
333
     */
334
    public function getFirstResult(): int
335
    {
336
        return (int)$this->concreteQueryBuilder->getFirstResult();
337
    }
338
339
    /**
340
     * Sets the maximum number of results to retrieve (the "limit").
341
     *
342
     * @param int $maxResults The maximum number of results to retrieve.
343
     *
344
     * @return QueryBuilder This QueryBuilder instance.
345
     */
346
    public function setMaxResults(int $maxResults): QueryBuilder
347
    {
348
        $this->concreteQueryBuilder->setMaxResults($maxResults);
349
350
        return $this;
351
    }
352
353
    /**
354
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
355
     * Returns 0 if setMaxResults was not applied to this query builder.
356
     *
357
     * @return int The maximum number of results.
358
     */
359
    public function getMaxResults(): int
360
    {
361
        return (int)$this->concreteQueryBuilder->getMaxResults();
362
    }
363
364
    /**
365
     * Either appends to or replaces a single, generic query part.
366
     *
367
     * The available parts are: 'select', 'from', 'set', 'where',
368
     * 'groupBy', 'having' and 'orderBy'.
369
     *
370
     * @param string $sqlPartName
371
     * @param string|array $sqlPart
372
     * @param bool $append
373
     *
374
     * @return QueryBuilder This QueryBuilder instance.
375
     */
376
    public function add(string $sqlPartName, $sqlPart, bool $append = false): QueryBuilder
377
    {
378
        $this->concreteQueryBuilder->add($sqlPartName, $sqlPart, $append);
379
380
        return $this;
381
    }
382
383
    /**
384
     * Specifies the item that is to be counted in the query result.
385
     * Replaces any previously specified selections, if any.
386
     *
387
     * @param string $item Will be quoted according to database platform automatically.
388
     * @return QueryBuilder This QueryBuilder instance.
389
     */
390
    public function count(string $item): QueryBuilder
391
    {
392
        $countExpr = $this->getConnection()->getDatabasePlatform()->getCountExpression(
393
            $item === '*' ? $item : $this->quoteIdentifier($item)
394
        );
395
        $this->concreteQueryBuilder->select($countExpr);
396
397
        return $this;
398
    }
399
400
    /**
401
     * Specifies items that are to be returned in the query result.
402
     * Replaces any previously specified selections, if any.
403
     *
404
     * @param array<int,string> $selects
405
     * @return QueryBuilder This QueryBuilder instance.
406
     */
407
    public function select(string ...$selects): QueryBuilder
408
    {
409
        $this->concreteQueryBuilder->select(...$this->quoteIdentifiersForSelect($selects));
410
411
        return $this;
412
    }
413
414
    /**
415
     * Adds an item that is to be returned in the query result.
416
     *
417
     * @param array<int,string> $selects The selection expression.
418
     *
419
     * @return QueryBuilder This QueryBuilder instance.
420
     */
421
    public function addSelect(string ...$selects): QueryBuilder
422
    {
423
        $this->concreteQueryBuilder->addSelect(...$this->quoteIdentifiersForSelect($selects));
424
425
        return $this;
426
    }
427
428
    /**
429
     * Specifies items that are to be returned in the query result.
430
     * Replaces any previously specified selections, if any.
431
     * This should only be used for literal SQL expressions as no
432
     * quoting/escaping of any kind will be performed on the items.
433
     *
434
     * @param array<int,string> $selects Literal SQL expressions to be selected. Warning: No quoting will be done!
435
     * @return QueryBuilder This QueryBuilder instance.
436
     */
437
    public function selectLiteral(string ...$selects): QueryBuilder
438
    {
439
        $this->concreteQueryBuilder->select(...$selects);
440
441
        return $this;
442
    }
443
444
    /**
445
     * Adds an item that is to be returned in the query result. This should
446
     * only be used for literal SQL expressions as no quoting/escaping of
447
     * any kind will be performed on the items.
448
     *
449
     * @param array<int,string> $selects Literal SQL expressions to be selected.
450
     * @return QueryBuilder This QueryBuilder instance.
451
     */
452
    public function addSelectLiteral(string ...$selects): QueryBuilder
453
    {
454
        $this->concreteQueryBuilder->addSelect(...$selects);
455
456
        return $this;
457
    }
458
459
    /**
460
     * Turns the query being built into a bulk delete query that ranges over
461
     * a certain table.
462
     *
463
     * @param string $delete The table whose rows are subject to the deletion.
464
     *                       Will be quoted according to database platform automatically.
465
     * @param string $alias The table alias used in the constructed query.
466
     *                      Will be quoted according to database platform automatically.
467
     *
468
     * @return QueryBuilder This QueryBuilder instance.
469
     */
470
    public function delete(string $delete, string $alias = null): QueryBuilder
471
    {
472
        $this->concreteQueryBuilder->delete(
473
            $this->quoteIdentifier($delete),
474
            empty($alias) ? $alias : $this->quoteIdentifier($alias)
475
        );
476
477
        return $this;
478
    }
479
480
    /**
481
     * Turns the query being built into a bulk update query that ranges over
482
     * a certain table
483
     *
484
     * @param string $update The table whose rows are subject to the update.
485
     * @param string $alias The table alias used in the constructed query.
486
     *
487
     * @return QueryBuilder This QueryBuilder instance.
488
     */
489
    public function update(string $update, string $alias = null): QueryBuilder
490
    {
491
        $this->concreteQueryBuilder->update(
492
            $this->quoteIdentifier($update),
493
            empty($alias) ? $alias : $this->quoteIdentifier($alias)
494
        );
495
496
        return $this;
497
    }
498
499
    /**
500
     * Turns the query being built into an insert query that inserts into
501
     * a certain table
502
     *
503
     * @param string $insert The table into which the rows should be inserted.
504
     *
505
     * @return QueryBuilder This QueryBuilder instance.
506
     */
507
    public function insert(string $insert): QueryBuilder
508
    {
509
        $this->concreteQueryBuilder->insert($this->quoteIdentifier($insert));
510
511
        return $this;
512
    }
513
514
    /**
515
     * Creates and adds a query root corresponding to the table identified by the
516
     * given alias, forming a cartesian product with any existing query roots.
517
     *
518
     * @param string $from The table. Will be quoted according to database platform automatically.
519
     * @param string $alias The alias of the table. Will be quoted according to database platform automatically.
520
     *
521
     * @return QueryBuilder This QueryBuilder instance.
522
     */
523
    public function from(string $from, string $alias = null): QueryBuilder
524
    {
525
        $this->concreteQueryBuilder->from(
526
            $this->quoteIdentifier($from),
527
            empty($alias) ? $alias : $this->quoteIdentifier($alias)
528
        );
529
530
        return $this;
531
    }
532
533
    /**
534
     * Creates and adds a join to the query.
535
     *
536
     * @param string $fromAlias The alias that points to a from clause.
537
     * @param string $join The table name to join.
538
     * @param string $alias The alias of the join table.
539
     * @param string $condition The condition for the join.
540
     *
541
     * @return QueryBuilder This QueryBuilder instance.
542
     */
543
    public function join(string $fromAlias, string $join, string $alias, string $condition = null): QueryBuilder
544
    {
545
        $this->concreteQueryBuilder->innerJoin(
546
            $this->quoteIdentifier($fromAlias),
547
            $this->quoteIdentifier($join),
548
            $this->quoteIdentifier($alias),
549
            $condition
550
        );
551
552
        return $this;
553
    }
554
555
    /**
556
     * Creates and adds a join to the query.
557
     *
558
     * @param string $fromAlias The alias that points to a from clause.
559
     * @param string $join The table name to join.
560
     * @param string $alias The alias of the join table.
561
     * @param string $condition The condition for the join.
562
     *
563
     * @return QueryBuilder This QueryBuilder instance.
564
     */
565
    public function innerJoin(string $fromAlias, string $join, string $alias, string $condition = null): QueryBuilder
566
    {
567
        $this->concreteQueryBuilder->innerJoin(
568
            $this->quoteIdentifier($fromAlias),
569
            $this->quoteIdentifier($join),
570
            $this->quoteIdentifier($alias),
571
            $condition
572
        );
573
574
        return $this;
575
    }
576
577
    /**
578
     * Creates and adds a left join to the query.
579
     *
580
     * @param string $fromAlias The alias that points to a from clause.
581
     * @param string $join The table name to join.
582
     * @param string $alias The alias of the join table.
583
     * @param string $condition The condition for the join.
584
     *
585
     * @return QueryBuilder This QueryBuilder instance.
586
     */
587
    public function leftJoin(string $fromAlias, string $join, string $alias, string $condition = null): QueryBuilder
588
    {
589
        $this->concreteQueryBuilder->leftJoin(
590
            $this->quoteIdentifier($fromAlias),
591
            $this->quoteIdentifier($join),
592
            $this->quoteIdentifier($alias),
593
            $condition
594
        );
595
596
        return $this;
597
    }
598
599
    /**
600
     * Creates and adds a right join to the query.
601
     *
602
     * @param string $fromAlias The alias that points to a from clause.
603
     * @param string $join The table name to join.
604
     * @param string $alias The alias of the join table.
605
     * @param string $condition The condition for the join.
606
     *
607
     * @return QueryBuilder This QueryBuilder instance.
608
     */
609
    public function rightJoin(string $fromAlias, string $join, string $alias, string $condition = null): QueryBuilder
610
    {
611
        $this->concreteQueryBuilder->rightJoin(
612
            $this->quoteIdentifier($fromAlias),
613
            $this->quoteIdentifier($join),
614
            $this->quoteIdentifier($alias),
615
            $condition
616
        );
617
618
        return $this;
619
    }
620
621
    /**
622
     * Sets a new value for a column in a bulk update query.
623
     *
624
     * @param string $key The column to set.
625
     * @param string $value The value, expression, placeholder, etc.
626
     * @param bool $createNamedParameter Automatically create a named parameter for the value
627
     * @param int $type
628
     *
629
     * @return QueryBuilder This QueryBuilder instance.
630
     */
631
    public function set(string $key, $value, bool $createNamedParameter = true, int $type = \PDO::PARAM_STR): QueryBuilder
632
    {
633
        $this->concreteQueryBuilder->set(
634
            $this->quoteIdentifier($key),
635
            $createNamedParameter ? $this->createNamedParameter($value, $type) : $value
636
        );
637
638
        return $this;
639
    }
640
641
    /**
642
     * Specifies one or more restrictions to the query result.
643
     * Replaces any previously specified restrictions, if any.
644
     *
645
     * @param array<int,mixed> $predicates
646
     * @return QueryBuilder This QueryBuilder instance.
647
     */
648
    public function where(...$predicates): QueryBuilder
649
    {
650
        $this->concreteQueryBuilder->where(...$predicates);
651
652
        return $this;
653
    }
654
655
    /**
656
     * Adds one or more restrictions to the query results, forming a logical
657
     * conjunction with any previously specified restrictions.
658
     *
659
     * @param array<int,string> $where The query restrictions.
660
     *
661
     * @return QueryBuilder This QueryBuilder instance.
662
     *
663
     * @see where()
664
     */
665
    public function andWhere(...$where): QueryBuilder
666
    {
667
        $this->concreteQueryBuilder->andWhere(...$where);
668
669
        return $this;
670
    }
671
672
    /**
673
     * Adds one or more restrictions to the query results, forming a logical
674
     * disjunction with any previously specified restrictions.
675
     *
676
     * @param array<int,string> $where The WHERE statement.
677
     *
678
     * @return QueryBuilder This QueryBuilder instance.
679
     *
680
     * @see where()
681
     */
682
    public function orWhere(...$where): QueryBuilder
683
    {
684
        $this->concreteQueryBuilder->orWhere(...$where);
685
686
        return $this;
687
    }
688
689
    /**
690
     * Specifies a grouping over the results of the query.
691
     * Replaces any previously specified groupings, if any.
692
     *
693
     * @param array<int,string> $groupBy The grouping expression.
694
     *
695
     * @return QueryBuilder This QueryBuilder instance.
696
     */
697
    public function groupBy(...$groupBy): QueryBuilder
698
    {
699
        $this->concreteQueryBuilder->groupBy(...$this->quoteIdentifiers($groupBy));
700
701
        return $this;
702
    }
703
704
    /**
705
     * Adds a grouping expression to the query.
706
     *
707
     * @param array<int,string> $groupBy The grouping expression.
708
     *
709
     * @return QueryBuilder This QueryBuilder instance.
710
     */
711
    public function addGroupBy(...$groupBy): QueryBuilder
712
    {
713
        $this->concreteQueryBuilder->addGroupBy(...$this->quoteIdentifiers($groupBy));
714
715
        return $this;
716
    }
717
718
    /**
719
     * Sets a value for a column in an insert query.
720
     *
721
     * @param string $column The column into which the value should be inserted.
722
     * @param string $value The value that should be inserted into the column.
723
     * @param bool $createNamedParameter Automatically create a named parameter for the value
724
     *
725
     * @return QueryBuilder This QueryBuilder instance.
726
     */
727
    public function setValue(string $column, $value, bool $createNamedParameter = true): QueryBuilder
728
    {
729
        $this->concreteQueryBuilder->setValue(
730
            $this->quoteIdentifier($column),
731
            $createNamedParameter ? $this->createNamedParameter($value) : $value
732
        );
733
734
        return $this;
735
    }
736
737
    /**
738
     * Specifies values for an insert query indexed by column names.
739
     * Replaces any previous values, if any.
740
     *
741
     * @param array $values The values to specify for the insert query indexed by column names.
742
     * @param bool $createNamedParameters Automatically create named parameters for all values
743
     *
744
     * @return QueryBuilder This QueryBuilder instance.
745
     */
746
    public function values(array $values, bool $createNamedParameters = true): QueryBuilder
747
    {
748
        if ($createNamedParameters === true) {
749
            foreach ($values as &$value) {
750
                $value = $this->createNamedParameter($value);
751
            }
752
        }
753
754
        $this->concreteQueryBuilder->values($this->quoteColumnValuePairs($values));
755
756
        return $this;
757
    }
758
759
    /**
760
     * Specifies a restriction over the groups of the query.
761
     * Replaces any previous having restrictions, if any.
762
     *
763
     * @param array<int,string> $having The restriction over the groups.
764
     *
765
     * @return QueryBuilder This QueryBuilder instance.
766
     */
767
    public function having(...$having): QueryBuilder
768
    {
769
        $this->concreteQueryBuilder->having(...$having);
770
        return $this;
771
    }
772
773
    /**
774
     * Adds a restriction over the groups of the query, forming a logical
775
     * conjunction with any existing having restrictions.
776
     *
777
     * @param array<int,string> $having The restriction to append.
778
     *
779
     * @return QueryBuilder This QueryBuilder instance.
780
     */
781
    public function andHaving(...$having): QueryBuilder
782
    {
783
        $this->concreteQueryBuilder->andHaving(...$having);
784
785
        return $this;
786
    }
787
788
    /**
789
     * Adds a restriction over the groups of the query, forming a logical
790
     * disjunction with any existing having restrictions.
791
     *
792
     * @param array<int,string> $having The restriction to add.
793
     *
794
     * @return QueryBuilder This QueryBuilder instance.
795
     */
796
    public function orHaving(...$having): QueryBuilder
797
    {
798
        $this->concreteQueryBuilder->orHaving(...$having);
799
800
        return $this;
801
    }
802
803
    /**
804
     * Specifies an ordering for the query results.
805
     * Replaces any previously specified orderings, if any.
806
     *
807
     * @param string $fieldName The fieldName to order by. Will be quoted according to database platform automatically.
808
     * @param string $order The ordering direction. No automatic quoting/escaping.
809
     *
810
     * @return QueryBuilder This QueryBuilder instance.
811
     */
812
    public function orderBy(string $fieldName, string $order = null): QueryBuilder
813
    {
814
        $this->concreteQueryBuilder->orderBy($this->connection->quoteIdentifier($fieldName), $order);
815
816
        return $this;
817
    }
818
819
    /**
820
     * Adds an ordering to the query results.
821
     *
822
     * @param string $fieldName The fieldName to order by. Will be quoted according to database platform automatically.
823
     * @param string $order The ordering direction.
824
     *
825
     * @return QueryBuilder This QueryBuilder instance.
826
     */
827
    public function addOrderBy(string $fieldName, string $order = null): QueryBuilder
828
    {
829
        $this->concreteQueryBuilder->addOrderBy($this->connection->quoteIdentifier($fieldName), $order);
830
831
        return $this;
832
    }
833
834
    /**
835
     * Gets a query part by its name.
836
     *
837
     * @param string $queryPartName
838
     *
839
     * @return mixed
840
     */
841
    public function getQueryPart(string $queryPartName)
842
    {
843
        return $this->concreteQueryBuilder->getQueryPart($queryPartName);
844
    }
845
846
    /**
847
     * Gets all query parts.
848
     *
849
     * @return array
850
     */
851
    public function getQueryParts(): array
852
    {
853
        return $this->concreteQueryBuilder->getQueryParts();
854
    }
855
856
    /**
857
     * Resets SQL parts.
858
     *
859
     * @param array|null $queryPartNames
860
     *
861
     * @return QueryBuilder This QueryBuilder instance.
862
     */
863
    public function resetQueryParts(array $queryPartNames = null): QueryBuilder
864
    {
865
        $this->concreteQueryBuilder->resetQueryParts($queryPartNames);
866
867
        return $this;
868
    }
869
870
    /**
871
     * Resets a single SQL part.
872
     *
873
     * @param string $queryPartName
874
     *
875
     * @return QueryBuilder This QueryBuilder instance.
876
     */
877
    public function resetQueryPart($queryPartName): QueryBuilder
878
    {
879
        $this->concreteQueryBuilder->resetQueryPart($queryPartName);
880
881
        return $this;
882
    }
883
884
    /**
885
     * Gets a string representation of this QueryBuilder which corresponds to
886
     * the final SQL query being constructed.
887
     *
888
     * @return string The string representation of this QueryBuilder.
889
     */
890
    public function __toString(): string
891
    {
892
        return $this->getSQL();
893
    }
894
895
    /**
896
     * Creates a new named parameter and bind the value $value to it.
897
     *
898
     * This method provides a shortcut for PDOStatement::bindValue
899
     * when using prepared statements.
900
     *
901
     * The parameter $value specifies the value that you want to bind. If
902
     * $placeholder is not provided bindValue() will automatically create a
903
     * placeholder for you. An automatic placeholder will be of the name
904
     * ':dcValue1', ':dcValue2' etc.
905
     *
906
     * @param mixed $value
907
     * @param int $type
908
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
909
     *
910
     * @return string the placeholder name used.
911
     */
912
    public function createNamedParameter($value, int $type = \PDO::PARAM_STR, string $placeHolder = null): string
913
    {
914
        return $this->concreteQueryBuilder->createNamedParameter($value, $type, $placeHolder);
915
    }
916
917
    /**
918
     * Creates a new positional parameter and bind the given value to it.
919
     *
920
     * Attention: If you are using positional parameters with the query builder you have
921
     * to be very careful to bind all parameters in the order they appear in the SQL
922
     * statement , otherwise they get bound in the wrong order which can lead to serious
923
     * bugs in your code.
924
     *
925
     * @param mixed $value
926
     * @param int $type
927
     *
928
     * @return string
929
     */
930
    public function createPositionalParameter($value, int $type = \PDO::PARAM_STR): string
931
    {
932
        return $this->concreteQueryBuilder->createPositionalParameter($value, $type);
933
    }
934
935
    /**
936
     * Quotes like wildcards for given string value.
937
     *
938
     * @param string $value The value to be quoted.
939
     *
940
     * @return string The quoted value.
941
     */
942
    public function escapeLikeWildcards(string $value): string
943
    {
944
        return addcslashes($value, '_%');
945
    }
946
947
    /**
948
     * Quotes a given input parameter.
949
     *
950
     * @param mixed $input The parameter to be quoted.
951
     * @param int|null $type The type of the parameter.
952
     *
953
     * @return mixed Often string, but also int or float or similar depending on $input and platform
954
     */
955
    public function quote($input, int $type = null)
956
    {
957
        return $this->getConnection()->quote($input, $type);
958
    }
959
960
    /**
961
     * Quotes a string so it can be safely used as a table or column name, even if
962
     * it is a reserved name.
963
     *
964
     * Delimiting style depends on the underlying database platform that is being used.
965
     *
966
     * @param string $identifier The name to be quoted.
967
     *
968
     * @return string The quoted name.
969
     */
970
    public function quoteIdentifier(string $identifier): string
971
    {
972
        return $this->getConnection()->quoteIdentifier($identifier);
973
    }
974
975
    /**
976
     * Quotes an array of column names so it can be safely used, even if the name is a reserved name.
977
     *
978
     * Delimiting style depends on the underlying database platform that is being used.
979
     *
980
     * @param array $input
981
     *
982
     * @return array
983
     */
984
    public function quoteIdentifiers(array $input): array
985
    {
986
        return $this->getConnection()->quoteIdentifiers($input);
987
    }
988
989
    /**
990
     * Quotes an array of column names so it can be safely used, even if the name is a reserved name.
991
     * Takes into account the special case of the * placeholder that can only be used in SELECT type
992
     * statements.
993
     *
994
     * Delimiting style depends on the underlying database platform that is being used.
995
     *
996
     * @param array $input
997
     *
998
     * @return array
999
     * @throws \InvalidArgumentException
1000
     */
1001
    public function quoteIdentifiersForSelect(array $input): array
1002
    {
1003
        foreach ($input as &$select) {
1004
            [$fieldName, $alias, $suffix] = array_pad(
1005
                GeneralUtility::trimExplode(
1006
                    ' AS ',
1007
                    str_ireplace(' as ', ' AS ', $select),
1008
                    true,
1009
                    3
1010
                ),
1011
                3,
1012
                null
1013
            );
1014
            if (!empty($suffix)) {
1015
                throw new \InvalidArgumentException(
1016
                    'QueryBuilder::quoteIdentifiersForSelect() could not parse the select ' . $select . '.',
1017
                    1461170686
1018
                );
1019
            }
1020
1021
            // The SQL * operator must not be quoted. As it can only occur either by itself
1022
            // or preceded by a tablename (tablename.*) check if the last character of a select
1023
            // expression is the * and quote only prepended table name. In all other cases the
1024
            // full expression is being quoted.
1025
            if (substr($fieldName, -2) === '.*') {
1026
                $select = $this->quoteIdentifier(substr($fieldName, 0, -2)) . '.*';
1027
            } elseif ($fieldName !== '*') {
1028
                $select = $this->quoteIdentifier($fieldName);
1029
            }
1030
1031
            // Quote the alias for the current fieldName, if given
1032
            if (!empty($alias)) {
1033
                $select .= ' AS ' . $this->quoteIdentifier($alias);
1034
            }
1035
        }
1036
        return $input;
1037
    }
1038
1039
    /**
1040
     * Quotes an associative array of column-value so the column names can be safely used, even
1041
     * if the name is a reserved name.
1042
     *
1043
     * Delimiting style depends on the underlying database platform that is being used.
1044
     *
1045
     * @param array $input
1046
     *
1047
     * @return array
1048
     */
1049
    public function quoteColumnValuePairs(array $input): array
1050
    {
1051
        return $this->getConnection()->quoteColumnValuePairs($input);
1052
    }
1053
1054
    /**
1055
     * Creates a cast of the $fieldName to a text datatype depending on the database management system.
1056
     *
1057
     * @param string $fieldName The fieldname will be quoted and casted according to database platform automatically
1058
     * @return string
1059
     */
1060
    public function castFieldToTextType(string $fieldName): string
1061
    {
1062
        $databasePlatform = $this->connection->getDatabasePlatform();
1063
        // https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert
1064
        if ($databasePlatform instanceof MySqlPlatform) {
1065
            return sprintf('CONVERT(%s, CHAR)', $this->connection->quoteIdentifier($fieldName));
1066
        }
1067
        // https://www.postgresql.org/docs/current/sql-createcast.html
1068
        if ($databasePlatform instanceof PostgreSqlPlatform) {
1069
            return sprintf('%s::text', $this->connection->quoteIdentifier($fieldName));
1070
        }
1071
        // https://www.sqlite.org/lang_expr.html#castexpr
1072
        if ($databasePlatform instanceof SqlitePlatform) {
1073
            return sprintf('CAST(%s as TEXT)', $this->connection->quoteIdentifier($fieldName));
1074
        }
1075
        // https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver15#implicit-conversions
1076
        if ($databasePlatform instanceof SQLServerPlatform) {
1077
            return sprintf('CAST(%s as VARCHAR)', $this->connection->quoteIdentifier($fieldName));
1078
        }
1079
        // https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj33562.html
1080
        if ($databasePlatform instanceof OraclePlatform) {
1081
            return sprintf('CAST(%s as VARCHAR)', $this->connection->quoteIdentifier($fieldName));
1082
        }
1083
1084
        throw new \RuntimeException(
1085
            sprintf(
1086
                '%s is not implemented for the used database platform "%s", yet!',
1087
                __METHOD__,
1088
                get_class($this->connection->getDatabasePlatform())
1089
            ),
1090
            1584637096
1091
        );
1092
    }
1093
1094
    /**
1095
     * Unquote a single identifier (no dot expansion). Used to unquote the table names
1096
     * from the expressionBuilder so that the table can be found in the TCA definition.
1097
     *
1098
     * @param string $identifier The identifier / table name
1099
     * @return string The unquoted table name / identifier
1100
     */
1101
    protected function unquoteSingleIdentifier(string $identifier): string
1102
    {
1103
        $identifier = trim($identifier);
1104
        $platform = $this->getConnection()->getDatabasePlatform();
1105
        if ($platform instanceof SQLServerPlatform) {
1106
            // mssql quotes identifiers with [ and ], not a single character
1107
            $identifier = ltrim($identifier, '[');
1108
            $identifier = rtrim($identifier, ']');
1109
        } else {
1110
            $quoteChar = $platform->getIdentifierQuoteCharacter();
1111
            $identifier = trim($identifier, $quoteChar);
1112
            $identifier = str_replace($quoteChar . $quoteChar, $quoteChar, $identifier);
1113
        }
1114
        return $identifier;
1115
    }
1116
1117
    /**
1118
     * Return all tables/aliases used in FROM or JOIN query parts from the query builder.
1119
     *
1120
     * The table names are automatically unquoted. This is a helper for to build the list
1121
     * of queried tables for the AbstractRestrictionContainer.
1122
     *
1123
     * @return string[]
1124
     */
1125
    protected function getQueriedTables(): array
1126
    {
1127
        $queriedTables = [];
1128
1129
        // Loop through all FROM tables
1130
        foreach ($this->getQueryPart('from') as $from) {
1131
            $tableName = $this->unquoteSingleIdentifier($from['table']);
1132
            $tableAlias = isset($from['alias']) ? $this->unquoteSingleIdentifier($from['alias']) : $tableName;
1133
            $queriedTables[$tableAlias] = $tableName;
1134
        }
1135
1136
        // Loop through all JOIN tables
1137
        foreach ($this->getQueryPart('join') as $fromTable => $joins) {
1138
            foreach ($joins as $join) {
1139
                $tableName = $this->unquoteSingleIdentifier($join['joinTable']);
1140
                $tableAlias = isset($join['joinAlias']) ? $this->unquoteSingleIdentifier($join['joinAlias']) : $tableName;
1141
                $queriedTables[$tableAlias] = $tableName;
1142
            }
1143
        }
1144
1145
        return $queriedTables;
1146
    }
1147
1148
    /**
1149
     * Add the additional query conditions returned by the QueryRestrictionBuilder
1150
     * to the current query and return the original set of conditions so that they
1151
     * can be restored after the query has been built/executed.
1152
     *
1153
     * @return \Doctrine\DBAL\Query\Expression\CompositeExpression|mixed
1154
     */
1155
    protected function addAdditionalWhereConditions()
1156
    {
1157
        $originalWhereConditions = $this->concreteQueryBuilder->getQueryPart('where');
1158
        $expression = $this->restrictionContainer->buildExpression($this->getQueriedTables(), $this->expr());
1159
        // This check would be obsolete, as the composite expression would not add empty expressions anyway
1160
        // But we keep it here to only clone the previous state, in case we really will change it.
1161
        // Once we remove this state preserving functionality, we can remove the count check here
1162
        // and just add the expression to the query builder.
1163
        if ($expression->count() > 0) {
1164
            if ($originalWhereConditions instanceof CompositeExpression) {
1165
                // Save the original query conditions so we can restore
1166
                // them after the query has been built.
1167
                $originalWhereConditions = clone $originalWhereConditions;
1168
            }
1169
            $this->concreteQueryBuilder->andWhere($expression);
1170
        }
1171
1172
        return $originalWhereConditions;
1173
    }
1174
1175
    /**
1176
     * Deep clone of the QueryBuilder
1177
     * @see \Doctrine\DBAL\Query\QueryBuilder::__clone()
1178
     */
1179
    public function __clone()
1180
    {
1181
        $this->concreteQueryBuilder = clone $this->concreteQueryBuilder;
1182
        $this->restrictionContainer = clone $this->restrictionContainer;
1183
    }
1184
}
1185