Completed
Pull Request — master (#3696)
by
unknown
14:04
created

QueryBuilder::distinct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
rs 10
ccs 3
cts 3
cp 1
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Doctrine\DBAL\Query;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Driver\Statement;
7
use Doctrine\DBAL\ParameterType;
8
use Doctrine\DBAL\Query\Expression\CompositeExpression;
9
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
10
use function array_key_exists;
11
use function array_keys;
12
use function array_unshift;
13
use function func_get_args;
14
use function func_num_args;
15
use function implode;
16
use function is_array;
17
use function is_object;
18
use function key;
19
use function strtoupper;
20
use function substr;
21
22
/**
23
 * QueryBuilder class is responsible to dynamically create SQL queries.
24
 *
25
 * Important: Verify that every feature you use will work with your database vendor.
26
 * SQL Query Builder does not attempt to validate the generated SQL at all.
27
 *
28
 * The query builder does no validation whatsoever if certain features even work with the
29
 * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
30
 * even if some vendors such as MySQL support it.
31
 */
32
class QueryBuilder
33
{
34
    /*
35
     * The query types.
36
     */
37
    public const SELECT = 0;
38
    public const DELETE = 1;
39
    public const UPDATE = 2;
40
    public const INSERT = 3;
41
42
    /*
43
     * The builder states.
44
     */
45
    public const STATE_DIRTY = 0;
46
    public const STATE_CLEAN = 1;
47
48
    /**
49
     * The DBAL Connection.
50
     *
51
     * @var Connection
52
     */
53
    private $connection;
54
55
    /*
56
     * The default values of SQL parts collection
57
     */
58
    private const SQL_PARTS_DEFAULTS = [
59
        'select'   => [],
60
        'distinct' => false,
61
        'from'     => [],
62
        'join'     => [],
63
        'set'      => [],
64
        'where'    => null,
65
        'groupBy'  => [],
66
        'having'   => null,
67
        'orderBy'  => [],
68
        'values'   => [],
69
    ];
70
71
    /**
72
     * The array of SQL parts collected.
73
     *
74
     * @var mixed[]
75
     */
76
    private $sqlParts = self::SQL_PARTS_DEFAULTS;
77
78
    /**
79
     * The complete SQL string for this query.
80
     *
81
     * @var string
82
     */
83
    private $sql;
84
85
    /**
86
     * The query parameters.
87
     *
88
     * @var mixed[]
89
     */
90
    private $params = [];
91
92
    /**
93
     * The parameter type map of this query.
94
     *
95
     * @var int[]|string[]
96
     */
97
    private $paramTypes = [];
98
99
    /**
100
     * The type of query this is. Can be select, update or delete.
101
     *
102
     * @var int
103
     */
104
    private $type = self::SELECT;
105
106
    /**
107
     * The state of the query object. Can be dirty or clean.
108
     *
109
     * @var int
110
     */
111
    private $state = self::STATE_CLEAN;
112
113
    /**
114
     * The index of the first result to retrieve.
115
     *
116
     * @var int
117
     */
118
    private $firstResult = null;
119
120
    /**
121
     * The maximum number of results to retrieve.
122
     *
123
     * @var int
124
     */
125
    private $maxResults = null;
126
127
    /**
128
     * The counter of bound parameters used with {@see bindValue).
129
     *
130
     * @var int
131
     */
132
    private $boundCounter = 0;
133
134
    /**
135
     * Initializes a new <tt>QueryBuilder</tt>.
136
     *
137
     * @param Connection $connection The DBAL Connection.
138
     */
139 1709
    public function __construct(Connection $connection)
140
    {
141 1709
        $this->connection = $connection;
142 1709
    }
143
144
    /**
145
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
146
     * This producer method is intended for convenient inline usage. Example:
147
     *
148
     * <code>
149
     *     $qb = $conn->createQueryBuilder()
150
     *         ->select('u')
151
     *         ->from('users', 'u')
152
     *         ->where($qb->expr()->eq('u.id', 1));
153
     * </code>
154
     *
155
     * For more complex expression construction, consider storing the expression
156
     * builder object in a local variable.
157
     *
158
     * @return ExpressionBuilder
159
     */
160 1556
    public function expr()
161
    {
162 1556
        return $this->connection->getExpressionBuilder();
163
    }
164
165
    /**
166
     * Gets the type of the currently built query.
167
     *
168
     * @return int
169
     */
170 999
    public function getType()
171
    {
172 999
        return $this->type;
173
    }
174
175
    /**
176
     * Gets the associated DBAL Connection for this query builder.
177
     *
178
     * @return Connection
179
     */
180 552
    public function getConnection()
181
    {
182 552
        return $this->connection;
183
    }
184
185
    /**
186
     * Gets the state of this query builder instance.
187
     *
188
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
189
     */
190 531
    public function getState()
191
    {
192 531
        return $this->state;
193
    }
194
195
    /**
196
     * Executes this query using the bound parameters and their types.
197
     *
198
     * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
199
     * for insert, update and delete statements.
200
     *
201
     * @return Statement|int
202
     */
203
    public function execute()
204
    {
205
        if ($this->type === self::SELECT) {
206
            return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
207
        }
208
209
        return $this->connection->executeUpdate($this->getSQL(), $this->params, $this->paramTypes);
210
    }
211
212
    /**
213
     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
214
     *
215
     * <code>
216
     *     $qb = $em->createQueryBuilder()
217
     *         ->select('u')
218
     *         ->from('User', 'u')
219
     *     echo $qb->getSQL(); // SELECT u FROM User u
220
     * </code>
221
     *
222
     * @return string The SQL query string.
223
     */
224 1689
    public function getSQL()
225
    {
226 1689
        if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
227 527
            return $this->sql;
228
        }
229
230 1689
        switch ($this->type) {
231 1689
            case self::INSERT:
232 683
                $sql = $this->getSQLForInsert();
233 683
                break;
234 1681
            case self::DELETE:
235 781
                $sql = $this->getSQLForDelete();
236 781
                break;
237
238 1675
            case self::UPDATE:
239 881
                $sql = $this->getSQLForUpdate();
240 881
                break;
241
242 1669
            case self::SELECT:
243
            default:
244 1669
                $sql = $this->getSQLForSelect();
245 1665
                break;
246
        }
247
248 1685
        $this->state = self::STATE_CLEAN;
249 1685
        $this->sql   = $sql;
250
251 1685
        return $sql;
252
    }
253
254
    /**
255
     * Sets a query parameter for the query being constructed.
256
     *
257
     * <code>
258
     *     $qb = $conn->createQueryBuilder()
259
     *         ->select('u')
260
     *         ->from('users', 'u')
261
     *         ->where('u.id = :user_id')
262
     *         ->setParameter(':user_id', 1);
263
     * </code>
264
     *
265
     * @param string|int      $key   The parameter position or name.
266
     * @param mixed           $value The parameter value.
267
     * @param string|int|null $type  One of the {@link \Doctrine\DBAL\ParameterType} constants.
268
     *
269
     * @return $this This QueryBuilder instance.
270
     */
271 437
    public function setParameter($key, $value, $type = null)
272
    {
273 437
        if ($type !== null) {
274 435
            $this->paramTypes[$key] = $type;
275
        }
276
277 437
        $this->params[$key] = $value;
278
279 437
        return $this;
280
    }
281
282
    /**
283
     * Sets a collection of query parameters for the query being constructed.
284
     *
285
     * <code>
286
     *     $qb = $conn->createQueryBuilder()
287
     *         ->select('u')
288
     *         ->from('users', 'u')
289
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
290
     *         ->setParameters(array(
291
     *             ':user_id1' => 1,
292
     *             ':user_id2' => 2
293
     *         ));
294
     * </code>
295
     *
296
     * @param mixed[]        $params The query parameters to set.
297
     * @param int[]|string[] $types  The query parameters types to set.
298
     *
299
     * @return $this This QueryBuilder instance.
300
     */
301
    public function setParameters(array $params, array $types = [])
302
    {
303
        $this->paramTypes = $types;
304
        $this->params     = $params;
305
306
        return $this;
307
    }
308
309
    /**
310
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
311
     *
312
     * @return mixed[] The currently defined query parameters indexed by parameter index or name.
313
     */
314 227
    public function getParameters()
315
    {
316 227
        return $this->params;
317
    }
318
319
    /**
320
     * Gets a (previously set) query parameter of the query being constructed.
321
     *
322
     * @param mixed $key The key (index or name) of the bound parameter.
323
     *
324
     * @return mixed The value of the bound parameter.
325
     */
326 431
    public function getParameter($key)
327
    {
328 431
        return $this->params[$key] ?? null;
329
    }
330
331
    /**
332
     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
333
     *
334
     * @return int[]|string[] The currently defined query parameter types indexed by parameter index or name.
335
     */
336 52
    public function getParameterTypes()
337
    {
338 52
        return $this->paramTypes;
339
    }
340
341
    /**
342
     * Gets a (previously set) query parameter type of the query being constructed.
343
     *
344
     * @param mixed $key The key (index or name) of the bound parameter type.
345
     *
346
     * @return mixed The value of the bound parameter type.
347
     */
348 433
    public function getParameterType($key)
349
    {
350 433
        return $this->paramTypes[$key] ?? null;
351
    }
352
353
    /**
354
     * Sets the position of the first result to retrieve (the "offset").
355
     *
356
     * @param int $firstResult The first result to return.
357
     *
358
     * @return $this This QueryBuilder instance.
359
     */
360 2
    public function setFirstResult($firstResult)
361
    {
362 2
        $this->state       = self::STATE_DIRTY;
363 2
        $this->firstResult = $firstResult;
364
365 2
        return $this;
366
    }
367
368
    /**
369
     * Gets the position of the first result the query object was set to retrieve (the "offset").
370
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
371
     *
372
     * @return int The position of the first result.
373
     */
374 2
    public function getFirstResult()
375
    {
376 2
        return $this->firstResult;
377
    }
378
379
    /**
380
     * Sets the maximum number of results to retrieve (the "limit").
381
     *
382
     * @param int $maxResults The maximum number of results to retrieve.
383
     *
384
     * @return $this This QueryBuilder instance.
385
     */
386 502
    public function setMaxResults($maxResults)
387
    {
388 502
        $this->state      = self::STATE_DIRTY;
389 502
        $this->maxResults = $maxResults;
390
391 502
        return $this;
392
    }
393
394
    /**
395
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
396
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
397
     *
398
     * @return int The maximum number of results.
399
     */
400 502
    public function getMaxResults()
401
    {
402 502
        return $this->maxResults;
403
    }
404
405
    /**
406
     * Either appends to or replaces a single, generic query part.
407
     *
408
     * The available parts are: 'select', 'from', 'set', 'where',
409
     * 'groupBy', 'having' and 'orderBy'.
410
     *
411
     * @param string $sqlPartName
412
     * @param mixed  $sqlPart
413
     * @param bool   $append
414
     *
415
     * @return $this This QueryBuilder instance.
416
     */
417 1693
    public function add($sqlPartName, $sqlPart, $append = false)
418
    {
419 1693
        $isArray    = is_array($sqlPart);
420 1693
        $isMultiple = is_array($this->sqlParts[$sqlPartName]);
421
422 1693
        if ($isMultiple && ! $isArray) {
423 1064
            $sqlPart = [$sqlPart];
424
        }
425
426 1693
        $this->state = self::STATE_DIRTY;
427
428 1693
        if ($append) {
429 1652
            if ($sqlPartName === 'orderBy' || $sqlPartName === 'groupBy' || $sqlPartName === 'select' || $sqlPartName === 'set') {
430 1268
                foreach ($sqlPart as $part) {
431 1268
                    $this->sqlParts[$sqlPartName][] = $part;
432
                }
433 1646
            } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) {
434 1499
                $key                                  = key($sqlPart);
435 1499
                $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key];
436 1646
            } elseif ($isMultiple) {
437 1646
                $this->sqlParts[$sqlPartName][] = $sqlPart;
438
            } else {
439 1416
                $this->sqlParts[$sqlPartName] = $sqlPart;
440
            }
441
442 1652
            return $this;
443
        }
