QueryBuilder.php 37.9 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\DBAL\Query;

5 6
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Statement;
7
use Doctrine\DBAL\ParameterType;
Benjamin Morel's avatar
Benjamin Morel committed
8
use Doctrine\DBAL\Query\Expression\CompositeExpression;
9
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
10 11 12 13 14 15 16 17 18 19 20
use function array_key_exists;
use function array_keys;
use function array_unshift;
use function func_get_args;
use function func_num_args;
use function implode;
use function is_array;
use function is_object;
use function key;
use function strtoupper;
use function substr;
21 22 23

/**
 * QueryBuilder class is responsible to dynamically create SQL queries.
24
 *
25 26
 * Important: Verify that every feature you use will work with your database vendor.
 * SQL Query Builder does not attempt to validate the generated SQL at all.
27
 *
28 29 30
 * The query builder does no validation whatsoever if certain features even work with the
 * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
 * even if some vendors such as MySQL support it.
31 32 33
 */
class QueryBuilder
{
Benjamin Morel's avatar
Benjamin Morel committed
34 35 36
    /*
     * The query types.
     */
37 38 39 40
    public const SELECT = 0;
    public const DELETE = 1;
    public const UPDATE = 2;
    public const INSERT = 3;
41

Benjamin Morel's avatar
Benjamin Morel committed
42 43 44
    /*
     * The builder states.
     */
45 46
    public const STATE_DIRTY = 0;
    public const STATE_CLEAN = 1;
47 48

    /**
Benjamin Morel's avatar
Benjamin Morel committed
49 50
     * The DBAL Connection.
     *
51
     * @var Connection
52
     */
Benjamin Morel's avatar
Benjamin Morel committed
53
    private $connection;
54

55 56 57 58 59
    /**
     * The array of SQL parts collected.
     *
     * @var mixed[]
     */
60 61 62 63 64
    private $sqlParts = [
        'select'  => [],
        'from'    => [],
        'join'    => [],
        'set'     => [],
65
        'where'   => null,
66
        'groupBy' => [],
67
        'having'  => null,
68 69 70
        'orderBy' => [],
        'values'  => [],
    ];
71 72

    /**
Benjamin Morel's avatar
Benjamin Morel committed
73 74 75
     * The complete SQL string for this query.
     *
     * @var string
76 77 78 79
     */
    private $sql;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
80 81
     * The query parameters.
     *
82
     * @var mixed[]
83
     */
84
    private $params = [];
85 86

    /**
Benjamin Morel's avatar
Benjamin Morel committed
87 88
     * The parameter type map of this query.
     *
89
     * @var int[]|string[]
90
     */
91
    private $paramTypes = [];
92 93

    /**
Benjamin Morel's avatar
Benjamin Morel committed
94 95
     * The type of query this is. Can be select, update or delete.
     *
96
     * @var int
97 98 99 100
     */
    private $type = self::SELECT;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
101 102
     * The state of the query object. Can be dirty or clean.
     *
103
     * @var int
104 105 106 107
     */
    private $state = self::STATE_CLEAN;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
108 109
     * The index of the first result to retrieve.
     *
110
     * @var int
111 112 113 114
     */
    private $firstResult = null;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
115 116
     * The maximum number of results to retrieve.
     *
117
     * @var int
118 119
     */
    private $maxResults = null;
120

121
    /**
Benjamin Morel's avatar
Benjamin Morel committed
122
     * The counter of bound parameters used with {@see bindValue).
123
     *
124
     * @var int
125 126
     */
    private $boundCounter = 0;
127 128 129 130

