QueryBuilder.php 38.2 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 406 407
     * @param string $sqlPartName
     * @param string $sqlPart
     * @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 507 508 509 510
     *
     * <code>
     *     $qb = $conn->createQueryBuilder()
     *         ->delete('users', 'u')
     *         ->where('u.id = :user_id');
     *         ->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 876 877 878 879 880 881
     */
    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')
     *         ->groupBy('u.lastLogin');
     *         ->addGroupBy('u.createdAt')
     * </code>
     *
     * @param mixed $groupBy The grouping expression.
Benjamin Morel's avatar
Benjamin Morel committed
882
     *
883
     * @return $this This QueryBuilder instance.
884 885 886 887 888 889 890 891 892 893 894 895
     */
    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
896 897 898 899 900 901 902 903
    /**
     * 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
904
     *                 'name' => '?'
Steve Müller's avatar
Steve Müller committed
905 906
     *             )
     *         )
Steve Müller's avatar
Steve Müller committed
907
     *         ->setValue('password', '?');
Steve Müller's avatar
Steve Müller committed
908 909 910 911 912
     * </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.
     *
913
     * @return $this This QueryBuilder instance.
Steve Müller's avatar
Steve Müller committed
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
     */
    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
931 932
     *                 'name' => '?',
     *                 'password' => '?'
Steve Müller's avatar
Steve Müller committed
933 934 935 936
     *             )
     *         );
     * </code>
     *
937
     * @param mixed[] $values The values to specify for the insert query indexed by column names.
Steve Müller's avatar
Steve Müller committed
938
     *
939
     * @return $this This QueryBuilder instance.
Steve Müller's avatar
Steve Müller committed
940 941 942 943 944 945
     */
    public function values(array $values)
    {
        return $this->add('values', $values);
    }

946 947 948 949 950
    /**
     * 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
951
     *
952
     * @return $this This QueryBuilder instance.
953 954 955
     */
    public function having($having)
    {
956
        if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) {
957
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
958 959 960 961 962 963 964 965 966 967
        }

        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
968
     *
969
     * @return $this This QueryBuilder instance.
970 971 972
     */
    public function andHaving($having)
    {
973
        $args   = func_get_args();
974
        $having = $this->getQueryPart('having');
975

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

        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
991
     *
992
     * @return $this This QueryBuilder instance.
993 994 995
     */
    public function orHaving($having)
    {
996
        $args   = func_get_args();
997
        $having = $this->getQueryPart('having');
998

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

        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
1013
     * @param string $sort  The ordering expression.
1014
     * @param string $order The ordering direction.
Benjamin Morel's avatar
Benjamin Morel committed
1015
     *
1016
     * @return $this This QueryBuilder instance.
1017 1018 1019
     */
    public function orderBy($sort, $order = null)
    {
1020
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), false);
1021 1022 1023 1024 1025
    }

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

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

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

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

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

        return $this;
    }

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

        $this->state = self::STATE_DIRTY;

        return $this;
    }
1094

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

1104
        $query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '')
jeroendedauw's avatar
jeroendedauw committed
1105 1106 1107 1108 1109
            . ($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']) : '');

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

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

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

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

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

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

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

        return $fromClauses;
    }

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

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

Steve Müller's avatar
Steve Müller committed
1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182
    /**
     * 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']) . ')';
    }

1183 1184
    /**
     * Converts this instance into an UPDATE string in SQL.
1185
     *
1186 1187 1188 1189
     * @return string
     */
    private function getSQLForUpdate()
    {
1190 1191 1192 1193 1194 1195 1196 1197
        $from = $this->sqlParts['from'];

        if ($from['alias'] === null || $from['alias'] === $from['table']) {
            $table = $from['table'];
        } else {
            $table = $from['table'] . ' ' . $from['alias'];
        }

1198 1199
        return 'UPDATE ' . $table
            . ' SET ' . implode(', ', $this->sqlParts['set'])
Steve Müller's avatar
Steve Müller committed
1200
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1201
    }
1202