444
445 1693
        $this->sqlParts[$sqlPartName] = $sqlPart;
446
447 1693
        return $this;
448
    }
449
450
    /**
451
     * Specifies an item that is to be returned in the query result.
452
     * Replaces any previously specified selections, if any.
453
     *
454
     * <code>
455
     *     $qb = $conn->createQueryBuilder()
456
     *         ->select('u.id', 'p.id')
457
     *         ->from('users', 'u')
458
     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
459
     * </code>
460
     *
461
     * @param mixed $select The selection expressions.
462
     *
463
     * @return $this This QueryBuilder instance.
464
     */
465 1675
    public function select($select = null)
466
    {
467 1675
        $this->type = self::SELECT;
468
469 1675
        if (empty($select)) {
470 977
            return $this;
471
        }
472
473 1673
        $selects = is_array($select) ? $select : func_get_args();
474
475 1673
        return $this->add('select', $selects);
476
    }
477
478
    /**
479
     * Adds DISTINCT to the query.
480
     *
481
     * <code>
482
     *     $qb = $conn->createQueryBuilder()
483
     *         ->select('u.id')
484
     *         ->distinct()
485
     *         ->from('users', 'u')
486
     * </code>
487
     *
488
     * @return $this This QueryBuilder instance.
489
     */
490 1527
    public function distinct() : self