    /**
     * Initializes a new <tt>QueryBuilder</tt>.
     *
131
     * @param Connection $connection The DBAL Connection.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
     */
    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
     * This producer method is intended for convenient inline usage. Example:
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u')
     *         ->from('users', 'u')
     *         ->where($qb->expr()->eq('u.id', 1));
     * </code>
     *
     * For more complex expression construction, consider storing the expression
     * builder object in a local variable.
     *
152
     * @return ExpressionBuilder
153 154 155 156 157 158 159
     */
    public function expr()
    {
        return $this->connection->getExpressionBuilder();
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
160
     * Gets the type of the currently built query.
161
     *
162
     * @return int
163 164 165 166 167 168 169
     */
    public function getType()
    {
        return $this->type;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
170
     * Gets the associated DBAL Connection for this query builder.
171
     *
172
     * @return Connection
173 174 175 176 177 178 179
     */
    public function getConnection()
    {
        return $this->connection;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
180
     * Gets the state of this query builder instance.
181
     *
182
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
183 184 185 186 187
     */
    public function getState()
    {
        return $this->state;
    }
188

189
    /**
Benjamin Morel's avatar
Benjamin Morel committed
190
     * Executes this query using the bound parameters and their types.
191
     *
192 193
     * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
     * for insert, update and delete statements.
194
     *
195
     * @return Statement|int
196 197 198
     */
    public function execute()
    {
199
        if ($this->type === self::SELECT) {
200 201
            return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
        }
Gabriel Caruso's avatar
Gabriel Caruso committed
202 203

        return $this->connection->executeUpdate($this->getSQL(), $this->params, $this->paramTypes);
204
    }
205 206

    /**
Benjamin Morel's avatar
Benjamin Morel committed
207
     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
208 209 210 211 212
     *
     * <code>
     *     $qb = $em->createQueryBuilder()
     *         ->select('u')
     *         ->from('User', 'u')
213
     *     echo $qb->getSQL(); // SELECT u FROM User u
214 215
     * </code>
     *
Benjamin Morel's avatar
Benjamin Morel committed
216
     * @return string The SQL query string.
217 218 219 220 221 222 223 224
     */
    public function getSQL()
    {
        if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
            return $this->sql;
        }

        switch ($this->type) {
Steve Müller's avatar
Steve Müller committed
225 226 227
            case self::INSERT:
                $sql = $this->getSQLForInsert();
                break;
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
            case self::DELETE:
                $sql = $this->getSQLForDelete();
                break;

            case self::UPDATE:
                $sql = $this->getSQLForUpdate();
                break;

            case self::SELECT:
            default:
                $sql = $this->getSQLForSelect();
                break;
        }

        $this->state = self::STATE_CLEAN;
243
        $this->sql   = $sql;
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258

        return $sql;
    }

    /**
     * Sets a query parameter for the query being constructed.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u')
     *         ->from('users', 'u')
     *         ->where('u.id = :user_id')
     *         ->setParameter(':user_id', 1);
     * </code>
     *
259 260 261
     * @param string|int      $key   The parameter position or name.
     * @param mixed           $value The parameter value.
     * @param string|int|null $type  One of the {@link \Doctrine\DBAL\ParameterType} constants.
Benjamin Morel's avatar
Benjamin Morel committed
262
     *
263
     * @return $this This QueryBuilder instance.
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
     */
    public function setParameter($key, $value, $type = null)
    {
        if ($type !== null) {
            $this->paramTypes[$key] = $type;
        }

        $this->params[$key] = $value;

        return $this;
    }

    /**
     * Sets a collection of query parameters for the query being constructed.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u')
     *         ->from('users', 'u')
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
     *         ->setParameters(array(
     *             ':user_id1' => 1,
     *             ':user_id2' => 2
     *         ));
     * </code>
     *
290 291
     * @param mixed[]        $params The query parameters to set.
     * @param int[]|string[] $types  The query parameters types to set.
Benjamin Morel's avatar
Benjamin Morel committed
292
     *
293
     * @return $this This QueryBuilder instance.
294
     */
295
    public function setParameters(array $params, array $types = [])
296 297
    {
        $this->paramTypes = $types;
298
        $this->params     = $params;
299 300 301 302 303

        return $this;
    }

    /**
304
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
305
     *
306
     * @return mixed[] The currently defined query parameters indexed by parameter index or name.
307 308 309 310 311 312 313 314 315 316
     */
    public function getParameters()
    {
        return $this->params;
    }

    /**
     * Gets a (previously set) query parameter of the query being constructed.
     *
     * @param mixed $key The key (index or name) of the bound parameter.
Benjamin Morel's avatar
Benjamin Morel committed
317
     *
318 319 320 321
     * @return mixed The value of the bound parameter.
     */
    public function getParameter($key)
    {
322
        return $this->params[$key] ?? null;
323 324
    }

325 326 327
    /**
     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
     *
328
     * @return int[]|string[] The currently defined query parameter types indexed by parameter index or name.
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
     */
    public function getParameterTypes()
    {
        return $this->paramTypes;
    }

    /**
     * Gets a (previously set) query parameter type of the query being constructed.
     *
     * @param mixed $key The key (index or name) of the bound parameter type.
     *
     * @return mixed The value of the bound parameter type.
     */
    public function getParameterType($key)
    {
344
        return $this->paramTypes[$key] ?? null;
345 346
    }

347 348 349
    /**
     * Sets the position of the first result to retrieve (the "offset").
     *
350
     * @param int $firstResult The first result to return.
Benjamin Morel's avatar
Benjamin Morel committed
351
     *
352
     * @return $this This QueryBuilder instance.
353 354 355
     */
    public function setFirstResult($firstResult)
    {
356
        $this->state       = self::STATE_DIRTY;
357
        $this->firstResult = $firstResult;
Benjamin Morel's avatar
Benjamin Morel committed
358

359 360 361 362 363 364 365
        return $this;
    }

    /**
     * Gets the position of the first result the query object was set to retrieve (the "offset").
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
     *
366
     * @return int The position of the first result.
367 368 369 370 371 372 373 374 375
     */
    public function getFirstResult()
    {
        return $this->firstResult;
    }

    /**
     * Sets the maximum number of results to retrieve (the "limit").
     *
376
     * @param int $maxResults The maximum number of results to retrieve.
Benjamin Morel's avatar
Benjamin Morel committed
377
     *
378
     * @return $this This QueryBuilder instance.
379 380 381
     */
    public function setMaxResults($maxResults)
    {
382
        $this->state      = self::STATE_DIRTY;
383
        $this->maxResults = $maxResults;
Benjamin Morel's avatar
Benjamin Morel committed
384

385 386 387 388 389 390 391
        return $this;
    }

    /**
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
     *
392
     * @return int The maximum number of results.
393 394 395 396 397 398 399 400 401 402 403 404
     */
    public function getMaxResults()
    {
        return $this->maxResults;
    }

    /**
     * Either appends to or replaces a single, generic query part.
     *
     * The available parts are: 'select', 'from', 'set', 'where',
     * 'groupBy', 'having' and 'orderBy'.
     *
405
     * @param string $sqlPartName
Sergei Morozov's avatar
Sergei Morozov committed
406
     * @param mixed  $sqlPart
407
     * @param bool   $append
Benjamin Morel's avatar
Benjamin Morel committed
408
     *
409
     * @return $this This QueryBuilder instance.
410 411 412
     */
    public function add($sqlPartName, $sqlPart, $append = false)
    {
413
        $isArray    = is_array($sqlPart);
414 415
        $isMultiple = is_array($this->sqlParts[$sqlPartName]);

416
        if ($isMultiple && ! $isArray) {
417
            $sqlPart = [$sqlPart];
418 419 420 421 422
        }

        $this->state = self::STATE_DIRTY;

        if ($append) {
423
            if ($sqlPartName === 'orderBy' || $sqlPartName === 'groupBy' || $sqlPartName === 'select' || $sqlPartName === 'set') {
424
                foreach ($sqlPart as $part) {
425 426
                    $this->sqlParts[$sqlPartName][] = $part;
                }
Steve Müller's avatar
Steve Müller committed
427
            } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) {
428
                $key                                  = key($sqlPart);
429
                $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key];
Steve Müller's avatar
Steve Müller committed
430
            } elseif ($isMultiple) {
431 432 433 434 435 436 437 438 439
                $this->sqlParts[$sqlPartName][] = $sqlPart;
            } else {
                $this->sqlParts[$sqlPartName] = $sqlPart;
            }

            return $this;
        }

        $this->sqlParts[$sqlPartName] = $sqlPart;
440

441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
        return $this;
    }

