Passed
Push — master ( 373b25...9bb6b3 )
by
unknown
18:48
created

QueryBuilder   F

Complexity

Total Complexity 106

Size/Duplication

Total Lines 1170
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 106
eloc 228
c 0
b 0
f 0
dl 0
loc 1170
rs 2

67 Methods

Rating   Name   Duplication   Size   Complexity  
A values() 0 11 3
A set() 0 8 2
A having() 0 4 1
A leftJoin() 0 16 1
A selectLiteral() 0 5 1
A addOrderBy() 0 5 1
A getConcreteQueryBuilder() 0 3 1
A groupBy() 0 5 1
A select() 0 5 1
A getParameter() 0 3 1
A getType() 0 3 1
A quoteIdentifiersForSelect() 0 36 6
A addAdditionalWhereConditions() 0 18 3
A getParameters() 0 3 1
A castFieldToTextType() 0 31 6
A rightJoin() 0 25 5
A __construct() 0 10 4
A addSelectLiteral() 0 5 1
A quoteIdentifier() 0 3 1
A resetRestrictions() 0 3 1
A getQueryPart() 0 3 1
A addGroupBy() 0 5 1
A setValue() 0 8 2
A getState() 0 3 1
A insert() 0 5 1
A __clone() 0 4 1
A join() 0 10 1
A getSQL() 0 16 2
A orWhere() 0 5 1
A getQueryParts() 0 3 1
A setMaxResults() 0 5 1
A quoteIdentifiers() 0 3 1
A quote() 0 3 1
A count() 0 8 2
A escapeLikeWildcards() 0 3 1
A where() 0 5 1
A addSelect() 0 5 1
A delete() 0 8 2
A getParameterTypes() 0 3 1
A getMaxResults() 0 3 1
A from() 0 8 2
A __toString() 0 3 1
A limitRestrictionsToTables() 0 3 1
A getConnection() 0 3 1
A add() 0 5 1
A setParameters() 0 5 1
A getParameterType() 0 3 1
A setFirstResult() 0 5 1
A getFirstResult() 0 3 1
A quoteColumnValuePairs() 0 3 1
A update() 0 8 2
A orderBy() 0 5 1
A andHaving() 0 5 1
A innerJoin() 0 10 1
A orHaving() 0 5 1
A andWhere() 0 5 1
A createPositionalParameter() 0 3 1
A setRestrictions() 0 10 3
A unquoteSingleIdentifier() 0 14 2
A resetQueryParts() 0 5 1
A resetQueryPart() 0 5 1
A setParameter() 0 5 1
A execute() 0 16 2
A createNamedParameter() 0 3 1
B getQueriedTables() 0 25 8
A expr() 0 3 1
A getRestrictions() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like QueryBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryBuilder, and based on these observations, apply Extract Interface, too.

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

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

1041
                    /** @scrutinizer ignore-type */ str_ireplace(' as ', ' AS ', $select),
Loading history...
1042
                    true,
1043
                    3
1044
                ),
1045
                3,
1046
                null
1047
            );
1048
            if (!empty($suffix)) {
1049
                throw new \InvalidArgumentException(
1050
                    'QueryBuilder::quoteIdentifiersForSelect() could not parse the select ' . $select . '.',
1051
                    1461170686
1052
                );
1053
            }
1054
1055
            // The SQL * operator must not be quoted. As it can only occur either by itself
1056
            // or preceded by a tablename (tablename.*) check if the last character of a select
1057
            // expression is the * and quote only prepended table name. In all other cases the
1058
            // full expression is being quoted.
1059
            if (substr($fieldName, -2) === '.*') {
1060
                $select = $this->quoteIdentifier(substr($fieldName, 0, -2)) . '.*';
1061
            } elseif ($fieldName !== '*') {
1062
                $select = $this->quoteIdentifier($fieldName);
1063
            }
1064
1065
            // Quote the alias for the current fieldName, if given
1066
            if (!empty($alias)) {
1067
                $select .= ' AS ' . $this->quoteIdentifier($alias);
1068
            }
1069
        }
1070
        return $input;
1071
    }
1072
1073
    /**
1074
     * Quotes an associative array of column-value so the column names can be safely used, even
1075
     * if the name is a reserved name.
1076
     *
1077
     * Delimiting style depends on the underlying database platform that is being used.
1078
     *
1079
     * @param array $input
1080
     *
1081
     * @return array
1082
     */
1083
    public function quoteColumnValuePairs(array $input): array
1084
    {
1085
        return $this->getConnection()->quoteColumnValuePairs($input);
1086
    }
1087
1088
    /**
1089
     * Creates a cast of the $fieldName to a text datatype depending on the database management system.
1090
     *
1091
     * @param string $fieldName The fieldname will be quoted and casted according to database platform automatically
1092
     * @return string
1093
     */
1094
    public function castFieldToTextType(string $fieldName): string