491
    {
492 1527
        $this->sqlParts['distinct'] = true;
493
494 1527
        return $this;
495
    }
496
497
    /**
498
     * Adds an item that is to be returned in the query result.
499
     *
500
     * <code>
501
     *     $qb = $conn->createQueryBuilder()
502
     *         ->select('u.id')
503
     *         ->addSelect('p.id')
504
     *         ->from('users', 'u')
505
     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
506
     * </code>
507
     *
508
     * @param mixed $select The selection expression.
509
     *
510
     * @return $this This QueryBuilder instance.
511
     */
512 956
    public function addSelect($select = null)
513
    {
514 956
        $this->type = self::SELECT;
515
516 956
        if (empty($select)) {
517 927
            return $this;
518
        }
519
520 954
        $selects = is_array($select) ? $select : func_get_args();
521
522 954
        return $this->add('select', $selects, true);
523
    }
524
525
    /**
526
     * Turns the query being built into a bulk delete query that ranges over
527
     * a certain table.
528
     *
529
     * <code>
530
     *     $qb = $conn->createQueryBuilder()
531
     *         ->delete('users', 'u')
532
     *         ->where('u.id = :user_id')
533
     *         ->setParameter(':user_id', 1);
534
     * </code>
535
     *
536
     * @param string $delete The table whose rows are subject to the deletion.
537
     * @param string $alias  The table alias used in the constructed query.
538
     *
539
     * @return $this This QueryBuilder instance.
540
     */
541 783
    public function delete($delete = null, $alias = null)