    /**
     * Specifies an item that is to be returned in the query result.
     * Replaces any previously specified selections, if any.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u.id', 'p.id')
     *         ->from('users', 'u')
     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
     * </code>
     *
     * @param mixed $select The selection expressions.
Benjamin Morel's avatar
Benjamin Morel committed
456
     *
457
     * @return $this This QueryBuilder instance.
458 459 460 461 462 463 464 465 466 467 468
     */
    public function select($select = null)
    {
        $this->type = self::SELECT;

        if (empty($select)) {
            return $this;
        }

        $selects = is_array($select) ? $select : func_get_args();

469
        return $this->add('select', $selects);
470 471 472 473 474 475 476 477 478 479 480 481 482 483
    }

    /**
     * Adds an item that is to be returned in the query result.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u.id')
     *         ->addSelect('p.id')
     *         ->from('users', 'u')
     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
     * </code>
     *
     * @param mixed $select The selection expression.
Benjamin Morel's avatar
Benjamin Morel committed
484
     *
485
     * @return $this This QueryBuilder instance.
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
     */
    public function addSelect($select = null)
    {
        $this->type = self::SELECT;

        if (empty($select)) {
            return $this;
        }

        $selects = is_array($select) ? $select : func_get_args();

        return $this->add('select', $selects, true);
    }

    /**
     * Turns the query being built into a bulk delete query that ranges over
502
     * a certain table.
503 504 505 506
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->delete('users', 'u')
507
     *         ->where('u.id = :user_id')
508 509 510
     *         ->setParameter(':user_id', 1);
     * </code>
     *
511
     * @param string $delete The table whose rows are subject to the deletion.
Benjamin Morel's avatar
Benjamin Morel committed
512 513
     * @param string $alias  The table alias used in the constructed query.
     *
514
     * @return $this This QueryBuilder instance.
515 516 517 518 519
     */
    public function delete($delete = null, $alias = null)
    {
        $this->type = self::DELETE;

520
        if (! $delete) {
521 522 523
            return $this;
        }

524
        return $this->add('from', [
525
            'table' => $delete,
526
            'alias' => $alias,
527
        ]);
528 529 530 531
    }