1095
    {
1096
        $databasePlatform = $this->connection->getDatabasePlatform();
1097
        // https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert
1098
        if ($databasePlatform instanceof MySqlPlatform) {
1099
            return sprintf('CONVERT(%s, CHAR)', $this->connection->quoteIdentifier($fieldName));
1100
        }
1101
        // https://www.postgresql.org/docs/current/sql-createcast.html
1102
        if ($databasePlatform instanceof PostgreSqlPlatform) {
1103
            return sprintf('%s::text', $this->connection->quoteIdentifier($fieldName));
1104
        }
1105
        // https://www.sqlite.org/lang_expr.html#castexpr
1106
        if ($databasePlatform instanceof SqlitePlatform) {
1107
            return sprintf('CAST(%s as TEXT)', $this->connection->quoteIdentifier($fieldName));
1108
        }
1109
        // https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver15#implicit-conversions
1110
        if ($databasePlatform instanceof SQLServerPlatform) {
1111
            return sprintf('CAST(%s as VARCHAR)', $this->connection->quoteIdentifier($fieldName));
1112
        }
1113
        // https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj33562.html
1114
        if ($databasePlatform instanceof OraclePlatform) {
1115
            return sprintf('CAST(%s as VARCHAR)', $this->connection->quoteIdentifier($fieldName));
1116
        }
1117
1118
        throw new \RuntimeException(
1119
            sprintf(
1120
                '%s is not implemented for the used database platform "%s", yet!',
1121
                __METHOD__,
1122
                get_class($this->connection->getDatabasePlatform())
1123
            ),
1124
            1584637096
1125
        );
1126
    }
1127
1128
    /**
1129
     * Unquote a single identifier (no dot expansion). Used to unquote the table names
1130
     * from the expressionBuilder so that the table can be found in the TCA definition.
1131
     *
1132
     * @param string $identifier The identifier / table name
1133
     * @return string The unquoted table name / identifier
1134
     */
1135
    protected function unquoteSingleIdentifier(string $identifier): string
1136
    {
1137
        $identifier = trim($identifier);
1138
        $platform = $this->getConnection()->getDatabasePlatform();
1139
        if ($platform instanceof SQLServerPlatform) {
1140
            // mssql quotes identifiers with [ and ], not a single character
1141
            $identifier = ltrim($identifier, '[');
1142
            $identifier = rtrim($identifier, ']');
1143
        } else {
1144
            $quoteChar = $platform->getIdentifierQuoteCharacter();
1145
            $identifier = trim($identifier, $quoteChar);
1146
            $identifier = str_replace($quoteChar . $quoteChar, $quoteChar, $identifier);
1147
        }
1148
        return $identifier;
1149
    }
1150
1151
    /**
1152
     * Return all tables/aliases used in FROM or JOIN query parts from the query builder.
1153
     *
1154
     * The table names are automatically unquoted. This is a helper for to build the list
1155
     * of queried tables for the AbstractRestrictionContainer.
1156
     *
1157
     * @return string[]
1158
     */
1159
    protected function getQueriedTables(): array
1160
    {
1161
        $queriedTables = [];
1162
1163
        // Loop through all FROM tables
1164
        foreach ($this->getQueryPart('from') as $from) {
1165
            $tableName = $this->unquoteSingleIdentifier($from['table']);
1166
            $tableAlias = isset($from['alias']) ? $this->unquoteSingleIdentifier($from['alias']) : $tableName;
1167
            if (!in_array($tableAlias, $this->restrictionsAppliedInJoinCondition, true)) {
1168
                $queriedTables[$tableAlias] = $tableName;
1169
            }
1170
        }
1171
1172
        // Loop through all JOIN tables
1173
        foreach ($this->getQueryPart('join') as $fromTable => $joins) {
1174
            foreach ($joins as $join) {
1175
                $tableName = $this->unquoteSingleIdentifier($join['joinTable']);
1176
                $tableAlias = isset($join['joinAlias']) ? $this->unquoteSingleIdentifier($join['joinAlias']) : $tableName;
1177
                if (!in_array($tableAlias, $this->restrictionsAppliedInJoinCondition, true)) {
1178
                    $queriedTables[$tableAlias] = $tableName;
1179
                }
1180
            }
1181
        }
1182
1183
        return $queriedTables;
1184
    }
1185
1186
    /**
1187
     * Add the additional query conditions returned by the QueryRestrictionBuilder
1188
     * to the current query and return the original set of conditions so that they
1189
     * can be restored after the query has been built/executed.
1190
     *
1191
     * @return \Doctrine\DBAL\Query\Expression\CompositeExpression|mixed
1192
     */
1193
    protected function addAdditionalWhereConditions()
1194
    {
1195
        $originalWhereConditions = $this->concreteQueryBuilder->getQueryPart('where');
1196
        $expression = $this->restrictionContainer->buildExpression($this->getQueriedTables(), $this->expr());
1197
        // This check would be obsolete, as the composite expression would not add empty expressions anyway
1198
        // But we keep it here to only clone the previous state, in case we really will change it.
1199
        // Once we remove this state preserving functionality, we can remove the count check here
1200
        // and just add the expression to the query builder.
1201
        if ($expression->count() > 0) {
1202
            if ($originalWhereConditions instanceof CompositeExpression) {
1203
                // Save the original query conditions so we can restore
1204
                // them after the query has been built.
1205
                $originalWhereConditions = clone $originalWhereConditions;
1206
            }
1207
            $this->concreteQueryBuilder->andWhere($expression);
1208
        }
1209
1210
        return $originalWhereConditions;
1211
    }
1212
1213
    /**
1214
     * Deep clone of the QueryBuilder
1215
     * @see \Doctrine\DBAL\Query\QueryBuilder::__clone()
1216
     */
1217
    public function __clone()
1218
    {
1219
        $this->concreteQueryBuilder = clone $this->concreteQueryBuilder;
1220
        $this->restrictionContainer = clone $this->restrictionContainer;
1221
    }
1222
}
1223