542
    {
543 783
        $this->type = self::DELETE;
544
545 783
        if (! $delete) {
546 702
            return $this;
547
        }
548
549 781
        return $this->add('from', [
550 781
            'table' => $delete,
551 781
            'alias' => $alias,
552
        ]);
553
    }
554
555
    /**
556
     * Turns the query being built into a bulk update query that ranges over
557
     * a certain table
558
     *
559
     * <code>
560
     *     $qb = $conn->createQueryBuilder()
561
     *         ->update('counters', 'c')
562
     *         ->set('c.value', 'c.value + 1')
563
     *         ->where('c.id = ?');
564
     * </code>
565
     *
566
     * @param string $update The table whose rows are subject to the update.
567
     * @param string $alias  The table alias used in the constructed query.
568
     *
569
     * @return $this This QueryBuilder instance.
570
     */
571 883
    public function update($update = null, $alias = null)
572
    {
573 883
        $this->type = self::UPDATE;
574
575 883
        if (! $update) {
576 802
            return $this;
577
        }
578
579 881
        return $this->add('from', [
580 881
            'table' => $update,
581 881
            'alias' => $alias,
582
        ]);
583
    }
584
585
    /**
586
     * Turns the query being built into an insert query that inserts into
587
     * a certain table
588
     *
589
     * <code>
590
     *     $qb = $conn->createQueryBuilder()
591
     *         ->insert('users')
592
     *         ->values(
593
     *             array(
594
     *                 'name' => '?',
595
     *                 'password' => '?'
596
     *             )
597
     *         );
598
     * </code>
599
     *
600
     * @param string $insert The table into which the rows should be inserted.
601
     *
602
     * @return $this This QueryBuilder instance.
603
     */
604 685
    public function insert($insert = null)
605
    {
606 685
        $this->type = self::INSERT;
607
608 685
        if (! $insert) {
609 577
            return $this;
610
        }
611
612 683
        return $this->add('from', ['table' => $insert]);
613
    }
614
615
    /**
616
     * Creates and adds a query root corresponding to the table identified by the
617
     * given alias, forming a cartesian product with any existing query roots.
618
     *
619
     * <code>
620
     *     $qb = $conn->createQueryBuilder()
621
     *         ->select('u.id')
622
     *         ->from('users', 'u')
623
     * </code>
624
     *
625
     * @param string      $from  The table.
626
     * @param string|null $alias The alias of the table.
627
     *
628
     * @return $this This QueryBuilder instance.
629
     */
630 1646
    public function from($from, $alias = null)
631
    {
632 1646
        return $this->add('from', [
633 1646
            'table' => $from,
634 1646
            'alias' => $alias,
635 1646
        ], true);
636
    }
637
638
    /**
639
     * Creates and adds a join to the query.
640
     *
641
     * <code>
642
     *     $qb = $conn->createQueryBuilder()
643
     *         ->select('u.name')
644
     *         ->from('users', 'u')
645
     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
646
     * </code>
647
     *
648
     * @param string $fromAlias The alias that points to a from clause.
649
     * @param string $join      The table name to join.
650
     * @param string $alias     The alias of the join table.
651
     * @param string $condition The condition for the join.
652
     *
653
     * @return $this This QueryBuilder instance.
654
     */
655 1458
    public function join($fromAlias, $join, $alias, $condition = null)
656
    {
657 1458
        return $this->innerJoin($fromAlias, $join, $alias, $condition);
658
    }
659
660
    /**
661
     * Creates and adds a join to the query.
662
     *
663
     * <code>
664
     *     $qb = $conn->createQueryBuilder()
665
     *         ->select('u.name')
666
     *         ->from('users', 'u')
667
     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
668
     * </code>
669
     *
670
     * @param string $fromAlias The alias that points to a from clause.
671
     * @param string $join      The table name to join.
672
     * @param string $alias     The alias of the join table.
673
     * @param string $condition The condition for the join.
674
     *
675
     * @return $this This QueryBuilder instance.
676
     */
677 1470
    public function innerJoin($fromAlias, $join, $alias, $condition = null)
678
    {
679 1470
        return $this->add('join', [
680
            $fromAlias => [
681 1470
                'joinType'      => 'inner',
682 1470
                'joinTable'     => $join,
683 1470
                'joinAlias'     => $alias,
684 1470
                'joinCondition' => $condition,
685
            ],
686 1470
        ], true);
687
    }
688
689
    /**
690
     * Creates and adds a left join to the query.
691
     *
692
     * <code>
693
     *     $qb = $conn->createQueryBuilder()
694
     *         ->select('u.name')
695
     *         ->from('users', 'u')
696
     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
697
     * </code>
698
     *
699
     * @param string $fromAlias The alias that points to a from clause.
700
     * @param string $join      The table name to join.
701
     * @param string $alias     The alias of the join table.
702
     * @param string $condition The condition for the join.
703
     *
704
     * @return $this This QueryBuilder instance.
705
     */
706 1477
    public function leftJoin($fromAlias, $join, $alias, $condition = null)
707
    {
708 1477
        return $this->add('join', [
709
            $fromAlias => [
710 1477
                'joinType'      => 'left',
711 1477
                'joinTable'     => $join,
712 1477
                'joinAlias'     => $alias,
713 1477
                'joinCondition' => $condition,
714
            ],
715 1477
        ], true);
716
    }