    /**
     * Turns the query being built into a bulk update query that ranges over
532
     * a certain table
533 534 535
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
536 537 538
     *         ->update('counters', 'c')
     *         ->set('c.value', 'c.value + 1')
     *         ->where('c.id = ?');
539 540
     * </code>
     *
541
     * @param string $update The table whose rows are subject to the update.
Benjamin Morel's avatar
Benjamin Morel committed
542 543
     * @param string $alias  The table alias used in the constructed query.
     *
544
     * @return $this This QueryBuilder instance.
545 546 547 548 549
     */
    public function update($update = null, $alias = null)
    {
        $this->type = self::UPDATE;

550
        if (! $update) {
551 552 553
            return $this;
        }

554
        return $this->add('from', [
555
            'table' => $update,
556
            'alias' => $alias,
557
        ]);
558 559
    }

Steve Müller's avatar
Steve Müller committed
560 561 562 563 564 565 566 567 568
    /**
     * Turns the query being built into an insert query that inserts into
     * a certain table
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->insert('users')
     *         ->values(
     *             array(
Steve Müller's avatar
Steve Müller committed
569 570
     *                 'name' => '?',
     *                 'password' => '?'
Steve Müller's avatar
Steve Müller committed
571 572 573 574 575 576
     *             )
     *         );
     * </code>
     *
     * @param string $insert The table into which the rows should be inserted.
     *
577
     * @return $this This QueryBuilder instance.
Steve Müller's avatar
Steve Müller committed
578 579 580 581 582
     */
    public function insert($insert = null)
    {
        $this->type = self::INSERT;

583
        if (! $insert) {
Steve Müller's avatar
Steve Müller committed
584 585 586
            return $this;
        }

587
        return $this->add('from', ['table' => $insert]);
Steve Müller's avatar
Steve Müller committed
588 589
    }

590
    /**
Benjamin Morel's avatar
Benjamin Morel committed
591
     * Creates and adds a query root corresponding to the table identified by the
592 593 594 595 596 597 598 599
     * given alias, forming a cartesian product with any existing query roots.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u.id')
     *         ->from('users', 'u')
     * </code>
     *
600 601
     * @param string      $from  The table.
     * @param string|null $alias The alias of the table.
Benjamin Morel's avatar
Benjamin Morel committed
602
     *
603
     * @return $this This QueryBuilder instance.
604
     */
605
    public function from($from, $alias = null)
606
    {
607
        return $this->add('from', [
608
            'table' => $from,
609
            'alias' => $alias,
610
        ], true);
611 612 613
    }

    /**
614
     * Creates and adds a join to the query.
615 616 617 618 619 620 621 622
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u.name')
     *         ->from('users', 'u')
     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
     * </code>
     *
Benjamin Morel's avatar
Benjamin Morel committed
623 624 625 626 627
     * @param string $fromAlias The alias that points to a from clause.
     * @param string $join      The table name to join.
     * @param string $alias     The alias of the join table.
     * @param string $condition The condition for the join.
     *
628
     * @return $this This QueryBuilder instance.
629 630 631 632 633 634 635
     */
    public function join($fromAlias, $join, $alias, $condition = null)
    {
        return $this->innerJoin($fromAlias, $join, $alias, $condition);
    }

    /**
636
     * Creates and adds a join to the query.
637 638 639 640 641 642 643 644
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u.name')
     *         ->from('users', 'u')
     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
     * </code>
     *
Benjamin Morel's avatar
Benjamin Morel committed
645 646 647 648 649
     * @param string $fromAlias The alias that points to a from clause.
     * @param string $join      The table name to join.
     * @param string $alias     The alias of the join table.
     * @param string $condition The condition for the join.
     *
650
     * @return $this This QueryBuilder instance.
651 652 653
     */
    public function innerJoin($fromAlias, $join, $alias, $condition = null)
    {
654 655
        return $this->add('join', [
            $fromAlias => [
656 657 658
                'joinType'      => 'inner',
                'joinTable'     => $join,
                'joinAlias'     => $alias,
659 660
                'joinCondition' => $condition,
            ],
661
        ], true);
662 663 664
    }

    /**
665
     * Creates and adds a left join to the query.
666 667 668 669 670 671 672 673
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u.name')
     *         ->from('users', 'u')
     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
     * </code>
     *
Benjamin Morel's avatar
Benjamin Morel committed
674 675 676 677 678
     * @param string $fromAlias The alias that points to a from clause.
     * @param string $join      The table name to join.
     * @param string $alias     The alias of the join table.
     * @param string $condition The condition for the join.
     *
679
     * @return $this This QueryBuilder instance.
680 681 682
     */
    public function leftJoin($fromAlias, $join, $alias, $condition = null)
    {
683 684
        return $this->add('join', [
            $fromAlias => [
685 686 687
                'joinType'      => 'left',
                'joinTable'     => $join,
                'joinAlias'     => $alias,
688 689
                'joinCondition' => $condition,
            ],
690
        ], true);
691
    }
692

693 694 695 696 697 698 699 700 701 702
    /**
     * Creates and adds a right join to the query.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u.name')
     *         ->from('users', 'u')
     *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
     * </code>
     *
Benjamin Morel's avatar
Benjamin Morel committed
703 704 705 706 707
     * @param string $fromAlias The alias that points to a from clause.
     * @param string $join      The table name to join.
     * @param string $alias     The alias of the join table.
     * @param string $condition The condition for the join.
     *
708
     * @return $this This QueryBuilder instance.
709 710 711
     */
    public function rightJoin($fromAlias, $join, $alias, $condition = null)
    {
712 713
        return $this->add('join', [
            $fromAlias => [
714 715 716
                'joinType'      => 'right',
                'joinTable'     => $join,
                'joinAlias'     => $alias,
717 718
                'joinCondition' => $condition,
            ],
719
        ], true);
720
    }
721 722