1203 1204
    /**
     * Converts this instance into a DELETE string in SQL.
1205
     *
1206 1207 1208 1209
     * @return string
     */
    private function getSQLForDelete()
    {
1210 1211 1212 1213 1214 1215 1216 1217
        $from = $this->sqlParts['from'];

        if ($from['alias'] === null || $from['alias'] === $from['table']) {
            $table = $from['table'];
        } else {
            $table = $from['table'] . ' ' . $from['alias'];
        }

1218
        return 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230
    }

    /**
     * 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();
    }
1231

1232
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1233
     * Creates a new named parameter and bind the value $value to it.
1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252
     *
     * 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
1253 1254 1255 1256 1257
     *
     * @param mixed  $value
     * @param mixed  $type
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
     *
1258 1259
     * @return string the placeholder name used.
     */
1260
    public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null)
1261
    {
Steve Müller's avatar
Steve Müller committed
1262
        if ($placeHolder === null) {
1263
            $this->boundCounter++;
1264
            $placeHolder = ':dcValue' . $this->boundCounter;
1265 1266 1267 1268 1269
        }
        $this->setParameter(substr($placeHolder, 1), $value, $type);

        return $placeHolder;
    }
1270

1271
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1272
     * Creates a new positional parameter and bind the given value to it.
1273
     *
1274 1275 1276 1277
     * 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.
1278
     *
1279 1280 1281 1282 1283
     * Example:
     * <code>
     *  $qb = $conn->createQueryBuilder();
     *  $qb->select('u.*')
     *     ->from('users', 'u')
1284 1285
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1286
     * </code>
1287
     *
1288 1289
     * @param mixed $value
     * @param int   $type
Benjamin Morel's avatar
Benjamin Morel committed
1290
     *
1291 1292
     * @return string
     */
1293
    public function createPositionalParameter($value, $type = ParameterType::STRING)
1294 1295 1296
    {
        $this->boundCounter++;
        $this->setParameter($this->boundCounter, $value, $type);
1297

1298
        return '?';
1299
    }
Deni's avatar
Deni committed
1300

Benjamin Morel's avatar
Benjamin Morel committed
1301
    /**
1302 1303
     * @param string   $fromAlias
     * @param string[] $knownAliases
Benjamin Morel's avatar
Benjamin Morel committed
1304 1305
     *
     * @return string
1306 1307
     *
     * @throws QueryException
Benjamin Morel's avatar
Benjamin Morel committed
1308
     */
Deni's avatar
Deni committed
1309 1310 1311 1312 1313 1314
    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
1315
                if (array_key_exists($join['joinAlias'], $knownAliases)) {
1316
                    throw QueryException::nonUniqueAlias($join['joinAlias'], array_keys($knownAliases));
jarekj's avatar
jarekj committed
1317
                }
1318
                $sql                             .= ' ' . strtoupper($join['joinType'])
Steve Müller's avatar
Steve Müller committed
1319 1320
                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
                    . ' ON ' . ((string) $join['joinCondition']);
Deni's avatar
Deni committed
1321
                $knownAliases[$join['joinAlias']] = true;
1322
            }
Deni's avatar
Deni committed
1323

1324
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
Deni's avatar
Deni committed
1325 1326 1327 1328 1329 1330
                $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases);
            }
        }

        return $sql;
    }
Paul's avatar
Paul committed
1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341

    /**
     * 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) {
1342 1343
                    if (! is_object($element)) {
                        continue;
Paul's avatar
Paul committed
1344
                    }
1345 1346

                    $this->sqlParts[$part][$idx] = clone $element;
Paul's avatar
Paul committed
1347
                }
Steve Müller's avatar
Steve Müller committed
1348
            } elseif (is_object($elements)) {
Paul's avatar
Paul committed
1349 1350 1351 1352
                $this->sqlParts[$part] = clone $elements;
            }
        }

1353
        foreach ($this->params as $name => $param) {
1354 1355
            if (! is_object($param)) {
                continue;
Paul's avatar
Paul committed
1356
            }
1357 1358

            $this->params[$name] = clone $param;
Paul's avatar
Paul committed
1359 1360
        }
    }
1361
}