717
718
    /**
719
     * Creates and adds a right join to the query.
720
     *
721
     * <code>
722
     *     $qb = $conn->createQueryBuilder()
723
     *         ->select('u.name')
724
     *         ->from('users', 'u')
725
     *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
726
     * </code>
727
     *
728
     * @param string $fromAlias The alias that points to a from clause.
729
     * @param string $join      The table name to join.
730
     * @param string $alias     The alias of the join table.
731
     * @param string $condition The condition for the join.
732
     *
733
     * @return $this This QueryBuilder instance.
734
     */
735 2
    public function rightJoin($fromAlias, $join, $alias, $condition = null)
736
    {
737 2
        return $this->add('join', [
738
            $fromAlias => [
739 2
                'joinType'      => 'right',
740 2
                'joinTable'     => $join,
741 2
                'joinAlias'     => $alias,
742 2
                'joinCondition' => $condition,
743
            ],
744 2
        ], true);
745
    }
746
747
    /**
748
     * Sets a new value for a column in a bulk update query.
749
     *
750
     * <code>
751
     *     $qb = $conn->createQueryBuilder()
752
     *         ->update('counters', 'c')
753
     *         ->set('c.value', 'c.value + 1')
754
     *         ->where('c.id = ?');
755
     * </code>
756
     *
757
     * @param string $key   The column to set.
758
     * @param string $value The value, expression, placeholder, etc.
759
     *
760
     * @return $this This QueryBuilder instance.
761
     */
762 881
    public function set($key, $value)
763
    {
764 881
        return $this->add('set', $key . ' = ' . $value, true);
765
    }
766
767
    /**
768
     * Specifies one or more restrictions to the query result.
769
     * Replaces any previously specified restrictions, if any.
770
     *
771
     * <code>
772
     *     $qb = $conn->createQueryBuilder()
773
     *         ->select('c.value')
774
     *         ->from('counters', 'c')
775
     *         ->where('c.id = ?');
776
     *
777
     *     // You can optionally programatically build and/or expressions
778
     *     $qb = $conn->createQueryBuilder();
779
     *
780
     *     $or = $qb->expr()->orx();
781
     *     $or->add($qb->expr()->eq('c.id', 1));
782
     *     $or->add($qb->expr()->eq('c.id', 2));
783
     *
784
     *     $qb->update('counters', 'c')
785
     *         ->set('c.value', 'c.value + 1')
786
     *         ->where($or);
787
     * </code>
788
     *
789
     * @param mixed $predicates The restriction predicates.
790
     *
791
     * @return $this This QueryBuilder instance.
792
     */
793 1538
    public function where($predicates)
794
    {
795 1538
        if (! (func_num_args() === 1 && $predicates instanceof CompositeExpression)) {
796 1436
            $predicates = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
797
        }
798
799 1538
        return $this->add('where', $predicates);
800
    }
801
802
    /**
803
     * Adds one or more restrictions to the query results, forming a logical
804
     * conjunction with any previously specified restrictions.
805
     *
806
     * <code>
807
     *     $qb = $conn->createQueryBuilder()
808
     *         ->select('u')
809
     *         ->from('users', 'u')
810
     *         ->where('u.username LIKE ?')
811
     *         ->andWhere('u.is_active = 1');
812
     * </code>
813
     *
814
     * @see where()
815
     *
816
     * @param mixed $where The query restrictions.
817
     *
818
     * @return $this This QueryBuilder instance.
819
     */
820 1412
    public function andWhere($where)
821
    {
822 1412
        $args  = func_get_args();
823 1412
        $where = $this->getQueryPart('where');
824
825 1412
        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) {
826 1412
            $where->addMultiple($args);
827
        } else {
828 1327
            array_unshift($args, $where);
829 1327
            $where = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
830
        }
831
832 1412
        return $this->add('where', $where, true);
833
    }
834
835
    /**
836
     * Adds one or more restrictions to the query results, forming a logical
837
     * disjunction with any previously specified restrictions.
838
     *
839
     * <code>
840
     *     $qb = $em->createQueryBuilder()
841
     *         ->select('u.name')
842
     *         ->from('users', 'u')
843
     *         ->where('u.id = 1')
844
     *         ->orWhere('u.id = 2');
845
     * </code>
846
     *
847
     * @see where()
848
     *
849
     * @param mixed $where The WHERE statement.
850
     *
851
     * @return $this This QueryBuilder instance.
852
     */
853 1381
    public function orWhere($where)
854
    {
855 1381
        $args  = func_get_args();
856 1381
        $where = $this->getQueryPart('where');
857
858 1381
        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) {
859 1352
            $where->addMultiple($args);
860
        } else {
861 1381
            array_unshift($args, $where);
862 1381
            $where = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
863
        }
864
865 1381
        return $this->add('where', $where, true);
866
    }