    /**
723
     * Sets a new value for a column in a bulk update query.
724 725 726
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
727 728 729
     *         ->update('counters', 'c')
     *         ->set('c.value', 'c.value + 1')
     *         ->where('c.id = ?');
730 731
     * </code>
     *
Benjamin Morel's avatar
Benjamin Morel committed
732
     * @param string $key   The column to set.
733
     * @param string $value The value, expression, placeholder, etc.
Benjamin Morel's avatar
Benjamin Morel committed
734
     *
735
     * @return $this This QueryBuilder instance.
736 737 738
     */
    public function set($key, $value)
    {
739
        return $this->add('set', $key . ' = ' . $value, true);
740 741 742 743 744 745 746 747
    }

    /**
     * Specifies one or more restrictions to the query result.
     * Replaces any previously specified restrictions, if any.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
748 749 750
     *         ->select('c.value')
     *         ->from('counters', 'c')
     *         ->where('c.id = ?');
751 752 753 754 755
     *
     *     // You can optionally programatically build and/or expressions
     *     $qb = $conn->createQueryBuilder();
     *
     *     $or = $qb->expr()->orx();
756 757
     *     $or->add($qb->expr()->eq('c.id', 1));
     *     $or->add($qb->expr()->eq('c.id', 2));
758
     *
759 760
     *     $qb->update('counters', 'c')
     *         ->set('c.value', 'c.value + 1')
761 762 763 764
     *         ->where($or);
     * </code>
     *
     * @param mixed $predicates The restriction predicates.
Benjamin Morel's avatar
Benjamin Morel committed
765
     *
766
     * @return $this This QueryBuilder instance.
767 768 769
     */
    public function where($predicates)
    {
770
        if (! (func_num_args() === 1 && $predicates instanceof CompositeExpression)) {
771
            $predicates = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
        }

        return $this->add('where', $predicates);
    }

    /**
     * Adds one or more restrictions to the query results, forming a logical
     * conjunction with any previously specified restrictions.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u')
     *         ->from('users', 'u')
     *         ->where('u.username LIKE ?')
     *         ->andWhere('u.is_active = 1');
     * </code>
     *
789 790
     * @see where()
     *
791
     * @param mixed $where The query restrictions.
Benjamin Morel's avatar
Benjamin Morel committed
792
     *
793
     * @return $this This QueryBuilder instance.
794 795 796
     */
    public function andWhere($where)
    {
797
        $args  = func_get_args();
798
        $where = $this->getQueryPart('where');
799 800 801 802 803

        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) {
            $where->addMultiple($args);
        } else {
            array_unshift($args, $where);
804
            $where = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
        }

        return $this->add('where', $where, true);
    }

    /**
     * Adds one or more restrictions to the query results, forming a logical
     * disjunction with any previously specified restrictions.
     *
     * <code>
     *     $qb = $em->createQueryBuilder()
     *         ->select('u.name')
     *         ->from('users', 'u')
     *         ->where('u.id = 1')
     *         ->orWhere('u.id = 2');
     * </code>
     *
822 823
     * @see where()
     *
Benjamin Morel's avatar
Benjamin Morel committed
824 825
     * @param mixed $where The WHERE statement.
     *
826
     * @return $this This QueryBuilder instance.
827 828 829
     */
    public function orWhere($where)
    {
830
        $args  = func_get_args();
831
        $where = $this->getQueryPart('where');
832 833 834 835 836

        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) {
            $where->addMultiple($args);
        } else {
            array_unshift($args, $where);
837
            $where = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
        }

        return $this->add('where', $where, true);
    }

    /**
     * Specifies a grouping over the results of the query.
     * Replaces any previously specified groupings, if any.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u.name')
     *         ->from('users', 'u')
     *         ->groupBy('u.id');
     * </code>
     *
     * @param mixed $groupBy The grouping expression.
Benjamin Morel's avatar
Benjamin Morel committed
855
     *
856
     * @return $this This QueryBuilder instance.
857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
     */
    public function groupBy($groupBy)
    {
        if (empty($groupBy)) {
            return $this;
        }

        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();

        return $this->add('groupBy', $groupBy, false);
    }

    /**
     * Adds a grouping expression to the query.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->select('u.name')
     *         ->from('users', 'u')
876 877
     *         ->groupBy('u.lastLogin')
     *         ->addGroupBy('u.createdAt');
878 879 880
     * </code>
     *
     * @param mixed $groupBy The grouping expression.
Benjamin Morel's avatar
Benjamin Morel committed
881
     *
882
     * @return $this This QueryBuilder instance.
883 884 885 886 887 888 889 890 891 892 893 894
     */
    public function addGroupBy($groupBy)
    {
        if (empty($groupBy)) {
            return $this;
        }

        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();

        return $this->add('groupBy', $groupBy, true);
    }

