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