867
868
    /**
869
     * Specifies a grouping over the results of the query.
870
     * Replaces any previously specified groupings, if any.
871
     *
872
     * <code>
873
     *     $qb = $conn->createQueryBuilder()
874
     *         ->select('u.name')
875
     *         ->from('users', 'u')
876
     *         ->groupBy('u.id');
877
     * </code>
878
     *
879
     * @param mixed $groupBy The grouping expression.
880
     *
881
     * @return $this This QueryBuilder instance.
882
     */
883 1320
    public function groupBy($groupBy)
884
    {
885 1320
        if (empty($groupBy)) {
886 1277
            return $this;
887
        }
888
889 1318
        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
890
891 1318
        return $this->add('groupBy', $groupBy, false);
892
    }
893
894
    /**
895
     * Adds a grouping expression to the query.
896
     *
897
     * <code>
898
     *     $qb = $conn->createQueryBuilder()
899
     *         ->select('u.name')
900
     *         ->from('users', 'u')
901
     *         ->groupBy('u.lastLogin')
902
     *         ->addGroupBy('u.createdAt');
903
     * </code>
904
     *
905
     * @param mixed $groupBy The grouping expression.
906
     *
907
     * @return $this This QueryBuilder instance.
908
     */
909 1256
    public function addGroupBy($groupBy)
910
    {
911 1256
        if (empty($groupBy)) {
912 2
            return $this;
913
        }
914
915 1254
        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
916
917 1254
        return $this->add('groupBy', $groupBy, true);
918
    }
919
920
    /**
921
     * Sets a value for a column in an insert query.
922
     *
923
     * <code>
924
     *     $qb = $conn->createQueryBuilder()
925
     *         ->insert('users')
926
     *         ->values(
927
     *             array(
928
     *                 'name' => '?'
929
     *             )
930
     *         )
931
     *         ->setValue('password', '?');
932
     * </code>
933
     *
934
     * @param string $column The column into which the value should be inserted.
935
     * @param string $value  The value that should be inserted into the column.
936
     *
937
     * @return $this This QueryBuilder instance.
938
     */
939 629
    public function setValue($column, $value)
940
    {
941 629
        $this->sqlParts['values'][$column] = $value;
942
943 629
        return $this;
944
    }
945
946
    /**
947
     * Specifies values for an insert query indexed by column names.
948
     * Replaces any previous values, if any.
949
     *
950
     * <code>
951
     *     $qb = $conn->createQueryBuilder()
952
     *         ->insert('users')
953
     *         ->values(
954
     *             array(
955
     *                 'name' => '?',
956
     *                 'password' => '?'
957
     *             )
958
     *         );
959
     * </code>
960
     *
961
     * @param mixed[] $values The values to specify for the insert query indexed by column names.
962
     *
963
     * @return $this This QueryBuilder instance.
964
     */
965 681
    public function values(array $values)
966
    {
967 681
        return $this->add('values', $values);
968
    }
969
970
    /**
971
     * Specifies a restriction over the groups of the query.
972
     * Replaces any previous having restrictions, if any.
973
     *
974
     * @param mixed $having The restriction over the groups.
975
     *
976
     * @return $this This QueryBuilder instance.
977
     */
978 1208
    public function having($having)
979
    {
980 1208
        if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) {
981 1208
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
982
        }
983
984 1208
        return $this->add('having', $having);
985
    }
986
987
    /**
988
     * Adds a restriction over the groups of the query, forming a logical
989
     * conjunction with any existing having restrictions.
990
     *
991
     * @param mixed $having The restriction to append.
992
     *
993
     * @return $this This QueryBuilder instance.
994
     */
995 1181
    public function andHaving($having)
996
    {
997 1181
        $args   = func_get_args();
998 1181
        $having = $this->getQueryPart('having');
999
1000 1181
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) {
1001 1152
            $having->addMultiple($args);
1002
        } else {
1003 1179
            array_unshift($args, $having);
1004 1179
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
1005
        }
1006
1007 1181
        return $this->add('having', $having);
1008
    }
1009
1010
    /**
1011
     * Adds a restriction over the groups of the query, forming a logical
1012
     * disjunction with any existing having restrictions.
1013
     *
1014
     * @param mixed $having The restriction to add.
1015
     *
1016
     * @return $this This QueryBuilder instance.
1017
     */
1018 1131
    public function orHaving($having)
1019
    {
1020 1131
        $args   = func_get_args();
1021 1131
        $having = $this->getQueryPart('having');
1022
1023 1131
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) {
1024 1102
            $having->addMultiple($args);
1025
        } else {
1026 1131
            array_unshift($args, $having);
1027 1131
            $having = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
1028
        }
1029
1030 1131
        return $this->add('having', $having);
1031
    }
1032
1033
    /**
1034
     * Specifies an ordering for the query results.
1035
     * Replaces any previously specified orderings, if any.
1036
     *
1037
     * @param string $sort  The ordering expression.
1038
     * @param string $order The ordering direction.
1039
     *
1040
     * @return $this This QueryBuilder instance.
1041
     */
1042 1056
    public function orderBy($sort, $order = null)
1043
    {
1044 1056
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), false);
1045
    }