Steve Müller's avatar
Steve Müller committed
895 896 897 898 899 900 901 902
    /**
     * Sets a value for a column in an insert query.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->insert('users')
     *         ->values(
     *             array(
Steve Müller's avatar
Steve Müller committed
903
     *                 'name' => '?'
Steve Müller's avatar
Steve Müller committed
904 905
     *             )
     *         )
Steve Müller's avatar
Steve Müller committed
906
     *         ->setValue('password', '?');
Steve Müller's avatar
Steve Müller committed
907 908 909 910 911
     * </code>
     *
     * @param string $column The column into which the value should be inserted.
     * @param string $value  The value that should be inserted into the column.
     *
912
     * @return $this This QueryBuilder instance.
Steve Müller's avatar
Steve Müller committed
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
     */
    public function setValue($column, $value)
    {
        $this->sqlParts['values'][$column] = $value;

        return $this;
    }

    /**
     * Specifies values for an insert query indexed by column names.
     * Replaces any previous values, if any.
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->insert('users')
     *         ->values(
     *             array(
Steve Müller's avatar
Steve Müller committed
930 931
     *                 'name' => '?',
     *                 'password' => '?'
Steve Müller's avatar
Steve Müller committed
932 933 934 935
     *             )
     *         );
     * </code>
     *
936
     * @param mixed[] $values The values to specify for the insert query indexed by column names.
Steve Müller's avatar
Steve Müller committed
937
     *
938
     * @return $this This QueryBuilder instance.
Steve Müller's avatar
Steve Müller committed
939 940 941 942 943 944
     */
    public function values(array $values)
    {
        return $this->add('values', $values);
    }

945 946 947 948 949
    /**
     * Specifies a restriction over the groups of the query.
     * Replaces any previous having restrictions, if any.
     *
     * @param mixed $having The restriction over the groups.
Benjamin Morel's avatar
Benjamin Morel committed
950
     *
951
     * @return $this This QueryBuilder instance.
952 953 954
     */
    public function having($having)
    {
955
        if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) {
956
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
957 958 959 960 961 962 963 964 965 966
        }

        return $this->add('having', $having);
    }

    /**
     * Adds a restriction over the groups of the query, forming a logical
     * conjunction with any existing having restrictions.
     *
     * @param mixed $having The restriction to append.
Benjamin Morel's avatar
Benjamin Morel committed
967
     *
968
     * @return $this This QueryBuilder instance.
969 970 971
     */
    public function andHaving($having)
    {
972
        $args   = func_get_args();
973
        $having = $this->getQueryPart('having');
974

975
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) {
976 977 978
            $having->addMultiple($args);
        } else {
            array_unshift($args, $having);
979
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
980 981 982 983 984 985 986 987 988 989
        }

        return $this->add('having', $having);
    }

    /**
     * Adds a restriction over the groups of the query, forming a logical
     * disjunction with any existing having restrictions.
     *
     * @param mixed $having The restriction to add.
Benjamin Morel's avatar
Benjamin Morel committed
990
     *
991
     * @return $this This QueryBuilder instance.
992 993 994
     */
    public function orHaving($having)
    {
995
        $args   = func_get_args();
996
        $having = $this->getQueryPart('having');
997

998
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) {
999 1000 1001
            $having->addMultiple($args);
        } else {
            array_unshift($args, $having);
1002
            $having = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
1003 1004 1005 1006 1007 1008 1009 1010 1011
        }

        return $this->add('having', $having);
    }

    /**
     * Specifies an ordering for the query results.
     * Replaces any previously specified orderings, if any.
     *
Benjamin Morel's avatar
Benjamin Morel committed
1012
     * @param string $sort  The ordering expression.
1013
     * @param string $order The ordering direction.
Benjamin Morel's avatar
Benjamin Morel committed
1014
     *
1015
     * @return $this This QueryBuilder instance.
1016 1017 1018
     */
    public function orderBy($sort, $order = null)
    {
1019
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), false);
1020 1021 1022 1023 1024
    }

    /**
     * Adds an ordering to the query results.
     *
Benjamin Morel's avatar
Benjamin Morel committed
1025
     * @param string $sort  The ordering expression.
1026
     * @param string $order The ordering direction.
Benjamin Morel's avatar
Benjamin Morel committed
1027
     *
1028
     * @return $this This QueryBuilder instance.
1029 1030 1031
     */
    public function addOrderBy($sort, $order = null)
    {
1032
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), true);
1033 1034 1035
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1036
     * Gets a query part by its name.
1037 1038
     *
     * @param string $queryPartName
Benjamin Morel's avatar
Benjamin Morel committed
1039 1040
     *
     * @return mixed
1041 1042 1043 1044 1045 1046 1047
     */
    public function getQueryPart($queryPartName)
    {
        return $this->sqlParts[$queryPartName];
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1048
     * Gets all query parts.
1049
     *
1050
     * @return mixed[]
1051 1052 1053 1054 1055 1056 1057
     */
    public function getQueryParts()
    {
        return $this->sqlParts;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1058
     * Resets SQL parts.
1059
     *
1060
     * @param string[]|null $queryPartNames
Benjamin Morel's avatar
Benjamin Morel committed
1061
     *
1062
     * @return $this This QueryBuilder instance.
1063 1064 1065
     */
    public function resetQueryParts($queryPartNames = null)
    {
1066
        if ($queryPartNames === null) {
1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
            $queryPartNames = array_keys($this->sqlParts);
        }

        foreach ($queryPartNames as $queryPartName) {
            $this->resetQueryPart($queryPartName);
        }

        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1078
     * Resets a single SQL part.
1079 1080
     *
     * @param string $queryPartName
Benjamin Morel's avatar
Benjamin Morel committed
1081
     *
1082
     * @return $this This QueryBuilder instance.
1083 1084 1085 1086
     */
    public function resetQueryPart($queryPartName)
    {
        $this->sqlParts[$queryPartName] = is_array($this->sqlParts[$queryPartName])
1087
            ? [] : null;
1088 1089 1090 1091 1092

        $this->state = self::STATE_DIRTY;

        return $this;
    }
1093

Benjamin Morel's avatar
Benjamin Morel committed
1094 1095 1096
    /**
     * @return string
     *
1097
     * @throws QueryException
Benjamin Morel's avatar
Benjamin Morel committed
1098
     */
1099 1100
    private function getSQLForSelect()
    {
1101
        $query = 'SELECT ' . implode(', ', $this->sqlParts['select']);
1102

1103
        $query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '')
jeroendedauw's avatar
jeroendedauw committed
1104 1105 1106 1107 1108
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '')
            . ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '')
            . ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '')
            . ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : '');

1109 1110 1111 1112 1113 1114 1115 1116 1117
        if ($this->isLimitQuery()) {
            return $this->connection->getDatabasePlatform()->modifyLimitQuery(
                $query,
                $this->maxResults,
                $this->firstResult
            );
        }

        return $query;
jeroendedauw's avatar
jeroendedauw committed
1118 1119
    }

1120 1121 1122
    /**
     * @return string[]
     */
jeroendedauw's avatar
jeroendedauw committed
1123 1124
    private function getFromClauses()
    {
1125
        $fromClauses  = [];
1126
        $knownAliases = [];
Deni's avatar
Deni committed
1127

1128 1129
        // Loop through all FROM clauses
        foreach ($this->sqlParts['from'] as $from) {
1130
            if ($from['alias'] === null) {
1131
                $tableSql       = $from['table'];
1132
                $tableReference = $from['table'];
1133
            } else {
1134
                $tableSql       = $from['table'] . ' ' . $from['alias'];
1135 1136
                $tableReference = $from['alias'];
            }
jeroendedauw's avatar
jeroendedauw committed
1137

1138
            $knownAliases[$tableReference] = true;
Deni's avatar
Deni committed
1139

1140
            $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1141
        }
1142

jeroendedauw's avatar
jeroendedauw committed
1143
        $this->verifyAllAliasesAreKnown($knownAliases);
jeroendedauw's avatar
jeroendedauw committed
1144 1145 1146 1147

        return $fromClauses;
    }

1148
    /**
1149
     * @param string[] $knownAliases
1150 1151 1152
     *
     * @throws QueryException
     */
jeroendedauw's avatar
jeroendedauw committed
1153
    private function verifyAllAliasesAreKnown(array $knownAliases)
jeroendedauw's avatar
jeroendedauw committed
1154
    {
Deni's avatar
Deni committed
1155
        foreach ($this->sqlParts['join'] as $fromAlias => $joins) {
1156
            if (! isset($knownAliases[$fromAlias])) {
Deni's avatar
Deni committed
1157 1158 1159
                throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases));
            }
        }
1160
    }
1161

1162 1163 1164
    /**
     * @return bool
     */
1165 1166
    private function isLimitQuery()
    {
1167
        return $this->maxResults !== null || $this->firstResult !== null;
1168 1169
    }

Steve Müller's avatar
Steve Müller committed
1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181
    /**
     * Converts this instance into an INSERT string in SQL.
     *
     * @return string
     */
    private function getSQLForInsert()
    {
        return 'INSERT INTO ' . $this->sqlParts['from']['table'] .
        ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' .
        ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')';
    }

1182 1183
    /**
     * Converts this instance into an UPDATE string in SQL.
1184
     *
1185 1186 1187 1188
     * @return string
     */
    private function getSQLForUpdate()
    {
1189
        $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
1190

1191 1192
        return 'UPDATE ' . $table
            . ' SET ' . implode(', ', $this->sqlParts['set'])
Steve Müller's avatar
Steve Müller committed
1193
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1194
    }
1195

1196 1197
    /**
     * Converts this instance into a DELETE string in SQL.
1198
     *
1199 1200 1201 1202
     * @return string
     */
    private function getSQLForDelete()
    {
1203
        $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
1204

1205
        return 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217
    }

    /**
     * Gets a string representation of this QueryBuilder which corresponds to
     * the final SQL query being constructed.
     *
     * @return string The string representation of this QueryBuilder.
     */
    public function __toString()
    {
        return $this->getSQL();
    }