1046
1047
    /**
1048
     * Adds an ordering to the query results.
1049
     *
1050
     * @param string $sort  The ordering expression.
1051
     * @param string $order The ordering direction.
1052
     *
1053
     * @return $this This QueryBuilder instance.
1054
     */
1055 1029
    public function addOrderBy($sort, $order = null)
1056
    {
1057 1029
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), true);
1058
    }
1059
1060
    /**
1061
     * Gets a query part by its name.
1062
     *
1063
     * @param string $queryPartName
1064
     *
1065
     * @return mixed
1066
     */
1067 1426
    public function getQueryPart($queryPartName)
1068
    {
1069 1426
        return $this->sqlParts[$queryPartName];
1070
    }
1071
1072
    /**
1073
     * Gets all query parts.
1074
     *
1075
     * @return mixed[]
1076
     */
1077 227
    public function getQueryParts()
1078
    {
1079 227
        return $this->sqlParts;
1080
    }
1081
1082
    /**
1083
     * Resets SQL parts.
1084
     *
1085
     * @param string[]|null $queryPartNames
1086
     *
1087
     * @return $this This QueryBuilder instance.
1088
     */
1089 452
    public function resetQueryParts($queryPartNames = null)
1090
    {
1091 452
        if ($queryPartNames === null) {
1092
            $queryPartNames = array_keys($this->sqlParts);
1093
        }
1094
1095 452
        foreach ($queryPartNames as $queryPartName) {
1096 452
            $this->resetQueryPart($queryPartName);
1097
        }
1098
1099 452
        return $this;
1100
    }
1101
1102
    /**
1103
     * Resets a single SQL part.
1104
     *
1105
     * @param string $queryPartName
1106
     *
1107
     * @return $this This QueryBuilder instance.
1108
     */
1109 479
    public function resetQueryPart($queryPartName)
1110
    {
1111 479
        $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName];
1112
1113 479
        $this->state = self::STATE_DIRTY;
1114
1115 479
        return $this;
1116
    }
1117
1118
    /**
1119
     * @return string
1120
     *
1121
     * @throws QueryException
1122
     */
1123 1669
    private function getSQLForSelect()
1124
    {
1125 1669
        $query = 'SELECT ' . ($this->sqlParts['distinct'] ? 'DISTINCT ' : '') .
1126 1669
                  implode(', ', $this->sqlParts['select']);
1127
1128 1669
        $query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '')
1129 1665
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '')
1130 1665
            . ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '')
1131 1665
            . ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '')
1132 1665
            . ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : '');
1133
1134 1665
        if ($this->isLimitQuery()) {
1135
            return $this->connection->getDatabasePlatform()->modifyLimitQuery(
1136
                $query,
1137
                $this->maxResults,
1138
                $this->firstResult
1139
            );
1140
        }
1141
1142 1665
        return $query;
1143
    }
1144
1145
    /**
1146
     * @return string[]
1147
     */
1148 1642
    private function getFromClauses()
1149
    {
1150 1642
        $fromClauses  = [];
1151 1642
        $knownAliases = [];
1152
1153
        // Loop through all FROM clauses
1154 1642
        foreach ($this->sqlParts['from'] as $from) {
1155 1642
            if ($from['alias'] === null) {
1156 212
                $tableSql       = $from['table'];
1157 212
                $tableReference = $from['table'];
1158
            } else {
1159 1632
                $tableSql       = $from['table'] . ' ' . $from['alias'];
1160 1632
                $tableReference = $from['alias'];
1161
            }
1162
1163 1642
            $knownAliases[$tableReference] = true;
1164
1165 1642
            $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1166
        }
1167
1168 1640
        $this->verifyAllAliasesAreKnown($knownAliases);
1169
1170 1638
        return $fromClauses;
1171
    }
1172
1173
    /**
1174
     * @param string[] $knownAliases
1175
     *
1176
     * @throws QueryException
1177
     */
1178 1640
    private function verifyAllAliasesAreKnown(array $knownAliases)
1179
    {
1180 1640
        foreach ($this->sqlParts['join'] as $fromAlias => $joins) {
1181 1497
            if (! isset($knownAliases[$fromAlias])) {
1182 372
                throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases));
1183
            }
1184
        }
1185 1638
    }
1186
1187
    /**
1188
     * @return bool
1189
     */
1190 1665
    private function isLimitQuery()
1191
    {
1192 1665
        return $this->maxResults !== null || $this->firstResult !== null;
1193
    }
1194
1195
    /**
1196
     * Converts this instance into an INSERT string in SQL.
1197
     *
1198
     * @return string
1199
     */
1200 683
    private function getSQLForInsert()
1201
    {
1202 683
        return 'INSERT INTO ' . $this->sqlParts['from']['table'] .
1203 683
        ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' .
1204 683
        ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')';
1205
    }
1206
1207
    /**
1208
     * Converts this instance into an UPDATE string in SQL.
1209
     *
1210
     * @return string
1211
     */
1212 881
    private function getSQLForUpdate()
1213
    {
1214 881
        $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
1215
1216 881
        return 'UPDATE ' . $table
1217 881
            . ' SET ' . implode(', ', $this->sqlParts['set'])
1218 881
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1219
    }
1220
1221
    /**
1222
     * Converts this instance into a DELETE string in SQL.
1223
     *
1224
     * @return string
1225
     */
1226 781
    private function getSQLForDelete()
1227
    {
1228 781
        $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
1229
1230 781
        return 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1231
    }
1232
1233
    /**
1234
     * Gets a string representation of this QueryBuilder which corresponds to
1235
     * the final SQL query being constructed.
1236
     *
1237
     * @return string The string representation of this QueryBuilder.
1238
     */
1239 1675
    public function __toString()
1240
    {
1241 1675
        return $this->getSQL();
1242
    }
1243
1244
    /**
1245
     * Creates a new named parameter and bind the value $value to it.
1246
     *
1247
     * This method provides a shortcut for PDOStatement::bindValue
1248
     * when using prepared statements.
1249
     *
1250
     * The parameter $value specifies the value that you want to bind. If
1251
     * $placeholder is not provided bindValue() will automatically create a
1252
     * placeholder for you. An automatic placeholder will be of the name
1253
     * ':dcValue1', ':dcValue2' etc.
1254
     *
1255
     * For more information see {@link http://php.net/pdostatement-bindparam}
1256
     *
1257
     * Example:
1258
     * <code>
1259
     * $value = 2;
1260
     * $q->eq( 'id', $q->bindValue( $value ) );
1261
     * $stmt = $q->executeQuery(); // executed with 'id = 2'
1262
     * </code>
1263
     *
1264
     * @link http://www.zetacomponents.org
1265
     *
1266
     * @param mixed  $value
1267
     * @param mixed  $type
1268
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1269
     *
1270
     * @return string the placeholder name used.
1271
     */
1272 429
    public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null)
1273
    {
1274 429
        if ($placeHolder === null) {
1275 427
            $this->boundCounter++;
1276 427
            $placeHolder = ':dcValue' . $this->boundCounter;
1277
        }
1278 429
        $this->setParameter(substr($placeHolder, 1), $value, $type);
1279
1280 429
        return $placeHolder;
1281
    }
1282
1283
    /**
1284
     * Creates a new positional parameter and bind the given value to it.
1285
     *
1286
     * Attention: If you are using positional parameters with the query builder you have
1287
     * to be very careful to bind all parameters in the order they appear in the SQL
1288
     * statement , otherwise they get bound in the wrong order which can lead to serious
1289
     * bugs in your code.
1290
     *
1291
     * Example:
1292
     * <code>
1293
     *  $qb = $conn->createQueryBuilder();
1294
     *  $qb->select('u.*')
1295
     *     ->from('users', 'u')
1296
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
1297
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1298
     * </code>
1299
     *
1300
     * @param mixed $value
1301
     * @param int   $type
1302
     *
1303
     * @return string
1304
     */
1305 377
    public function createPositionalParameter($value, $type = ParameterType::STRING)
1306
    {
1307 377
        $this->boundCounter++;
1308 377
        $this->setParameter($this->boundCounter, $value, $type);
1309
1310 377
        return '?';
1311
    }
1312
1313
    /**
1314
     * @param string   $fromAlias
1315
     * @param string[] $knownAliases
1316
     *
1317
     * @return string
1318
     *
1319
     * @throws QueryException
1320
     */
1321 1642
    private function getSQLForJoins($fromAlias, array &$knownAliases)
1322
    {
1323 1642
        $sql = '';
1324
1325 1642
        if (isset($this->sqlParts['join'][$fromAlias])) {
1326 1499
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
1327 1499
                if (array_key_exists($join['joinAlias'], $knownAliases)) {
1328 27
                    throw QueryException::nonUniqueAlias($join['joinAlias'], array_keys($knownAliases));
1329
                }
1330 1497
                $sql                             .= ' ' . strtoupper($join['joinType'])
1331 1497
                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
1332 1497
                    . ' ON ' . ((string) $join['joinCondition']);
1333 1497
                $knownAliases[$join['joinAlias']] = true;
1334
            }
1335
1336 1497
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
1337 1497
                $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases);
1338
            }
1339
        }
1340
1341 1640
        return $sql;
1342
    }
1343
1344
    /**
1345
     * Deep clone of all expression objects in the SQL parts.
1346
     *
1347
     * @return void
1348
     */
1349 227
    public function __clone()
1350
    {
1351 227
        foreach ($this->sqlParts as $part => $elements) {
1352 227
            if (is_array($this->sqlParts[$part])) {
1353 227
                foreach ($this->sqlParts[$part] as $idx => $element) {
1354 227
                    if (! is_object($element)) {
1355 227
                        continue;
1356
                    }
1357
1358 2
                    $this->sqlParts[$part][$idx] = clone $element;
1359
                }
1360 227
            } elseif (is_object($elements)) {
1361 227
                $this->sqlParts[$part] = clone $elements;
1362
            }
1363
        }
1364
1365 227
        foreach ($this->params as $name => $param) {
1366 227
            if (! is_object($param)) {
1367
                continue;
1368
            }
1369
1370 227
            $this->params[$name] = clone $param;
1371
        }
1372 227
    }
1373
}
1374