1218

1219
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1220
     * Creates a new named parameter and bind the value $value to it.
1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239
     *
     * This method provides a shortcut for PDOStatement::bindValue
     * when using prepared statements.
     *
     * The parameter $value specifies the value that you want to bind. If
     * $placeholder is not provided bindValue() will automatically create a
     * placeholder for you. An automatic placeholder will be of the name
     * ':dcValue1', ':dcValue2' etc.
     *
     * For more information see {@link http://php.net/pdostatement-bindparam}
     *
     * Example:
     * <code>
     * $value = 2;
     * $q->eq( 'id', $q->bindValue( $value ) );
     * $stmt = $q->executeQuery(); // executed with 'id = 2'
     * </code>
     *
     * @link http://www.zetacomponents.org
Benjamin Morel's avatar
Benjamin Morel committed
1240 1241 1242 1243 1244
     *
     * @param mixed  $value
     * @param mixed  $type
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
     *
1245 1246
     * @return string the placeholder name used.
     */
1247
    public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null)
1248
    {
Steve Müller's avatar
Steve Müller committed
1249
        if ($placeHolder === null) {
1250
            $this->boundCounter++;
1251
            $placeHolder = ':dcValue' . $this->boundCounter;
1252 1253 1254 1255 1256
        }
        $this->setParameter(substr($placeHolder, 1), $value, $type);

        return $placeHolder;
    }
1257

1258
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1259
     * Creates a new positional parameter and bind the given value to it.
1260
     *
1261 1262 1263 1264
     * Attention: If you are using positional parameters with the query builder you have
     * to be very careful to bind all parameters in the order they appear in the SQL
     * statement , otherwise they get bound in the wrong order which can lead to serious
     * bugs in your code.
1265
     *
1266 1267 1268 1269 1270
     * Example:
     * <code>
     *  $qb = $conn->createQueryBuilder();
     *  $qb->select('u.*')
     *     ->from('users', 'u')
1271 1272
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1273
     * </code>
1274
     *
1275 1276
     * @param mixed $value
     * @param int   $type
Benjamin Morel's avatar
Benjamin Morel committed
1277
     *
1278 1279
     * @return string
     */
1280
    public function createPositionalParameter($value, $type = ParameterType::STRING)
1281 1282 1283
    {
        $this->boundCounter++;
        $this->setParameter($this->boundCounter, $value, $type);
1284

1285
        return '?';
1286
    }
Deni's avatar
Deni committed
1287

Benjamin Morel's avatar
Benjamin Morel committed
1288
    /**
1289 1290
     * @param string   $fromAlias
     * @param string[] $knownAliases
Benjamin Morel's avatar
Benjamin Morel committed
1291 1292
     *
     * @return string
1293 1294
     *
     * @throws QueryException
Benjamin Morel's avatar
Benjamin Morel committed
1295
     */
Deni's avatar
Deni committed
1296 1297 1298 1299 1300 1301
    private function getSQLForJoins($fromAlias, array &$knownAliases)
    {
        $sql = '';

        if (isset($this->sqlParts['join'][$fromAlias])) {
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
jarekj's avatar
jarekj committed
1302
                if (array_key_exists($join['joinAlias'], $knownAliases)) {
1303
                    throw QueryException::nonUniqueAlias($join['joinAlias'], array_keys($knownAliases));
jarekj's avatar
jarekj committed
1304
                }
1305
                $sql                             .= ' ' . strtoupper($join['joinType'])
Steve Müller's avatar
Steve Müller committed
1306 1307
                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
                    . ' ON ' . ((string) $join['joinCondition']);
Deni's avatar
Deni committed
1308
                $knownAliases[$join['joinAlias']] = true;
1309
            }
Deni's avatar
Deni committed
1310

1311
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
Deni's avatar
Deni committed
1312 1313 1314 1315 1316 1317
                $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases);
            }
        }

        return $sql;
    }
Paul's avatar
Paul committed
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328

    /**
     * Deep clone of all expression objects in the SQL parts.
     *
     * @return void
     */
    public function __clone()
    {
        foreach ($this->sqlParts as $part => $elements) {
            if (is_array($this->sqlParts[$part])) {
                foreach ($this->sqlParts[$part] as $idx => $element) {
1329 1330
                    if (! is_object($element)) {
                        continue;
Paul's avatar
Paul committed
1331
                    }
1332 1333

                    $this->sqlParts[$part][$idx] = clone $element;
Paul's avatar
Paul committed
1334
                }
Steve Müller's avatar
Steve Müller committed
1335
            } elseif (is_object($elements)) {
Paul's avatar
Paul committed
1336 1337 1338 1339
                $this->sqlParts[$part] = clone $elements;
            }
        }

1340
        foreach ($this->params as $name => $param) {
1341 1342
            if (! is_object($param)) {
                continue;
Paul's avatar
Paul committed
1343
            }
1344 1345

            $this->params[$name] = clone $param;
Paul's avatar
Paul committed
1346 1347
        }
    }
1348
}