PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Database / Query / Builder.php

Builder.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.10.01, at vendor/wpfluent/framework/src/WPFluent/Database/Query/Builder.php

4,628 lines 127.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\Framework\Database\Query;
4
5 use Closure;
6 use DatePeriod;
7 use LogicException;
8 use RuntimeException;
9 use DateTimeInterface;
10 use InvalidArgumentException;
11 use FluentCommunity\Framework\Foundation\App;
12 use FluentCommunity\Framework\Support\Arr;
13 use FluentCommunity\Framework\Support\Str;
14 use FluentCommunity\Framework\Support\Helper;
15 use FluentCommunity\Framework\Support\MacroableTrait;
16 use FluentCommunity\Framework\Support\Collection;
17 use FluentCommunity\Framework\Pagination\Paginator;
18 use FluentCommunity\Framework\Support\ForwardsCalls;
19 use FluentCommunity\Framework\Support\LazyCollection;
20 use FluentCommunity\Framework\Database\Query\Expression;
21 use FluentCommunity\Framework\Database\Query\Grammars\Grammar;
22 use FluentCommunity\Framework\Database\Query\Processors\Processor;
23 use FluentCommunity\Framework\Database\Query\ConditionExpression;
24 use FluentCommunity\Framework\Support\ArrayableInterface;
25 use FluentCommunity\Framework\Database\ConnectionInterface;
26 use FluentCommunity\Framework\Database\Concerns\BuildsQueries;
27 use FluentCommunity\Framework\Database\Concerns\BuildsWhereDateClauses;
28 use FluentCommunity\Framework\Database\Concerns\ExplainsQueries;
29 use FluentCommunity\Framework\Database\Orm\Relations\Relation;
30 use FluentCommunity\Framework\Database\Orm\Builder as OrmBuilder;
31
32 class Builder
33 {
34 use BuildsQueries, BuildsWhereDateClauses, ExplainsQueries, ForwardsCalls, MacroableTrait {
35 __call as macroCall;
36 }
37
38 /**
39 * The database connection instance.
40 *
41 * @var \FluentCommunity\Framework\Database\ConnectionInterface
42 */
43 public $connection;
44
45 /**
46 * The database query grammar instance.
47 *
48 * @var \FluentCommunity\Framework\Database\Query\Grammars\Grammar
49 */
50 public $grammar;
51
52 /**
53 * The database query post processor instance.
54 *
55 * @var \FluentCommunity\Framework\Database\Query\Processors\Processor
56 */
57 public $processor;
58
59 /**
60 * The current query value bindings.
61 *
62 * @var array
63 */
64 public $bindings = [
65 'select' => [],
66 'from' => [],
67 'join' => [],
68 'where' => [],
69 'groupBy' => [],
70 'having' => [],
71 'order' => [],
72 'union' => [],
73 'unionOrder' => [],
74 ];
75
76 /**
77 * An aggregate function and column to be run.
78 *
79 * @var array
80 */
81 public $aggregate;
82
83 /**
84 * The columns that should be returned.
85 *
86 * @var array
87 */
88 public $columns;
89
90 /**
91 * Indicates if the query returns distinct results.
92 *
93 * Occasionally contains the columns that should be distinct.
94 *
95 * @var bool|array
96 */
97 public $distinct = false;
98
99 /**
100 * The table which the query is targeting.
101 *
102 * @var string
103 */
104 public $from;
105
106 /**
107 * The index hint for the query.
108 *
109 * @var \FluentCommunity\Framework\Database\Query\IndexHint
110 */
111 public $indexHint;
112
113 /**
114 * The table joins for the query.
115 *
116 * @var array
117 */
118 public $joins;
119
120 /**
121 * The where constraints for the query.
122 *
123 * @var array
124 */
125 public $wheres = [];
126
127 /**
128 * The groupings for the query.
129 *
130 * @var array
131 */
132 public $groups;
133
134 /**
135 * The having constraints for the query.
136 *
137 * @var array
138 */
139 public $havings;
140
141 /**
142 * The orderings for the query.
143 *
144 * @var array
145 */
146 public $orders;
147
148 /**
149 * The maximum number of records to return.
150 *
151 * @var int
152 */
153 public $limit;
154
155 /**
156 * The maximum number of records to return per group.
157 *
158 * @var array
159 */
160 public $groupLimit;
161
162 /**
163 * The number of records to skip.
164 *
165 * @var int
166 */
167 public $offset;
168
169 /**
170 * The query union statements.
171 *
172 * @var array
173 */
174 public $unions;
175
176 /**
177 * The maximum number of union records to return.
178 *
179 * @var int
180 */
181 public $unionLimit;
182
183 /**
184 * The number of union records to skip.
185 *
186 * @var int
187 */
188 public $unionOffset;
189
190 /**
191 * The orderings for the union query.
192 *
193 * @var array
194 */
195 public $unionOrders;
196
197 /**
198 * Indicates whether row locking is being used.
199 *
200 * @var string|bool
201 */
202 public $lock;
203
204 /**
205 * The callbacks that should be invoked before the query is executed.
206 *
207 * @var array
208 */
209 public $beforeQueryCallbacks = [];
210
211 /**
212 * The callbacks that should be invoked after retrieving data from the database.
213 *
214 * @var array
215 */
216 protected $afterQueryCallbacks = [];
217
218 /**
219 * All of the available clause operators.
220 *
221 * @var string[]
222 */
223 public $operators = [
224 '=', '<', '>', '<=', '>=', '<>', '!=', '<=>',
225 'like', 'like binary', 'not like', 'ilike',
226 '&', '|', '^', '<<', '>>', '&~', 'is', 'is not',
227 'rlike', 'not rlike', 'regexp', 'not regexp',
228 '~', '~*', '!~', '!~*', 'similar to',
229 'not similar to', 'not ilike', '~~*', '!~~*',
230 ];
231
232 /**
233 * All of the available bitwise operators.
234 *
235 * @var string[]
236 */
237 public $bitwiseOperators = [
238 '&', '|', '^', '<<', '>>', '&~',
239 ];
240
241 /**
242 * Whether to use write pdo for the select.
243 *
244 * @var bool
245 */
246 public $useWritePdo = false;
247
248 /**
249 * Allow dynamic property injection.
250 *
251 * @var array
252 */
253 protected $dynamicProperties = [];
254
255 /**
256 * Create a new query builder instance.
257 *
258 * @param \FluentCommunity\Framework\Database\ConnectionInterface $connection
259 * @param \FluentCommunity\Framework\Database\Query\Grammars\Grammar|null $grammar
260 * @param \FluentCommunity\Framework\Database\Query\Processors\Processor|null $processor
261 * @return void
262 */
263 public function __construct(
264 ConnectionInterface $connection,
265 ?Grammar $grammar = null,
266 ?Processor $processor = null
267 ) {
268 $this->connection = $connection;
269 $this->grammar = $grammar ?: $connection->getQueryGrammar();
270 $this->processor = $processor ?: $connection->getPostProcessor();
271 }
272
273 /**
274 * Set the columns to be selected.
275 *
276 * @param array|mixed $columns
277 * @return $this
278 */
279 public function select($columns = ['*'])
280 {
281 $this->columns = [];
282 $this->bindings['select'] = [];
283
284 $columns = is_array($columns) ? $columns : func_get_args();
285
286 foreach ($columns as $as => $column) {
287 if (is_string($as) && $this->isQueryable($column)) {
288 $this->selectSub($column, $as);
289 } else {
290 $this->columns[] = $column;
291 }
292 }
293
294 return $this;
295 }
296
297 /**
298 * Add a subselect expression to the query.
299 *
300 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
301 * @param string $as
302 * @return $this
303 *
304 * @throws \InvalidArgumentException
305 */
306 public function selectSub($query, $as)
307 {
308 $this->grammar->addAlias($as);
309
310 [$query, $bindings] = $this->createSub($query);
311
312 return $this->selectRaw(
313 '('.$query.') as '.$this->grammar->wrap($as), $bindings
314 );
315 }
316
317 /**
318 * Add a new "raw" select expression to the query.
319 *
320 * @param string $expression
321 * @param array $bindings
322 * @return $this
323 */
324 public function selectRaw($expression, array $bindings = [])
325 {
326 $this->addSelect(new Expression($expression));
327
328 if ($bindings) {
329 $this->addBinding($bindings, 'select');
330 }
331
332 return $this;
333 }
334
335 /**
336 * Makes "from" fetch from a subquery.
337 *
338 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $query
339 * @param string $as
340 * @return $this
341 *
342 * @throws \InvalidArgumentException
343 */
344 public function fromSub($query, $as)
345 {
346 $this->grammar->addAlias($as);
347
348 [$query, $bindings] = $this->createSub($query);
349
350 return $this->fromRaw('('.$query.') as '.$this->grammar->wrapTable($as), $bindings);
351 }
352
353 /**
354 * Add a raw from clause to the query.
355 *
356 * @param string $expression
357 * @param mixed $bindings
358 * @return $this
359 */
360 public function fromRaw($expression, $bindings = [])
361 {
362 $this->from = new Expression($expression);
363
364 $this->addBinding($bindings, 'from');
365
366 return $this;
367 }
368
369 /**
370 * Creates a subquery and parse it.
371 *
372 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $query
373 * @return array
374 */
375 protected function createSub($query)
376 {
377 // If the given query is a Closure, we will execute it while passing in a new
378 // query instance to the Closure. This will give the developer a chance to
379 // format and work with the query before we cast it to a raw SQL string.
380 if ($query instanceof Closure) {
381 $callback = $query;
382
383 $callback($query = $this->forSubQuery());
384 }
385
386 return $this->parseSub($query);
387 }
388
389 /**
390 * Parse the subquery into SQL and bindings.
391 *
392 * @param mixed $query
393 * @return array
394 *
395 * @throws \InvalidArgumentException
396 */
397 protected function parseSub($query)
398 {
399 if ($query instanceof self || $query instanceof OrmBuilder || $query instanceof Relation) {
400 $query = $this->prependDatabaseNameIfCrossDatabaseQuery($query);
401
402 return [$query->toSql(), $query->getBindings()];
403 } elseif (is_string($query)) {
404 return [$query, []];
405 } else {
406 throw new InvalidArgumentException(
407 'A subquery must be a query builder instance, a Closure, or a string.'
408 );
409 }
410 }
411
412 /**
413 * Prepend the database name if the given query is on another database.
414 *
415 * @param mixed $query
416 * @return mixed
417 */
418 protected function prependDatabaseNameIfCrossDatabaseQuery($query)
419 {
420 if ($query->getConnection()->getDatabaseName() !==
421 $this->getConnection()->getDatabaseName()) {
422 $databaseName = $query->getConnection()->getDatabaseName();
423
424 if (! str_starts_with($query->from, $databaseName) && ! str_contains($query->from, '.')) {
425 $query->from($databaseName.'.'.$query->from);
426 }
427 }
428
429 return $query;
430 }
431
432 /**
433 * Add a new select column to the query.
434 *
435 * @param array|mixed $column
436 * @return $this
437 */
438 public function addSelect($column)
439 {
440 $columns = is_array($column) ? $column : func_get_args();
441
442 foreach ($columns as $as => $column) {
443 if (is_string($as) && $this->isQueryable($column)) {
444 if (is_null($this->columns)) {
445 $this->select($this->from.'.*');
446 }
447
448 $this->selectSub($column, $as);
449 } else {
450 if (is_array($this->columns) && in_array($column, $this->columns, true)) {
451 continue;
452 }
453
454 $this->columns[] = $column;
455 }
456 }
457
458 return $this;
459 }
460
461 /**
462 * Force the query to only return distinct results.
463 *
464 * @return $this
465 */
466 public function distinct()
467 {
468 $columns = func_get_args();
469
470 if (count($columns) > 0) {
471 $this->distinct = is_array($columns[0]) || is_bool($columns[0]) ? $columns[0] : $columns;
472 } else {
473 $this->distinct = true;
474 }
475
476 return $this;
477 }
478
479 /**
480 * Set the table which the query is targeting.
481 *
482 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $table
483 * @param string|null $as
484 * @return $this
485 */
486 public function from($table, $as = null)
487 {
488 if (is_string($table) && stripos($table, ' as ') !== false) {
489 [$table, $as] = explode(' as ', $table);
490 $this->grammar->addAlias($as);
491 }
492
493 if ($this->isQueryable($table)) {
494 return $this->fromSub($table, $as);
495 }
496
497 $this->from = $as ? "{$table} as {$as}" : $table;
498
499 return $this;
500 }
501
502 /**
503 * Add an index hint to suggest a query index.
504 *
505 * @param string $index
506 * @return $this
507 */
508 public function useIndex($index)
509 {
510 $this->indexHint = new IndexHint('hint', $index);
511
512 return $this;
513 }
514
515 /**
516 * Add an index hint to force a query index.
517 *
518 * @param string $index
519 * @return $this
520 */
521 public function forceIndex($index)
522 {
523 $this->indexHint = new IndexHint('force', $index);
524
525 return $this;
526 }
527
528 /**
529 * Add an index hint to ignore a query index.
530 *
531 * @param string $index
532 * @return $this
533 */
534 public function ignoreIndex($index)
535 {
536 $this->indexHint = new IndexHint('ignore', $index);
537
538 return $this;
539 }
540
541 /**
542 * Add a join clause to the query.
543 *
544 * @param string $table
545 * @param \Closure|string $first
546 * @param string|null $operator
547 * @param string|null $second
548 * @param string $type
549 * @param bool $where
550 * @return $this
551 */
552 public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
553 {
554 $join = $this->newJoinClause($this, $type, $table);
555
556 // If the first "column" of the join is really a Closure instance the developer
557 // is trying to build a join with a complex "on" clause containing more than
558 // one condition, so we'll add the join and call a Closure with the query.
559 if ($first instanceof Closure) {
560 $first($join);
561
562 $this->joins[] = $join;
563
564 $this->addBinding($join->getBindings(), 'join');
565 }
566
567 // If the column is simply a string, we can assume the join simply has a basic
568 // "on" clause with a single condition. So we will just build the join with
569 // this simple join clauses attached to it. There is not a join callback.
570 else {
571 $method = $where ? 'where' : 'on';
572
573 $this->joins[] = $join->$method($first, $operator, $second);
574
575 $this->addBinding($join->getBindings(), 'join');
576 }
577
578 return $this;
579 }
580
581 /**
582 * Add a "join where" clause to the query.
583 *
584 * @param string $table
585 * @param \Closure|string $first
586 * @param string $operator
587 * @param string $second
588 * @param string $type
589 * @return $this
590 */
591 public function joinWhere($table, $first, $operator, $second, $type = 'inner')
592 {
593 return $this->join($table, $first, $operator, $second, $type, true);
594 }
595
596 /**
597 * Add a subquery join clause to the query.
598 *
599 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
600 * @param string $as
601 * @param \Closure|string $first
602 * @param string|null $operator
603 * @param string|null $second
604 * @param string $type
605 * @param bool $where
606 * @return $this
607 *
608 * @throws \InvalidArgumentException
609 */
610 public function joinSub($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false)
611 {
612 $this->grammar->addAlias($as);
613
614 [$query, $bindings] = $this->createSub($query);
615
616 $expression = '('.$query.') as '.$this->grammar->wrapTable($as);
617
618 $this->addBinding($bindings, 'join');
619
620 return $this->join(new Expression($expression), $first, $operator, $second, $type, $where);
621 }
622
623 /**
624 * Add a lateral join clause to the query.
625 *
626 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
627 * @param string $as
628 * @param string $type
629 * @return $this
630 */
631 public function joinLateral($query, string $as, string $type = 'inner')
632 {
633 [$query, $bindings] = $this->createSub($query);
634
635 $expression = '('.$query.') as '.$this->grammar->wrapTable($as);
636
637 $this->addBinding($bindings, 'join');
638
639 $this->joins[] = $this->newJoinLateralClause($this, $type, new Expression($expression));
640
641 return $this;
642 }
643
644 /**
645 * Add a lateral left join to the query.
646 *
647 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
648 * @param string $as
649 * @return $this
650 */
651 public function leftJoinLateral($query, string $as)
652 {
653 return $this->joinLateral($query, $as, 'left');
654 }
655
656 /**
657 * Add a left join to the query.
658 *
659 * @param string $table
660 * @param \Closure|string $first
661 * @param string|null $operator
662 * @param string|null $second
663 * @return $this
664 */
665 public function leftJoin($table, $first, $operator = null, $second = null)
666 {
667 return $this->join($table, $first, $operator, $second, 'left');
668 }
669
670 /**
671 * Add a "join where" clause to the query.
672 *
673 * @param string $table
674 * @param \Closure|string $first
675 * @param string $operator
676 * @param string $second
677 * @return $this
678 */
679 public function leftJoinWhere($table, $first, $operator, $second)
680 {
681 return $this->joinWhere($table, $first, $operator, $second, 'left');
682 }
683
684 /**
685 * Add a subquery left join to the query.
686 *
687 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
688 * @param string $as
689 * @param \Closure|string $first
690 * @param string|null $operator
691 * @param string|null $second
692 * @return $this
693 */
694 public function leftJoinSub($query, $as, $first, $operator = null, $second = null)
695 {
696 return $this->joinSub($query, $as, $first, $operator, $second, 'left');
697 }
698
699 /**
700 * Add a right join to the query.
701 *
702 * @param string $table
703 * @param \Closure|string $first
704 * @param string|null $operator
705 * @param string|null $second
706 * @return $this
707 */
708 public function rightJoin($table, $first, $operator = null, $second = null)
709 {
710 return $this->join($table, $first, $operator, $second, 'right');
711 }
712
713 /**
714 * Add a "right join where" clause to the query.
715 *
716 * @param string $table
717 * @param \Closure|string $first
718 * @param string $operator
719 * @param string $second
720 * @return $this
721 */
722 public function rightJoinWhere($table, $first, $operator, $second)
723 {
724 return $this->joinWhere($table, $first, $operator, $second, 'right');
725 }
726
727 /**
728 * Add a subquery right join to the query.
729 *
730 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
731 * @param string $as
732 * @param \Closure|string $first
733 * @param string|null $operator
734 * @param string|null $second
735 * @return $this
736 */
737 public function rightJoinSub($query, $as, $first, $operator = null, $second = null)
738 {
739 return $this->joinSub($query, $as, $first, $operator, $second, 'right');
740 }
741
742 /**
743 * Add a "cross join" clause to the query.
744 *
745 * @param string $table
746 * @param \Closure|string|null $first
747 * @param string|null $operator
748 * @param string|null $second
749 * @return $this
750 */
751 public function crossJoin($table, $first = null, $operator = null, $second = null)
752 {
753 if ($first) {
754 return $this->join($table, $first, $operator, $second, 'cross');
755 }
756
757 $this->joins[] = $this->newJoinClause($this, 'cross', $table);
758
759 return $this;
760 }
761
762 /**
763 * Add a subquery cross join to the query.
764 *
765 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $query
766 * @param string $as
767 * @return $this
768 */
769 public function crossJoinSub($query, $as)
770 {
771 $this->grammar->addAlias($as);
772
773 [$query, $bindings] = $this->createSub($query);
774
775 $expression = '('.$query.') as '.$this->grammar->wrapTable($as);
776
777 $this->addBinding($bindings, 'join');
778
779 $this->joins[] = $this->newJoinClause($this, 'cross', new Expression($expression));
780
781 return $this;
782 }
783
784 /**
785 * Get a new join clause.
786 *
787 * @param \FluentCommunity\Framework\Database\Query\Builder $parentQuery
788 * @param string $type
789 * @param string $table
790 * @return \FluentCommunity\Framework\Database\Query\JoinClause
791 */
792 protected function newJoinClause(self $parentQuery, $type, $table)
793 {
794 if (stripos($table, ' as ') !== false) {
795 [$_, $as] = explode(' as ', $table);
796 $this->grammar->addAlias($as);
797 }
798
799 return new JoinClause($parentQuery, $type, $table);
800 }
801
802 /**
803 * Get a new join lateral clause.
804 *
805 * @param \FluentCommunity\Framework\Database\Query\Builder $parentQuery
806 * @param string $type
807 * @param string $table
808 * @return \FluentCommunity\Framework\Database\Query\JoinLateralClause
809 */
810 protected function newJoinLateralClause(self $parentQuery, $type, $table)
811 {
812 return new JoinLateralClause($parentQuery, $type, $table);
813 }
814
815 /**
816 * Merge an array of where clauses and bindings.
817 *
818 * @param array $wheres
819 * @param array $bindings
820 * @return $this
821 */
822 public function mergeWheres($wheres, $bindings)
823 {
824 $this->wheres = array_merge($this->wheres, (array) $wheres);
825
826 $this->bindings['where'] = array_values(
827 array_merge($this->bindings['where'], (array) $bindings)
828 );
829
830 return $this;
831 }
832
833 /**
834 * Add a basic where clause to the query.
835 *
836 * @param \Closure|string|array $column
837 * @param mixed $operator
838 * @param mixed $value
839 * @param string $boolean
840 * @return $this
841 */
842 public function where($column, $operator = null, $value = null, $boolean = 'and')
843 {
844 if ($column instanceof ConditionExpression) {
845 $type = 'Expression';
846
847 $this->wheres[] = compact('type', 'column', 'boolean');
848
849 return $this;
850 }
851
852 // If the column is an array, we will assume it is an array of key-value pairs
853 // and can add them each as a where clause. We will maintain the boolean we
854 // received when the method was called and pass it into the nested where.
855 if (is_array($column)) {
856 return $this->addArrayOfWheres($column, $boolean);
857 }
858
859 // Here we will make some assumptions about the operator. If only 2 values are
860 // passed to the method, we will assume that the operator is an equals sign
861 // and keep going. Otherwise, we'll require the operator to be passed in.
862 [$value, $operator] = $this->prepareValueAndOperator(
863 $value, $operator, func_num_args() === 2
864 );
865
866 // If the column is actually a Closure instance, we will assume the developer
867 // wants to begin a nested where statement which is wrapped in parentheses.
868 // We will add that Closure to the query and return back out immediately.
869 if ($column instanceof Closure && is_null($operator)) {
870 return $this->whereNested($column, $boolean);
871 }
872
873 // If the column is a Closure instance and there is an operator value, we will
874 // assume the developer wants to run a subquery and then compare the result
875 // of that subquery with the given value that was provided to the method.
876 if ($this->isQueryable($column) && ! is_null($operator)) {
877 [$sub, $bindings] = $this->createSub($column);
878
879 return $this->addBinding($bindings, 'where')
880 ->where(new Expression('('.$sub.')'), $operator, $value, $boolean);
881 }
882
883 // If the given operator is not found in the list of valid operators we will
884 // assume that the developer is just short-cutting the '=' operators and
885 // we will set the operators to '=' and set the values appropriately.
886 if ($this->invalidOperator($operator)) {
887 [$value, $operator] = [$operator, '='];
888 }
889
890 // If the value is a Closure, it means the developer is performing an entire
891 // sub-select within the query and we will need to compile the sub-select
892 // within the where clause to get the appropriate query record results.
893 if ($this->isQueryable($value)) {
894 return $this->whereSub($column, $operator, $value, $boolean);
895 }
896
897 // If the value is "null", we will just assume the developer wants to add a
898 // where null clause to the query. So, we will allow a short-cut here to
899 // that method for convenience so the developer doesn't have to check.
900 if (is_null($value)) {
901 return $this->whereNull($column, $boolean, $operator !== '=');
902 }
903
904 $type = 'Basic';
905
906 $columnString = ($column instanceof Expression)
907 ? $this->grammar->getValue($column)
908 : $column;
909
910 // If the column is making a JSON reference we'll check to see if the value
911 // is a boolean. If it is, we'll add the raw boolean string as an actual
912 // value to the query to ensure this is properly handled by the query.
913 if (str_contains($columnString, '->') && is_bool($value)) {
914 $value = new Expression($value ? 'true' : 'false');
915
916 if (is_string($column)) {
917 $type = 'JsonBoolean';
918 }
919 }
920
921 if ($this->isBitwiseOperator($operator)) {
922 $type = 'Bitwise';
923 }
924
925 // Now that we are working with just a simple query we can put the elements
926 // in our array and add the query binding to our array of bindings that
927 // will be bound to each SQL statements when it is finally executed.
928 $this->wheres[] = compact(
929 'type', 'column', 'operator', 'value', 'boolean'
930 );
931
932 if (! $value instanceof Expression) {
933 $this->addBinding($this->flattenValue($value), 'where');
934 }
935
936 return $this;
937 }
938
939 /**
940 * Add an array of where clauses to the query.
941 *
942 * @param array $column
943 * @param string $boolean
944 * @param string $method
945 * @return $this
946 */
947 protected function addArrayOfWheres($column, $boolean, $method = 'where')
948 {
949 return $this->whereNested(function ($query) use ($column, $method, $boolean) {
950 foreach ($column as $key => $value) {
951 if (is_numeric($key) && is_array($value)) {
952 $query->{$method}(...array_values($value));
953 } else {
954 $query->{$method}($key, '=', $value, $boolean);
955 }
956 }
957 }, $boolean);
958 }
959
960 /**
961 * Prepare the value and operator for a where clause.
962 *
963 * @param string $value
964 * @param string $operator
965 * @param bool $useDefault
966 * @return array
967 *
968 * @throws \InvalidArgumentException
969 */
970 public function prepareValueAndOperator($value, $operator, $useDefault = false)
971 {
972 if ($useDefault) {
973 return [$operator, '='];
974 } elseif ($this->invalidOperatorAndValue($operator, $value)) {
975 throw new InvalidArgumentException(
976 'Illegal operator and value combination.'
977 );
978 }
979
980 return [$value, $operator];
981 }
982
983 /**
984 * Determine if the given operator and value combination is legal.
985 *
986 * Prevents using Null values with invalid operators.
987 *
988 * @param string $operator
989 * @param mixed $value
990 * @return bool
991 */
992 protected function invalidOperatorAndValue($operator, $value)
993 {
994 return is_null($value) && in_array($operator, $this->operators) &&
995 ! in_array($operator, ['=', '<>', '!=']);
996 }
997
998 /**
999 * Determine if the given operator is supported.
1000 *
1001 * @param string $operator
1002 * @return bool
1003 */
1004 protected function invalidOperator($operator)
1005 {
1006 return ! is_string($operator) || (! in_array(strtolower($operator), $this->operators, true) &&
1007 ! in_array(strtolower($operator), $this->grammar->getOperators(), true));
1008 }
1009
1010 /**
1011 * Determine if the operator is a bitwise operator.
1012 *
1013 * @param string $operator
1014 * @return bool
1015 */
1016 protected function isBitwiseOperator($operator)
1017 {
1018 return in_array(strtolower($operator), $this->bitwiseOperators, true) ||
1019 in_array(strtolower($operator), $this->grammar->getBitwiseOperators(), true);
1020 }
1021
1022 /**
1023 * Add an "or where" clause to the query.
1024 *
1025 * @param \Closure|string|array $column
1026 * @param mixed $operator
1027 * @param mixed $value
1028 * @return $this
1029 */
1030 public function orWhere($column, $operator = null, $value = null)
1031 {
1032 [$value, $operator] = $this->prepareValueAndOperator(
1033 $value, $operator, func_num_args() === 2
1034 );
1035
1036 return $this->where($column, $operator, $value, 'or');
1037 }
1038
1039 /**
1040 * Add a basic "where not" clause to the query.
1041 *
1042 * @param \Closure|string|array|\FluentCommunity\Framework\Database\Query\Expression $column
1043 * @param mixed $operator
1044 * @param mixed $value
1045 * @param string $boolean
1046 * @return $this
1047 */
1048 public function whereNot(
1049 $column,
1050 $operator = null,
1051 $value = null,
1052 $boolean = 'and'
1053 ) {
1054 if (is_array($column)) {
1055 return $this->whereNested(function ($query) use ($column, $operator, $value, $boolean) {
1056 $query->where($column, $operator, $value, $boolean);
1057 }, $boolean.' not');
1058 }
1059
1060 return $this->where($column, $operator, $value, $boolean.' not');
1061 }
1062
1063 /**
1064 * Add an "or where not" clause to the query.
1065 *
1066 * @param \Closure|string|array|\FluentCommunity\Framework\Database\Query\Expression $column
1067 * @param mixed $operator
1068 * @param mixed $value
1069 * @return $this
1070 */
1071 public function orWhereNot($column, $operator = null, $value = null)
1072 {
1073 return $this->whereNot($column, $operator, $value, 'or');
1074 }
1075
1076 /**
1077 * Add a "where" clause comparing two columns to the query.
1078 *
1079 * @param string|array $first
1080 * @param string|null $operator
1081 * @param string|null $second
1082 * @param string|null $boolean
1083 * @return $this
1084 */
1085 public function whereColumn($first, $operator = null, $second = null, $boolean = 'and')
1086 {
1087 // If the column is an array, we will assume it is an array of key-value pairs
1088 // and can add them each as a where clause. We will maintain the boolean we
1089 // received when the method was called and pass it into the nested where.
1090 if (is_array($first)) {
1091 return $this->addArrayOfWheres($first, $boolean, 'whereColumn');
1092 }
1093
1094 // If the given operator is not found in the list of valid operators we will
1095 // assume that the developer is just short-cutting the '=' operators and
1096 // we will set the operators to '=' and set the values appropriately.
1097 if ($this->invalidOperator($operator)) {
1098 [$second, $operator] = [$operator, '='];
1099 }
1100
1101 // Finally, we will add this where clause into this array of clauses that we
1102 // are building for the query. All of them will be compiled via a grammar
1103 // once the query is about to be executed and run against the database.
1104 $type = 'Column';
1105
1106 $this->wheres[] = compact(
1107 'type', 'first', 'operator', 'second', 'boolean'
1108 );
1109
1110 return $this;
1111 }
1112
1113 /**
1114 * Add an "or where" clause comparing two columns to the query.
1115 *
1116 * @param string|array $first
1117 * @param string|null $operator
1118 * @param string|null $second
1119 * @return $this
1120 */
1121 public function orWhereColumn($first, $operator = null, $second = null)
1122 {
1123 return $this->whereColumn($first, $operator, $second, 'or');
1124 }
1125
1126 /**
1127 * Add a raw where clause to the query.
1128 *
1129 * @param string $sql
1130 * @param mixed $bindings
1131 * @param string $boolean
1132 * @return $this
1133 */
1134 public function whereRaw($sql, $bindings = [], $boolean = 'and')
1135 {
1136 $this->wheres[] = ['type' => 'raw', 'sql' => $sql, 'boolean' => $boolean];
1137
1138 $this->addBinding((array) $bindings, 'where');
1139
1140 return $this;
1141 }
1142
1143 /**
1144 * Add a raw or where clause to the query.
1145 *
1146 * @param string $sql
1147 * @param mixed $bindings
1148 * @return $this
1149 */
1150 public function orWhereRaw($sql, $bindings = [])
1151 {
1152 return $this->whereRaw($sql, $bindings, 'or');
1153 }
1154
1155 /**
1156 * Add a "where like" clause to the query.
1157 *
1158 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1159 * @param string $value
1160 * @param bool $caseSensitive
1161 * @param string $boolean
1162 * @param bool $not
1163 * @return $this
1164 */
1165 public function whereLike(
1166 $column,
1167 $value,
1168 $caseSensitive = false,
1169 $boolean = 'and',
1170 $not = false
1171 ) {
1172 $type = 'Like';
1173
1174 $this->wheres[] = compact(
1175 'type', 'column', 'value', 'caseSensitive', 'boolean', 'not'
1176 );
1177
1178 if (method_exists($this->grammar, 'prepareWhereLikeBinding')) {
1179 $value = $this->grammar->prepareWhereLikeBinding(
1180 $value, $caseSensitive
1181 );
1182 }
1183
1184 if (!str_contains($value, '%')) {
1185 $value = '%'.$value.'%';
1186 }
1187
1188 $this->addBinding($value);
1189
1190 return $this;
1191 }
1192
1193 /**
1194 * Add an "or where like" clause to the query.
1195 *
1196 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1197 * @param string $value
1198 * @param bool $caseSensitive
1199 * @return $this
1200 */
1201 public function orWhereLike($column, $value, $caseSensitive = false)
1202 {
1203 return $this->whereLike($column, $value, $caseSensitive, 'or', false);
1204 }
1205
1206 /**
1207 * Add a "where not like" clause to the query.
1208 *
1209 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1210 * @param string $value
1211 * @param bool $caseSensitive
1212 * @param string $boolean
1213 * @return $this
1214 */
1215 public function whereNotLike(
1216 $column,
1217 $value,
1218 $caseSensitive = false,
1219 $boolean = 'and'
1220 ) {
1221 return $this->whereLike($column, $value, $caseSensitive, $boolean, true);
1222 }
1223
1224 /**
1225 * Add an "or where not like" clause to the query.
1226 *
1227 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1228 * @param string $value
1229 * @param bool $caseSensitive
1230 * @return $this
1231 */
1232 public function orWhereNotLike($column, $value, $caseSensitive = false)
1233 {
1234 return $this->whereNotLike($column, $value, $caseSensitive, 'or');
1235 }
1236
1237 /**
1238 * Add a "where like" clause to the query.
1239 *
1240 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1241 * @param string $value
1242 * @param bool $caseSensitive
1243 * @param string $boolean
1244 * @param bool $not
1245 * @return $this
1246 */
1247 public function whereStartsLike(
1248 $column,
1249 $value,
1250 $caseSensitive = false,
1251 $boolean = 'and',
1252 $not = false
1253 ) {
1254 return $this->whereLike(
1255 $column, $value.'%', $caseSensitive, $boolean, $not
1256 );
1257 }
1258
1259 /**
1260 * Add a "where like" clause to the query.
1261 *
1262 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1263 * @param string $value
1264 * @param bool $caseSensitive
1265 * @param string $boolean
1266 * @param bool $not
1267 * @return $this
1268 */
1269 public function whereEndsLike(
1270 $column,
1271 $value,
1272 $caseSensitive = false,
1273 $boolean = 'and',
1274 $not = false
1275 ) {
1276 return $this->whereLike(
1277 $column, '%'.$value, $caseSensitive, $boolean, $not
1278 );
1279 }
1280
1281 /**
1282 * Add a phonetic "sounds like" (SOUNDEX) clause to the query.
1283 *
1284 * Matches values pronounced similarly, e.g. searching "heera" matches
1285 * "hira"/"hera", "bol" matches "ball", "cloud" matches "claude".
1286 *
1287 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1288 * @param string $value
1289 * @param string $boolean
1290 * @return $this
1291 */
1292 public function whereSoundsLike($column, $value, $boolean = 'and')
1293 {
1294 return $this->whereRaw(
1295 $this->grammar->compileSoundsLike($column),
1296 [$this->grammar->prepareSoundsLikeBinding($value)],
1297 $boolean
1298 );
1299 }
1300
1301 /**
1302 * Add an "or" phonetic "sounds like" clause to the query.
1303 *
1304 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1305 * @param string $value
1306 * @return $this
1307 */
1308 public function orWhereSoundsLike($column, $value)
1309 {
1310 return $this->whereSoundsLike($column, $value, 'or');
1311 }
1312
1313 /**
1314 * Add a "where in" clause to the query.
1315 *
1316 * @param string $column
1317 * @param mixed $values
1318 * @param string $boolean
1319 * @param bool $not
1320 * @return $this
1321 */
1322 public function whereIn($column, $values, $boolean = 'and', $not = false)
1323 {
1324 $type = $not ? 'NotIn' : 'In';
1325
1326 // If the value is a query builder instance we will assume the developer wants to
1327 // look for any values that exists within this given query. So we will add the
1328 // query accordingly so that this query is properly executed when it is run.
1329 if ($this->isQueryable($values)) {
1330 [$query, $bindings] = $this->createSub($values);
1331
1332 $values = [new Expression($query)];
1333
1334 $this->addBinding($bindings, 'where');
1335 }
1336
1337 // Next, if the value is ArrayableInterface we need to cast it to its raw
1338 // array form so we have the underlying array value instead of an
1339 // Arrayable object which is not able to be added as a binding,
1340 // etc. We will then add to the wheres array.
1341 if ($values instanceof ArrayableInterface) {
1342 $values = $values->toArray();
1343 }
1344
1345 $this->wheres[] = compact('type', 'column', 'values', 'boolean');
1346
1347 if (count($values) !== count(Arr::flatten($values, 1))) {
1348 throw new InvalidArgumentException('Nested arrays may not be passed to whereIn method.');
1349 }
1350
1351 // Finally, we'll add a binding for each value unless that value is an
1352 // expression in which case we will just skip over it since it will
1353 // be the query as a raw string and not as a parameterized
1354 // place-holder to be replaced by the PDO.
1355 $this->addBinding($this->cleanBindings($values), 'where');
1356
1357 return $this;
1358 }
1359
1360 /**
1361 * Add an "or where in" clause to the query.
1362 *
1363 * @param string $column
1364 * @param mixed $values
1365 * @return $this
1366 */
1367 public function orWhereIn($column, $values)
1368 {
1369 return $this->whereIn($column, $values, 'or');
1370 }
1371
1372 /**
1373 * Add a "where not in" clause to the query.
1374 *
1375 * @param string $column
1376 * @param mixed $values
1377 * @param string $boolean
1378 * @return $this
1379 */
1380 public function whereNotIn($column, $values, $boolean = 'and')
1381 {
1382 return $this->whereIn($column, $values, $boolean, true);
1383 }
1384
1385 /**
1386 * Add an "or where not in" clause to the query.
1387 *
1388 * @param string $column
1389 * @param mixed $values
1390 * @return $this
1391 */
1392 public function orWhereNotIn($column, $values)
1393 {
1394 return $this->whereNotIn($column, $values, 'or');
1395 }
1396
1397 /**
1398 * Add a "where in raw" clause for integer values to the query.
1399 *
1400 * @param string $column
1401 * @param \FluentCommunity\Framework\Support\ArrayableInterface|array $values
1402 * @param string $boolean
1403 * @param bool $not
1404 * @return $this
1405 */
1406 public function whereIntegerInRaw($column, $values, $boolean = 'and', $not = false)
1407 {
1408 $type = $not ? 'NotInRaw' : 'InRaw';
1409
1410 if ($values instanceof ArrayableInterface) {
1411 $values = $values->toArray();
1412 }
1413
1414 $values = Arr::flatten($values);
1415
1416 foreach ($values as &$value) {
1417 if (class_exists('BackedEnum') && $value instanceof \BackedEnum) {
1418 $value = (int) $value->value;
1419 } else {
1420 $value = (int) $value;
1421 }
1422 }
1423
1424 $this->wheres[] = compact('type', 'column', 'values', 'boolean');
1425
1426 return $this;
1427 }
1428
1429 /**
1430 * Add an "or where in raw" clause for integer values to the query.
1431 *
1432 * @param string $column
1433 * @param \FluentCommunity\Framework\Support\ArrayableInterface|array $values
1434 * @return $this
1435 */
1436 public function orWhereIntegerInRaw($column, $values)
1437 {
1438 return $this->whereIntegerInRaw($column, $values, 'or');
1439 }
1440
1441 /**
1442 * Add a "where not in raw" clause for integer values to the query.
1443 *
1444 * @param string $column
1445 * @param \FluentCommunity\Framework\Support\ArrayableInterface|array $values
1446 * @param string $boolean
1447 * @return $this
1448 */
1449 public function whereIntegerNotInRaw($column, $values, $boolean = 'and')
1450 {
1451 return $this->whereIntegerInRaw($column, $values, $boolean, true);
1452 }
1453
1454 /**
1455 * Add an "or where not in raw" clause for integer values to the query.
1456 *
1457 * @param string $column
1458 * @param \FluentCommunity\Framework\Support\ArrayableInterface|array $values
1459 * @return $this
1460 */
1461 public function orWhereIntegerNotInRaw($column, $values)
1462 {
1463 return $this->whereIntegerNotInRaw($column, $values, 'or');
1464 }
1465
1466 /**
1467 * Add a "where null" clause to the query.
1468 *
1469 * @param string|array $columns
1470 * @param string $boolean
1471 * @param bool $not
1472 * @return $this
1473 */
1474 public function whereNull($columns, $boolean = 'and', $not = false)
1475 {
1476 $type = $not ? 'NotNull' : 'Null';
1477
1478 foreach (Arr::wrap($columns) as $column) {
1479 $this->wheres[] = compact('type', 'column', 'boolean');
1480 }
1481
1482 return $this;
1483 }
1484
1485 /**
1486 * Add an "or where null" clause to the query.
1487 *
1488 * @param string|array $column
1489 * @return $this
1490 */
1491 public function orWhereNull($column)
1492 {
1493 return $this->whereNull($column, 'or');
1494 }
1495
1496 /**
1497 * Add a "where not null" clause to the query.
1498 *
1499 * @param string|array $columns
1500 * @param string $boolean
1501 * @return $this
1502 */
1503 public function whereNotNull($columns, $boolean = 'and')
1504 {
1505 return $this->whereNull($columns, $boolean, true);
1506 }
1507
1508 /**
1509 * Add a where between statement to the query.
1510 *
1511 * @param string|\FluentCommunity\Framework\Database\Query\Expression $column
1512 * @param array $values
1513 * @param string $boolean
1514 * @param bool $not
1515 * @return $this
1516 */
1517 public function whereBetween($column, array $values, $boolean = 'and', $not = false)
1518 {
1519 $type = 'between';
1520
1521 $type = 'between';
1522
1523 if ($values instanceof DatePeriod) {
1524 $values = [$values->getStartDate(), $values->getEndDate()];
1525 }
1526
1527 $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not');
1528
1529 $this->addBinding(
1530 array_slice(
1531 $this->cleanBindings(Arr::flatten($values)), 0, 2
1532 ), 'where'
1533 );
1534
1535 return $this;
1536 }
1537
1538 /**
1539 * Add a where between statement using columns to the query.
1540 *
1541 * @param string $column
1542 * @param array $values
1543 * @param string $boolean
1544 * @param bool $not
1545 * @return $this
1546 */
1547 public function whereBetweenColumns(
1548 $column,
1549 array $values,
1550 $boolean = 'and',
1551 $not = false
1552 ) {
1553 $type = 'betweenColumns';
1554
1555 $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not');
1556
1557 return $this;
1558 }
1559
1560 /**
1561 * Add an or where between statement to the query.
1562 *
1563 * @param string $column
1564 * @param array $values
1565 * @return $this
1566 */
1567 public function orWhereBetween($column, array $values)
1568 {
1569 return $this->whereBetween($column, $values, 'or');
1570 }
1571
1572 /**
1573 * Add an or where between statement using columns to the query.
1574 *
1575 * @param string $column
1576 * @param array $values
1577 * @return $this
1578 */
1579 public function orWhereBetweenColumns($column, array $values)
1580 {
1581 return $this->whereBetweenColumns($column, $values, 'or');
1582 }
1583
1584 /**
1585 * Add a where not between statement to the query.
1586 *
1587 * @param string $column
1588 * @param array $values
1589 * @param string $boolean
1590 * @return $this
1591 */
1592 public function whereNotBetween($column, iterable $values, $boolean = 'and')
1593 {
1594 return $this->whereBetween($column, $values, $boolean, true);
1595 }
1596
1597 /**
1598 * Add a where not between statement using columns to the query.
1599 *
1600 * @param string $column
1601 * @param array $values
1602 * @param string $boolean
1603 * @return $this
1604 */
1605 public function whereNotBetweenColumns($column, array $values, $boolean = 'and')
1606 {
1607 return $this->whereBetweenColumns($column, $values, $boolean, true);
1608 }
1609
1610 /**
1611 * Add an or where not between statement to the query.
1612 *
1613 * @param string $column
1614 * @param array $values
1615 * @return $this
1616 */
1617 public function orWhereNotBetween($column, iterable $values)
1618 {
1619 return $this->whereNotBetween($column, $values, 'or');
1620 }
1621
1622 /**
1623 * Add an or where not between statement using columns to the query.
1624 *
1625 * @param string $column
1626 * @param array $values
1627 * @return $this
1628 */
1629 public function orWhereNotBetweenColumns($column, array $values)
1630 {
1631 return $this->whereNotBetweenColumns($column, $values, 'or');
1632 }
1633
1634 /**
1635 * Add an "or where not null" clause to the query.
1636 *
1637 * @param string $column
1638 * @return $this
1639 */
1640 public function orWhereNotNull($column)
1641 {
1642 return $this->whereNotNull($column, 'or');
1643 }
1644
1645 /**
1646 * Add a "where date" statement to the query.
1647 *
1648 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1649 * @param string $operator
1650 * @param \DateTimeInterface|string|null $value
1651 * @param string $boolean
1652 * @return $this
1653 */
1654 public function whereDate($column, $operator, $value = null, $boolean = 'and')
1655 {
1656 [$value, $operator] = $this->prepareValueAndOperator(
1657 $value, $operator, func_num_args() === 2
1658 );
1659
1660 $value = $this->flattenValue($value);
1661
1662 if ($value instanceof DateTimeInterface) {
1663 $value = $value->format('Y-m-d');
1664 }
1665
1666 return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean);
1667 }
1668
1669 /**
1670 * Add an "or where date" statement to the query.
1671 *
1672 * @param string $column
1673 * @param string $operator
1674 * @param \DateTimeInterface|string|null $value
1675 * @return $this
1676 */
1677 public function orWhereDate($column, $operator, $value = null)
1678 {
1679 [$value, $operator] = $this->prepareValueAndOperator(
1680 $value, $operator, func_num_args() === 2
1681 );
1682
1683 return $this->whereDate($column, $operator, $value, 'or');
1684 }
1685
1686 /**
1687 * Add a "where time" statement to the query.
1688 *
1689 * @param string $column
1690 * @param string $operator
1691 * @param \DateTimeInterface|string|null $value
1692 * @param string $boolean
1693 * @return $this
1694 */
1695 public function whereTime($column, $operator, $value = null, $boolean = 'and')
1696 {
1697 [$value, $operator] = $this->prepareValueAndOperator(
1698 $value, $operator, func_num_args() === 2
1699 );
1700
1701 $value = $this->flattenValue($value);
1702
1703 if ($value instanceof DateTimeInterface) {
1704 $value = $value->format('H:i:s');
1705 }
1706
1707 return $this->addDateBasedWhere('Time', $column, $operator, $value, $boolean);
1708 }
1709
1710 /**
1711 * Add an "or where time" statement to the query.
1712 *
1713 * @param string $column
1714 * @param string $operator
1715 * @param \DateTimeInterface|string|null $value
1716 * @return $this
1717 */
1718 public function orWhereTime($column, $operator, $value = null)
1719 {
1720 [$value, $operator] = $this->prepareValueAndOperator(
1721 $value, $operator, func_num_args() === 2
1722 );
1723
1724 return $this->whereTime($column, $operator, $value, 'or');
1725 }
1726
1727 /**
1728 * Add a "where day" statement to the query.
1729 *
1730 * @param string $column
1731 * @param string $operator
1732 * @param \DateTimeInterface|string|null $value
1733 * @param string $boolean
1734 * @return $this
1735 */
1736 public function whereDay($column, $operator, $value = null, $boolean = 'and')
1737 {
1738 [$value, $operator] = $this->prepareValueAndOperator(
1739 $value, $operator, func_num_args() === 2
1740 );
1741
1742 $value = $this->flattenValue($value);
1743
1744 if ($value instanceof DateTimeInterface) {
1745 $value = $value->format('d');
1746 }
1747
1748 if (! $value instanceof Expression) {
1749 $value = sprintf('%02d', $value);
1750 }
1751
1752 return $this->addDateBasedWhere('Day', $column, $operator, $value, $boolean);
1753 }
1754
1755 /**
1756 * Add an "or where day" statement to the query.
1757 *
1758 * @param string $column
1759 * @param string $operator
1760 * @param \DateTimeInterface|string|null $value
1761 * @return $this
1762 */
1763 public function orWhereDay($column, $operator, $value = null)
1764 {
1765 [$value, $operator] = $this->prepareValueAndOperator(
1766 $value, $operator, func_num_args() === 2
1767 );
1768
1769 return $this->whereDay($column, $operator, $value, 'or');
1770 }
1771
1772 /**
1773 * Add a "where month" statement to the query.
1774 *
1775 * @param string $column
1776 * @param string $operator
1777 * @param \DateTimeInterface|string|null $value
1778 * @param string $boolean
1779 * @return $this
1780 */
1781 public function whereMonth($column, $operator, $value = null, $boolean = 'and')
1782 {
1783 [$value, $operator] = $this->prepareValueAndOperator(
1784 $value, $operator, func_num_args() === 2
1785 );
1786
1787 $value = $this->flattenValue($value);
1788
1789 if ($value instanceof DateTimeInterface) {
1790 $value = $value->format('m');
1791 }
1792
1793 if (! $value instanceof Expression) {
1794 $value = sprintf('%02d', $value);
1795 }
1796
1797 return $this->addDateBasedWhere('Month', $column, $operator, $value, $boolean);
1798 }
1799
1800 /**
1801 * Add an "or where month" statement to the query.
1802 *
1803 * @param string $column
1804 * @param string $operator
1805 * @param \DateTimeInterface|string|null $value
1806 * @return $this
1807 */
1808 public function orWhereMonth($column, $operator, $value = null)
1809 {
1810 [$value, $operator] = $this->prepareValueAndOperator(
1811 $value, $operator, func_num_args() === 2
1812 );
1813
1814 return $this->whereMonth($column, $operator, $value, 'or');
1815 }
1816
1817 /**
1818 * Add a "where year" statement to the query.
1819 *
1820 * @param string $column
1821 * @param string $operator
1822 * @param \DateTimeInterface|string|int|null $value
1823 * @param string $boolean
1824 * @return $this
1825 */
1826 public function whereYear($column, $operator, $value = null, $boolean = 'and')
1827 {
1828 [$value, $operator] = $this->prepareValueAndOperator(
1829 $value, $operator, func_num_args() === 2
1830 );
1831
1832 $value = $this->flattenValue($value);
1833
1834 if ($value instanceof DateTimeInterface) {
1835 $value = $value->format('Y');
1836 }
1837
1838 return $this->addDateBasedWhere('Year', $column, $operator, $value, $boolean);
1839 }
1840
1841 /**
1842 * Add an "or where year" statement to the query.
1843 *
1844 * @param string $column
1845 * @param string $operator
1846 * @param \DateTimeInterface|string|int|null $value
1847 * @return $this
1848 */
1849 public function orWhereYear($column, $operator, $value = null)
1850 {
1851 [$value, $operator] = $this->prepareValueAndOperator(
1852 $value, $operator, func_num_args() === 2
1853 );
1854
1855 return $this->whereYear($column, $operator, $value, 'or');
1856 }
1857
1858 /**
1859 * Add a date based (year, month, day, time) statement to the query.
1860 *
1861 * @param string $type
1862 * @param string $column
1863 * @param string $operator
1864 * @param mixed $value
1865 * @param string $boolean
1866 * @return $this
1867 */
1868 protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and')
1869 {
1870 $this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value');
1871
1872 if (! $value instanceof Expression) {
1873 $this->addBinding($value, 'where');
1874 }
1875
1876 return $this;
1877 }
1878
1879 /**
1880 * Add a nested where statement to the query.
1881 *
1882 * @param \Closure $callback
1883 * @param string $boolean
1884 * @return $this
1885 */
1886 public function whereNested(Closure $callback, $boolean = 'and')
1887 {
1888 $callback($query = $this->forNestedWhere());
1889
1890 return $this->addNestedWhereQuery($query, $boolean);
1891 }
1892
1893 /**
1894 * Create a new query instance for nested where condition.
1895 *
1896 * @return \FluentCommunity\Framework\Database\Query\Builder
1897 */
1898 public function forNestedWhere()
1899 {
1900 return $this->newQuery()->from($this->from);
1901 }
1902
1903 /**
1904 * Add another query builder as a nested where to the query builder.
1905 *
1906 * @param \FluentCommunity\Framework\Database\Query\Builder $query
1907 * @param string $boolean
1908 * @return $this
1909 */
1910 public function addNestedWhereQuery($query, $boolean = 'and')
1911 {
1912 if (count($query->wheres)) {
1913 $type = 'Nested';
1914
1915 $this->wheres[] = compact('type', 'query', 'boolean');
1916
1917 $this->addBinding($query->getRawBindings()['where'], 'where');
1918 }
1919
1920 return $this;
1921 }
1922
1923 /**
1924 * Add a full sub-select to the query.
1925 *
1926 * @param string $column
1927 * @param string $operator
1928 * @param \Closure $callback
1929 * @param string $boolean
1930 * @return $this
1931 */
1932 protected function whereSub($column, $operator, $callback, $boolean)
1933 {
1934 $type = 'Sub';
1935
1936 if ($callback instanceof Closure) {
1937 // Once we have the query instance we can simply execute it so it can add all
1938 // of the sub-select's conditions to itself, and then we can cache it off
1939 // in the array of where clauses for the "main" parent query instance.
1940 $callback($query = $this->forSubQuery());
1941 } else {
1942 $query = $callback instanceof OrmBuilder ? $callback->toBase() : $callback;
1943 }
1944
1945 $this->wheres[] = compact(
1946 'type', 'column', 'operator', 'query', 'boolean'
1947 );
1948
1949 $this->addBinding($query->getBindings(), 'where');
1950
1951 return $this;
1952 }
1953
1954 /**
1955 * Add an exists clause to the query.
1956 *
1957 * @param \Closure $callback
1958 * @param string $boolean
1959 * @param bool $not
1960 * @return $this
1961 */
1962 public function whereExists(Closure $callback, $boolean = 'and', $not = false)
1963 {
1964 if ($callback instanceof Closure) {
1965 $query = $this->forSubQuery();
1966
1967 // Similar to the sub-select clause, we will create a new query instance so
1968 // the developer may cleanly specify the entire exists query and we will
1969 // compile the whole thing in the grammar and insert it into the SQL.
1970 $callback($query);
1971 } else {
1972 $query = $callback instanceof OrmBuilder ? $callback->toBase() : $callback;
1973 }
1974
1975 return $this->addWhereExistsQuery($query, $boolean, $not);
1976 }
1977
1978 /**
1979 * Add an or exists clause to the query.
1980 *
1981 * @param \Closure $callback
1982 * @param bool $not
1983 * @return $this
1984 */
1985 public function orWhereExists($callback, $not = false)
1986 {
1987 return $this->whereExists($callback, 'or', $not);
1988 }
1989
1990 /**
1991 * Add a where not exists clause to the query.
1992 *
1993 * @param \Closure $callback
1994 * @param string $boolean
1995 * @return $this
1996 */
1997 public function whereNotExists($callback, $boolean = 'and')
1998 {
1999 return $this->whereExists($callback, $boolean, true);
2000 }
2001
2002 /**
2003 * Add a where not exists clause to the query.
2004 *
2005 * @param \Closure $callback
2006 * @return $this
2007 */
2008 public function orWhereNotExists($callback)
2009 {
2010 return $this->orWhereExists($callback, true);
2011 }
2012
2013 /**
2014 * Add an exists clause to the query.
2015 *
2016 * @param \FluentCommunity\Framework\Database\Query\Builder $query
2017 * @param string $boolean
2018 * @param bool $not
2019 * @return $this
2020 */
2021 public function addWhereExistsQuery(self $query, $boolean = 'and', $not = false)
2022 {
2023 $type = $not ? 'NotExists' : 'Exists';
2024
2025 $this->wheres[] = compact('type', 'query', 'boolean');
2026
2027 $this->addBinding($query->getBindings(), 'where');
2028
2029 return $this;
2030 }
2031
2032 /**
2033 * Adds a where condition using row values.
2034 *
2035 * @param array $columns
2036 * @param string $operator
2037 * @param array $values
2038 * @param string $boolean
2039 * @return $this
2040 *
2041 * @throws \InvalidArgumentException
2042 */
2043 public function whereRowValues($columns, $operator, $values, $boolean = 'and')
2044 {
2045 if (count($columns) !== count($values)) {
2046 throw new InvalidArgumentException('The number of columns must match the number of values');
2047 }
2048
2049 $type = 'RowValues';
2050
2051 $this->wheres[] = compact('type', 'columns', 'operator', 'values', 'boolean');
2052
2053 $this->addBinding($this->cleanBindings($values));
2054
2055 return $this;
2056 }
2057
2058 /**
2059 * Adds an or where condition using row values.
2060 *
2061 * @param array $columns
2062 * @param string $operator
2063 * @param array $values
2064 * @return $this
2065 */
2066 public function orWhereRowValues($columns, $operator, $values)
2067 {
2068 return $this->whereRowValues($columns, $operator, $values, 'or');
2069 }
2070
2071 /**
2072 * Add a "where JSON contains" clause to the query.
2073 *
2074 * @param string $column
2075 * @param mixed $value
2076 * @param string $boolean
2077 * @param bool $not
2078 * @return $this
2079 */
2080 public function whereJsonContains($column, $value, $boolean = 'and', $not = false)
2081 {
2082 $type = 'JsonContains';
2083
2084 $this->wheres[] = compact('type', 'column', 'value', 'boolean', 'not');
2085
2086 if (! $value instanceof Expression) {
2087 $this->addBinding($this->grammar->prepareBindingForJsonContains($value));
2088 }
2089
2090 return $this;
2091 }
2092
2093 /**
2094 * Add an "or where JSON contains" clause to the query.
2095 *
2096 * @param string $column
2097 * @param mixed $value
2098 * @return $this
2099 */
2100 public function orWhereJsonContains($column, $value)
2101 {
2102 return $this->whereJsonContains($column, $value, 'or');
2103 }
2104
2105 /**
2106 * Add a "where JSON not contains" clause to the query.
2107 *
2108 * @param string $column
2109 * @param mixed $value
2110 * @param string $boolean
2111 * @return $this
2112 */
2113 public function whereJsonDoesntContain($column, $value, $boolean = 'and')
2114 {
2115 return $this->whereJsonContains($column, $value, $boolean, true);
2116 }
2117
2118 /**
2119 * Add an "or where JSON not contains" clause to the query.
2120 *
2121 * @param string $column
2122 * @param mixed $value
2123 * @return $this
2124 */
2125 public function orWhereJsonDoesntContain($column, $value)
2126 {
2127 return $this->whereJsonDoesntContain($column, $value, 'or');
2128 }
2129
2130 /**
2131 * Add a "where JSON overlaps" clause to the query.
2132 *
2133 * @param string $column
2134 * @param mixed $value
2135 * @param string $boolean
2136 * @param bool $not
2137 * @return $this
2138 */
2139 public function whereJsonOverlaps($column, $value, $boolean = 'and', $not = false)
2140 {
2141 $type = 'JsonOverlaps';
2142
2143 $this->wheres[] = compact('type', 'column', 'value', 'boolean', 'not');
2144
2145 if (! $value instanceof Expression) {
2146 $this->addBinding($this->grammar->prepareBindingForJsonContains($value));
2147 }
2148
2149 return $this;
2150 }
2151
2152 /**
2153 * Add an "or where JSON overlaps" clause to the query.
2154 *
2155 * @param string $column
2156 * @param mixed $value
2157 * @return $this
2158 */
2159 public function orWhereJsonOverlaps($column, $value)
2160 {
2161 return $this->whereJsonOverlaps($column, $value, 'or');
2162 }
2163
2164 /**
2165 * Add a "where JSON not overlap" clause to the query.
2166 *
2167 * @param string $column
2168 * @param mixed $value
2169 * @param string $boolean
2170 * @return $this
2171 */
2172 public function whereJsonDoesntOverlap($column, $value, $boolean = 'and')
2173 {
2174 return $this->whereJsonOverlaps($column, $value, $boolean, true);
2175 }
2176
2177 /**
2178 * Add an "or where JSON not overlap" clause to the query.
2179 *
2180 * @param string $column
2181 * @param mixed $value
2182 * @return $this
2183 */
2184 public function orWhereJsonDoesntOverlap($column, $value)
2185 {
2186 return $this->whereJsonDoesntOverlap($column, $value, 'or');
2187 }
2188
2189 /**
2190 * Add a clause that determines if a JSON path exists to the query.
2191 *
2192 * @param string $column
2193 * @param string $boolean
2194 * @param bool $not
2195 * @return $this
2196 */
2197 public function whereJsonContainsKey($column, $boolean = 'and', $not = false)
2198 {
2199 $type = 'JsonContainsKey';
2200
2201 $this->wheres[] = compact('type', 'column', 'boolean', 'not');
2202
2203 return $this;
2204 }
2205
2206 /**
2207 * Add an "or" clause that determines if a JSON path exists to the query.
2208 *
2209 * @param string $column
2210 * @return $this
2211 */
2212 public function orWhereJsonContainsKey($column)
2213 {
2214 return $this->whereJsonContainsKey($column, 'or');
2215 }
2216
2217 /**
2218 * Add a clause that determines if a JSON path does not exist to the query.
2219 *
2220 * @param string $column
2221 * @param string $boolean
2222 * @return $this
2223 */
2224 public function whereJsonDoesntContainKey($column, $boolean = 'and')
2225 {
2226 return $this->whereJsonContainsKey($column, $boolean, true);
2227 }
2228
2229 /**
2230 * Add an "or" clause that determines if a JSON path does not exist to the query.
2231 *
2232 * @param string $column
2233 * @return $this
2234 */
2235 public function orWhereJsonDoesntContainKey($column)
2236 {
2237 return $this->whereJsonDoesntContainKey($column, 'or');
2238 }
2239
2240 /**
2241 * Add a "where JSON length" clause to the query.
2242 *
2243 * @param string $column
2244 * @param mixed $operator
2245 * @param mixed $value
2246 * @param string $boolean
2247 * @return $this
2248 */
2249 public function whereJsonLength(
2250 $column,
2251 $operator,
2252 $value = null,
2253 $boolean = 'and'
2254 ) {
2255 $type = 'JsonLength';
2256
2257 [$value, $operator] = $this->prepareValueAndOperator(
2258 $value, $operator, func_num_args() === 2
2259 );
2260
2261 $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
2262
2263 if (! $value instanceof Expression) {
2264 $this->addBinding((int) $this->flattenValue($value));
2265 }
2266
2267 return $this;
2268 }
2269
2270 /**
2271 * Add an "or where JSON length" clause to the query.
2272 *
2273 * @param string $column
2274 * @param mixed $operator
2275 * @param mixed $value
2276 * @return $this
2277 */
2278 public function orWhereJsonLength($column, $operator, $value = null)
2279 {
2280 [$value, $operator] = $this->prepareValueAndOperator(
2281 $value, $operator, func_num_args() === 2
2282 );
2283
2284 return $this->whereJsonLength($column, $operator, $value, 'or');
2285 }
2286
2287 /**
2288 * Handles dynamic "where" clauses to the query.
2289 *
2290 * @param string $method
2291 * @param array $parameters
2292 * @return $this
2293 */
2294 public function dynamicWhere($method, $parameters)
2295 {
2296 $finder = substr($method, 5);
2297
2298 $segments = preg_split(
2299 '/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE
2300 );
2301
2302 // The connector variable will determine which connector will be used for the
2303 // query condition. We will change it as we come across new boolean values
2304 // in the dynamic method strings, which could contain a number of these.
2305 $connector = 'and';
2306
2307 $index = 0;
2308
2309 foreach ($segments as $segment) {
2310 // If the segment is not a boolean connector, we can assume it is a column's name
2311 // and we will add it to the query as a new constraint as a where clause, then
2312 // we can keep iterating through the dynamic method string's segments again.
2313 if ($segment !== 'And' && $segment !== 'Or') {
2314 $this->addDynamic($segment, $connector, $parameters, $index);
2315
2316 $index++;
2317 }
2318
2319 // Otherwise, we will store the connector so we know how the next where clause we
2320 // find in the query should be connected to the previous ones, meaning we will
2321 // have the proper boolean connector to connect the next where clause found.
2322 else {
2323 $connector = $segment;
2324 }
2325 }
2326
2327 return $this;
2328 }
2329
2330 /**
2331 * Add a single dynamic where clause statement to the query.
2332 *
2333 * @param string $segment
2334 * @param string $connector
2335 * @param array $parameters
2336 * @param int $index
2337 * @return void
2338 */
2339 protected function addDynamic($segment, $connector, $parameters, $index)
2340 {
2341 // Once we have parsed out the columns and formatted the boolean operators we
2342 // are ready to add it to this query as a where clause just like any other
2343 // clause on the query. Then we'll increment the parameter index values.
2344 $bool = strtolower($connector);
2345
2346 $this->where(Str::snake($segment), '=', $parameters[$index], $bool);
2347 }
2348
2349 /**
2350 * Add a "where fulltext" clause to the query.
2351 *
2352 * @param string|string[] $columns
2353 * @param string $value
2354 * @param string $boolean
2355 * @return $this
2356 */
2357 public function whereFullText(
2358 $columns,
2359 $value,
2360 array $options = [],
2361 $boolean = 'and'
2362 ) {
2363 $type = 'Fulltext';
2364
2365 $columns = (array) $columns;
2366
2367 $this->wheres[] = compact('type', 'columns', 'value', 'options', 'boolean');
2368
2369 $this->addBinding($value);
2370
2371 return $this;
2372 }
2373
2374 /**
2375 * Add a "or where fulltext" clause to the query.
2376 *
2377 * @param string|string[] $columns
2378 * @param string $value
2379 * @return $this
2380 */
2381 public function orWhereFullText($columns, $value, array $options = [])
2382 {
2383 return $this->whereFulltext($columns, $value, $options, 'or');
2384 }
2385
2386 /**
2387 * Add a full-text relevance score to the select clause.
2388 *
2389 * Compiles a "MATCH ... AGAINST" expression aliased as the given name so
2390 * results can be ordered by how well they match. A flat list of columns
2391 * produces a single composite match; an associative array of
2392 * column => weight produces a weighted per-column sum, e.g.
2393 * ['title' => 3, 'body' => 1].
2394 *
2395 * @param string|array $columns
2396 * @param string $value
2397 * @param array $options
2398 * @param string $as
2399 * @return $this
2400 */
2401 public function selectRelevance($columns, $value, array $options = [], $as = 'relevance')
2402 {
2403 [$sql, $bindings] = $this->grammar->compileRelevance(
2404 (array) $columns, $options, $value
2405 );
2406
2407 if (is_null($this->columns)) {
2408 $this->select('*');
2409 }
2410
2411 return $this->selectRaw($sql.' as '.$this->grammar->wrap($as), $bindings);
2412 }
2413
2414 /**
2415 * Order the query by a previously selected relevance score.
2416 *
2417 * @param string $direction
2418 * @param string $as
2419 * @return $this
2420 */
2421 public function orderByRelevance($direction = 'desc', $as = 'relevance')
2422 {
2423 $direction = strtolower($direction) === 'asc' ? 'asc' : 'desc';
2424
2425 return $this->orderByRaw($this->grammar->wrap($as).' '.$direction);
2426 }
2427
2428 /**
2429 * Add a "where" clause to the query for multiple columns with "and" conditions between them.
2430 *
2431 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2432 * @param mixed $operator
2433 * @param mixed $value
2434 * @param string $boolean
2435 * @return $this
2436 */
2437 public function whereAll(
2438 $columns,
2439 $operator = null,
2440 $value = null,
2441 $boolean = 'and'
2442 ) {
2443 [$value, $operator] = $this->prepareValueAndOperator(
2444 $value, $operator, func_num_args() === 2
2445 );
2446
2447 $this->whereNested(function ($query) use ($columns, $operator, $value) {
2448 foreach ($columns as $column) {
2449 $query->where($column, $operator, $value, 'and');
2450 }
2451 }, $boolean);
2452
2453 return $this;
2454 }
2455
2456 /**
2457 * Add an "or where" clause to the query for multiple columns with "and" conditions between them.
2458 *
2459 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2460 * @param mixed $operator
2461 * @param mixed $value
2462 * @return $this
2463 */
2464 public function orWhereAll($columns, $operator = null, $value = null)
2465 {
2466 return $this->whereAll($columns, $operator, $value, 'or');
2467 }
2468
2469 /**
2470 * Add a "where" clause to the query for multiple columns with "or" conditions between them.
2471 *
2472 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2473 * @param mixed $operator
2474 * @param mixed $value
2475 * @param string $boolean
2476 * @return $this
2477 */
2478 public function whereAny(
2479 $columns,
2480 $operator = null,
2481 $value = null,
2482 $boolean = 'and'
2483 ) {
2484 [$value, $operator] = $this->prepareValueAndOperator(
2485 $value, $operator, func_num_args() === 2
2486 );
2487
2488 $this->whereNested(function ($query) use ($columns, $operator, $value) {
2489 foreach ($columns as $column) {
2490 $query->where($column, $operator, $value, 'or');
2491 }
2492 }, $boolean);
2493
2494 return $this;
2495 }
2496
2497 /**
2498 * Add an "or where" clause to the query for multiple columns with "or" conditions between them.
2499 *
2500 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2501 * @param mixed $operator
2502 * @param mixed $value
2503 * @return $this
2504 */
2505 public function orWhereAny($columns, $operator = null, $value = null)
2506 {
2507 return $this->whereAny($columns, $operator, $value, 'or');
2508 }
2509
2510 /**
2511 * Add a "where not" clause to the query for multiple columns where none of the conditions should be true.
2512 *
2513 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2514 * @param mixed $operator
2515 * @param mixed $value
2516 * @param string $boolean
2517 * @return $this
2518 */
2519 public function whereNone($columns, $operator = null, $value = null, $boolean = 'and')
2520 {
2521 return $this->whereAny($columns, $operator, $value, $boolean.' not');
2522 }
2523
2524 /**
2525 * Add an "or where not" clause to the query for multiple columns where none of the conditions should be true.
2526 *
2527 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2528 * @param mixed $operator
2529 * @param mixed $value
2530 * @return $this
2531 */
2532 public function orWhereNone($columns, $operator = null, $value = null)
2533 {
2534 return $this->whereNone($columns, $operator, $value, 'or');
2535 }
2536
2537 /**
2538 * Add a "group by" clause to the query.
2539 *
2540 * @param array|string ...$groups
2541 * @return $this
2542 */
2543 public function groupBy(...$groups)
2544 {
2545 foreach ($groups as $group) {
2546 $this->groups = array_merge(
2547 (array) $this->groups,
2548 Arr::wrap($group)
2549 );
2550 }
2551
2552 return $this;
2553 }
2554
2555 /**
2556 * Add a raw groupBy clause to the query.
2557 *
2558 * @param string $sql
2559 * @param array $bindings
2560 * @return $this
2561 */
2562 public function groupByRaw($sql, array $bindings = [])
2563 {
2564 $this->groups[] = new Expression($sql);
2565
2566 $this->addBinding($bindings, 'groupBy');
2567
2568 return $this;
2569 }
2570
2571 /**
2572 * Add a "having" clause to the query.
2573 *
2574 * @param string $column
2575 * @param string|null $operator
2576 * @param string|null $value
2577 * @param string $boolean
2578 * @return $this
2579 */
2580 public function having($column, $operator = null, $value = null, $boolean = 'and')
2581 {
2582 $type = 'Basic';
2583
2584 if ($column instanceof ConditionExpression) {
2585 $type = 'Expression';
2586
2587 $this->havings[] = compact('type', 'column', 'boolean');
2588
2589 return $this;
2590 }
2591
2592 // Here we will make some assumptions about the operator. If only 2 values are
2593 // passed to the method, we will assume that the operator is an equals sign
2594 // and keep going. Otherwise, we'll require the operator to be passed in.
2595 [$value, $operator] = $this->prepareValueAndOperator(
2596 $value, $operator, func_num_args() === 2
2597 );
2598
2599 if ($column instanceof Closure && is_null($operator)) {
2600 return $this->havingNested($column, $boolean);
2601 }
2602
2603 // If the given operator is not found in the list of valid operators we will
2604 // assume that the developer is just short-cutting the '=' operators and
2605 // we will set the operators to '=' and set the values appropriately.
2606 if ($this->invalidOperator($operator)) {
2607 [$value, $operator] = [$operator, '='];
2608 }
2609
2610 if ($this->isBitwiseOperator($operator)) {
2611 $type = 'Bitwise';
2612 }
2613
2614 $this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean');
2615
2616 if (! $value instanceof Expression) {
2617 $this->addBinding($this->flattenValue($value), 'having');
2618 }
2619
2620 return $this;
2621 }
2622
2623 /**
2624 * Add an "or having" clause to the query.
2625 *
2626 * @param string $column
2627 * @param string|null $operator
2628 * @param string|null $value
2629 * @return $this
2630 */
2631 public function orHaving($column, $operator = null, $value = null)
2632 {
2633 [$value, $operator] = $this->prepareValueAndOperator(
2634 $value, $operator, func_num_args() === 2
2635 );
2636
2637 return $this->having($column, $operator, $value, 'or');
2638 }
2639
2640 /**
2641 * Add a nested having statement to the query.
2642 *
2643 * @param \Closure $callback
2644 * @param string $boolean
2645 * @return $this
2646 */
2647 public function havingNested(Closure $callback, $boolean = 'and')
2648 {
2649 $callback($query = $this->forNestedWhere());
2650
2651 return $this->addNestedHavingQuery($query, $boolean);
2652 }
2653
2654 /**
2655 * Add another query builder as a nested having to the query builder.
2656 *
2657 * @param \FluentCommunity\Framework\Database\Query\Builder $query
2658 * @param string $boolean
2659 * @return $this
2660 */
2661 public function addNestedHavingQuery($query, $boolean = 'and')
2662 {
2663 if (count($query->havings)) {
2664 $type = 'Nested';
2665
2666 $this->havings[] = compact('type', 'query', 'boolean');
2667
2668 $this->addBinding($query->getRawBindings()['having'], 'having');
2669 }
2670
2671 return $this;
2672 }
2673
2674 /**
2675 * Add a "having null" clause to the query.
2676 *
2677 * @param string|array $columns
2678 * @param string $boolean
2679 * @param bool $not
2680 * @return $this
2681 */
2682 public function havingNull($columns, $boolean = 'and', $not = false)
2683 {
2684 $type = $not ? 'NotNull' : 'Null';
2685
2686 foreach (Arr::wrap($columns) as $column) {
2687 $this->havings[] = compact('type', 'column', 'boolean');
2688 }
2689
2690 return $this;
2691 }
2692
2693 /**
2694 * Add an "or having null" clause to the query.
2695 *
2696 * @param string $column
2697 * @return $this
2698 */
2699 public function orHavingNull($column)
2700 {
2701 return $this->havingNull($column, 'or');
2702 }
2703
2704 /**
2705 * Add a "having not null" clause to the query.
2706 *
2707 * @param string|array $columns
2708 * @param string $boolean
2709 * @return $this
2710 */
2711 public function havingNotNull($columns, $boolean = 'and')
2712 {
2713 return $this->havingNull($columns, $boolean, true);
2714 }
2715
2716 /**
2717 * Add an "or having not null" clause to the query.
2718 *
2719 * @param string $column
2720 * @return $this
2721 */
2722 public function orHavingNotNull($column)
2723 {
2724 return $this->havingNotNull($column, 'or');
2725 }
2726
2727 /**
2728 * Add a "having between " clause to the query.
2729 *
2730 * @param string $column
2731 * @param array $values
2732 * @param string $boolean
2733 * @param bool $not
2734 * @return $this
2735 */
2736 public function havingBetween($column, array $values, $boolean = 'and', $not = false)
2737 {
2738 $type = 'between';
2739
2740 if ($values instanceof DatePeriod) {
2741 $values = [$values->getStartDate(), $values->getEndDate()];
2742 }
2743
2744 $this->havings[] = compact('type', 'column', 'values', 'boolean', 'not');
2745
2746 $this->addBinding(array_slice($this->cleanBindings(Arr::flatten($values)), 0, 2), 'having');
2747
2748 return $this;
2749 }
2750
2751 /**
2752 * Add a raw having clause to the query.
2753 *
2754 * @param string $sql
2755 * @param array $bindings
2756 * @param string $boolean
2757 * @return $this
2758 */
2759 public function havingRaw($sql, array $bindings = [], $boolean = 'and')
2760 {
2761 $type = 'Raw';
2762
2763 $this->havings[] = compact('type', 'sql', 'boolean');
2764
2765 $this->addBinding($bindings, 'having');
2766
2767 return $this;
2768 }
2769
2770 /**
2771 * Add a raw or having clause to the query.
2772 *
2773 * @param string $sql
2774 * @param array $bindings
2775 * @return $this
2776 */
2777 public function orHavingRaw($sql, array $bindings = [])
2778 {
2779 return $this->havingRaw($sql, $bindings, 'or');
2780 }
2781
2782 /**
2783 * Add an "order by" clause to the query.
2784 *
2785 * @param \Closure|\FluentCommunity\Framework\Database\Orm\Builder|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string $column
2786 * @param string $direction
2787 * @param array $allowedColumns
2788 * @return $this
2789 *
2790 * @throws \InvalidArgumentException
2791 */
2792 public function orderBy($column, $direction = 'asc', $allowedColumns = [])
2793 {
2794 if (!empty($allowedColumns) && !in_array($column, $allowedColumns, true)) {
2795 throw new LogicException(
2796 "Ordering by `$column` is not allowed for this query."
2797 );
2798 }
2799
2800 if (!preg_match('/^[a-zA-Z0-9_\.]+$/', $column)) {
2801 throw new LogicException("Invalid column name `$column`.");
2802 }
2803
2804 if ($this->isQueryable($column)) {
2805 [$query, $bindings] = $this->createSub($column);
2806
2807 $column = new Expression('('.$query.')');
2808
2809 $this->addBinding($bindings, $this->unions ? 'unionOrder' : 'order');
2810 }
2811
2812 $direction = strtolower($direction);
2813
2814 if (! in_array($direction, ['asc', 'desc'], true)) {
2815 throw new InvalidArgumentException(
2816 'Order direction must be "asc" or "desc".'
2817 );
2818 }
2819
2820 $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
2821 'column' => $column,
2822 'direction' => $direction,
2823 ];
2824
2825 return $this;
2826 }
2827
2828 /**
2829 * Add a descending "order by" clause to the query.
2830 *
2831 * @param \Closure|\FluentCommunity\Framework\Database\Orm\Builder|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string $column
2832 * @return $this
2833 */
2834 public function orderByDesc($column)
2835 {
2836 return $this->orderBy($column, 'desc');
2837 }
2838
2839 /**
2840 * Add an "order by" clause for a timestamp to the query.
2841 *
2842 * @param \Closure|\FluentCommunity\Framework\Database\Orm\Builder|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string $column
2843 * @return $this
2844 */
2845 public function latest($column = 'created_at')
2846 {
2847 return $this->orderBy($column, 'desc');
2848 }
2849
2850 /**
2851 * Add an "order by" clause for a timestamp to the query.
2852 *
2853 * @param \Closure|\FluentCommunity\Framework\Database\Orm\Builder|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string $column
2854 * @return $this
2855 */
2856 public function oldest($column = 'created_at')
2857 {
2858 return $this->orderBy($column, 'asc');
2859 }
2860
2861 /**
2862 * Put the query's results in random order.
2863 *
2864 * @param string $seed
2865 * @return $this
2866 */
2867 public function inRandomOrder($seed = '')
2868 {
2869 return $this->orderByRaw($this->grammar->compileRandom($seed));
2870 }
2871
2872 /**
2873 * Add a raw "order by" clause to the query.
2874 *
2875 * @param string $sql
2876 * @param array $bindings
2877 * @return $this
2878 */
2879 public function orderByRaw($sql, $bindings = [])
2880 {
2881 $type = 'Raw';
2882
2883 $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact(
2884 'type', 'sql'
2885 );
2886
2887 $this->addBinding($bindings, $this->unions ? 'unionOrder' : 'order');
2888
2889 return $this;
2890 }
2891
2892 /**
2893 * Alias to set the "offset" value of the query.
2894 *
2895 * @param int $value
2896 * @return $this
2897 */
2898 public function skip($value)
2899 {
2900 return $this->offset($value);
2901 }
2902
2903 /**
2904 * Set the "offset" value of the query.
2905 *
2906 * @param int $value
2907 * @return $this
2908 */
2909 public function offset($value)
2910 {
2911 $property = $this->unions ? 'unionOffset' : 'offset';
2912
2913 $this->$property = max(0, (int) $value);
2914
2915 return $this;
2916 }
2917
2918 /**
2919 * Alias to set the "limit" value of the query.
2920 *
2921 * @param int $value
2922 * @return $this
2923 */
2924 public function take($value)
2925 {
2926 return $this->limit($value);
2927 }
2928
2929 /**
2930 * Set the "limit" value of the query.
2931 *
2932 * @param int $value
2933 * @return $this
2934 */
2935 public function limit($value)
2936 {
2937 $property = $this->unions ? 'unionLimit' : 'limit';
2938
2939 if ($value >= 0) {
2940 $this->$property = ! is_null($value) ? (int) $value : null;
2941 }
2942
2943 return $this;
2944 }
2945
2946 /**
2947 * Add a "group limit" clause to the query.
2948 *
2949 * @param int $value
2950 * @param string $column
2951 * @return $this
2952 */
2953 public function groupLimit($value, $column)
2954 {
2955 if ($value >= 0) {
2956 $this->groupLimit = compact('value', 'column');
2957 }
2958
2959 return $this;
2960 }
2961
2962 /**
2963 * Set the limit and offset for a given page.
2964 *
2965 * @param int $page
2966 * @param int $perPage
2967 * @return $this
2968 */
2969 public function forPage($page, $perPage = 15)
2970 {
2971 return $this->offset(($page - 1) * $perPage)->limit($perPage);
2972 }
2973
2974 /**
2975 * Constrain the query to the previous "page" of results before a given ID.
2976 *
2977 * @param int $perPage
2978 * @param int|null $lastId
2979 * @param string $column
2980 * @return $this
2981 */
2982 public function forPageBeforeId($perPage = 15, $lastId = 0, $column = 'id')
2983 {
2984 $this->orders = $this->removeExistingOrdersFor($column);
2985
2986 if (! is_null($lastId)) {
2987 $this->where($column, '<', $lastId);
2988 }
2989
2990 return $this->orderBy($column, 'desc')
2991 ->limit($perPage);
2992 }
2993
2994 /**
2995 * Constrain the query to the next "page" of results after a given ID.
2996 *
2997 * @param int $perPage
2998 * @param int|null $lastId
2999 * @param string $column
3000 * @return $this
3001 */
3002 public function forPageAfterId($perPage = 15, $lastId = 0, $column = 'id')
3003 {
3004 $this->orders = $this->removeExistingOrdersFor($column);
3005
3006 if (! is_null($lastId)) {
3007 $this->where($column, '>', $lastId);
3008 }
3009
3010 return $this->orderBy($column, 'asc')
3011 ->limit($perPage);
3012 }
3013
3014 /**
3015 * Remove all existing orders and optionally add a new order.
3016 *
3017 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string|null $column
3018 * @param string $direction
3019 * @return $this
3020 */
3021 public function reorder($column = null, $direction = 'asc')
3022 {
3023 $this->orders = null;
3024 $this->unionOrders = null;
3025 $this->bindings['order'] = [];
3026 $this->bindings['unionOrder'] = [];
3027
3028 if ($column) {
3029 return $this->orderBy($column, $direction);
3030 }
3031
3032 return $this;
3033 }
3034
3035 /**
3036 * Get an array with all orders with a given column removed.
3037 *
3038 * @param string $column
3039 * @return array
3040 */
3041 protected function removeExistingOrdersFor($column)
3042 {
3043 return Collection::make($this->orders)
3044 ->reject(function ($order) use ($column) {
3045 return isset($order['column'])
3046 ? $order['column'] === $column : false;
3047 })->values()->all();
3048 }
3049
3050 /**
3051 * Add a union statement to the query.
3052 *
3053 * @param \FluentCommunity\Framework\Database\Query\Builder|\Closure $query
3054 * @param bool $all
3055 * @return $this
3056 */
3057 public function union($query, $all = false)
3058 {
3059 if ($query instanceof Closure) {
3060 $query($query = $this->newQuery());
3061 }
3062
3063 $this->unions[] = compact('query', 'all');
3064
3065 $this->addBinding($query->getBindings(), 'union');
3066
3067 return $this;
3068 }
3069
3070 /**
3071 * Add a union all statement to the query.
3072 *
3073 * @param \FluentCommunity\Framework\Database\Query\Builder|\Closure $query
3074 * @return $this
3075 */
3076 public function unionAll($query)
3077 {
3078 return $this->union($query, true);
3079 }
3080
3081 /**
3082 * Lock the selected rows in the table.
3083 *
3084 * @param string|bool $value
3085 * @return $this
3086 */
3087 public function lock($value = true)
3088 {
3089 $this->lock = $value;
3090
3091 if (! is_null($this->lock)) {
3092 $this->useWritePdo();
3093 }
3094
3095 return $this;
3096 }
3097
3098 /**
3099 * Lock the selected rows in the table for updating.
3100 *
3101 * @return \FluentCommunity\Framework\Database\Query\Builder
3102 */
3103 public function lockForUpdate()
3104 {
3105 return $this->lock(true);
3106 }
3107
3108 /**
3109 * Share lock the selected rows in the table.
3110 *
3111 * @return \FluentCommunity\Framework\Database\Query\Builder
3112 */
3113 public function sharedLock()
3114 {
3115 return $this->lock(false);
3116 }
3117
3118 /**
3119 * Register a closure to be invoked before the query is executed.
3120 *
3121 * @param callable $callback
3122 * @return $this
3123 */
3124 public function beforeQuery(callable $callback)
3125 {
3126 $this->beforeQueryCallbacks[] = $callback;
3127
3128 return $this;
3129 }
3130
3131 /**
3132 * Invoke the "before query" modification callbacks.
3133 *
3134 * @return void
3135 */
3136 public function applyBeforeQueryCallbacks()
3137 {
3138 foreach ($this->beforeQueryCallbacks as $callback) {
3139 $callback($this);
3140 }
3141
3142 $this->beforeQueryCallbacks = [];
3143 }
3144
3145 /**
3146 * Register a closure to be invoked after the query is executed.
3147 *
3148 * @param \Closure $callback
3149 * @return $this
3150 */
3151 public function afterQuery(Closure $callback)
3152 {
3153 $this->afterQueryCallbacks[] = $callback;
3154
3155 return $this;
3156 }
3157
3158 /**
3159 * Invoke the "after query" modification callbacks.
3160 *
3161 * @param mixed $result
3162 * @return mixed
3163 */
3164 public function applyAfterQueryCallbacks($result)
3165 {
3166 foreach ($this->afterQueryCallbacks as $afterQueryCallback) {
3167 $result = $afterQueryCallback($result) ?: $result;
3168 }
3169
3170 return $result;
3171 }
3172
3173 /**
3174 * Get the SQL representation of the query.
3175 *
3176 * @return string
3177 */
3178 public function toSql()
3179 {
3180 $this->applyBeforeQueryCallbacks();
3181
3182 return $this->grammar->compileSelect($this);
3183 }
3184
3185 /**
3186 * Get the raw SQL representation of the query with embedded bindings.
3187 *
3188 * @return string
3189 */
3190 public function toRawSql()
3191 {
3192 return $this->grammar->substituteBindingsIntoRawSql(
3193 $this->toSql(), $this->connection->prepareBindings($this->getBindings())
3194 );
3195 }
3196
3197 /**
3198 * Execute a query for a single record by ID.
3199 *
3200 * @param int|string $id
3201 * @param array $columns
3202 * @return mixed|static
3203 */
3204 public function find($id, $columns = ['*'])
3205 {
3206 return $this->where('id', '=', $id)->first($columns);
3207 }
3208
3209 /**
3210 * Execute a query for a single record by ID or call a callback.
3211 *
3212 * @template TValue
3213 *
3214 * @param mixed $id
3215 * @param (\Closure(): TValue)|list<string>|string $columns
3216 * @param (\Closure(): TValue)|null $callback
3217 * @return object|TValue
3218 */
3219 public function findOr($id, $columns = ['*'], ?Closure $callback = null)
3220 {
3221 if ($columns instanceof Closure) {
3222 $callback = $columns;
3223
3224 $columns = ['*'];
3225 }
3226
3227 if (! is_null($data = $this->find($id, $columns))) {
3228 return $data;
3229 }
3230
3231 return $callback();
3232 }
3233
3234 /**
3235 * Get a single column's value from the first result of a query.
3236 *
3237 * @param string $column
3238 * @return mixed
3239 */
3240 public function value($column)
3241 {
3242 $result = (array) $this->first([$column]);
3243
3244 return count($result) > 0 ? reset($result) : null;
3245 }
3246
3247 /**
3248 * Get a single expression value from the first result of a query.
3249 *
3250 * @param string $expression
3251 * @param array $bindings
3252 * @return mixed
3253 */
3254 public function rawValue(string $expression, array $bindings = [])
3255 {
3256 $result = (array) $this->selectRaw($expression, $bindings)->first();
3257
3258 return count($result) > 0 ? reset($result) : null;
3259 }
3260
3261 /**
3262 * Get a single column's value from the first result of a query if it's the sole matching record.
3263 *
3264 * @param string $column
3265 * @return mixed
3266 *
3267 * @throws \FluentCommunity\Framework\Database\RecordsNotFoundException
3268 * @throws \FluentCommunity\Framework\Database\MultipleRecordsFoundException
3269 */
3270 public function soleValue($column)
3271 {
3272 $result = (array) $this->sole([$column]);
3273
3274 return reset($result);
3275 }
3276
3277 /**
3278 * Execute the query as a "select" statement.
3279 *
3280 * @param array|string $columns
3281 * @return \FluentCommunity\Framework\Support\Collection
3282 */
3283 public function get($columns = ['*'])
3284 {
3285 $items = Helper::collect($this->onceWithColumns(Arr::wrap($columns), function () {
3286 return $this->processor->processSelect($this, $this->runSelect());
3287 }));
3288
3289 return $this->applyAfterQueryCallbacks(
3290 isset($this->groupLimit) ? $this->withoutGroupLimitKeys($items) : $items
3291 );
3292 }
3293
3294 /**
3295 * Run the query as a "select" statement against the connection.
3296 *
3297 * @return array
3298 */
3299 protected function runSelect()
3300 {
3301 return $this->connection->select(
3302 $this->toSql(), $this->getBindings(), ! $this->useWritePdo
3303 );
3304 }
3305
3306 /**
3307 * Remove the group limit keys from the results in the collection.
3308 *
3309 * @param \FluentCommunity\Framework\Support\Collection $items
3310 * @return \FluentCommunity\Framework\Support\Collection
3311 */
3312 protected function withoutGroupLimitKeys($items)
3313 {
3314 $keysToRemove = ['laravel_row'];
3315
3316 if (is_string($this->groupLimit['column'])) {
3317 $column = Helper::last(explode('.', $this->groupLimit['column']));
3318
3319 $keysToRemove[] = '@laravel_group := '.$this->grammar->wrap($column);
3320 $keysToRemove[] = '@laravel_group := '.$this->grammar->wrap('pivot_'.$column);
3321 }
3322
3323 $items->each(function ($item) use ($keysToRemove) {
3324 foreach ($keysToRemove as $key) {
3325 unset($item->$key);
3326 }
3327 });
3328
3329 return $items;
3330 }
3331
3332 /**
3333 * Paginate the given query into a simple paginator.
3334 *
3335 * @param int $perPage
3336 * @param array $columns
3337 * @param string $pageName
3338 * @param int|null $page
3339 * @param int|null $total
3340 * @return \FluentCommunity\Framework\Pagination\LengthAwarePaginatorInterface
3341 */
3342 public function paginate(
3343 $perPage = 15,
3344 $columns = ['*'],
3345 $pageName = 'page',
3346 $page = null,
3347 $total = null
3348 ) {
3349 $page = $page ?: Paginator::resolveCurrentPage($pageName);
3350
3351 $total = Helper::value($total) ?? $this->getCountForPagination();
3352
3353 $perPage = $perPage instanceof Closure ? $perPage($total) : $perPage;
3354
3355 $results = $total
3356 ? $this->forPage($page, $perPage)->get($columns)
3357 : Helper::collect();
3358
3359 return $this->paginator($results, $total, $perPage, $page, [
3360 'path' => Paginator::resolveCurrentPath(),
3361 'pageName' => $pageName,
3362 ]);
3363 }
3364
3365 /**
3366 * Get a paginator only supporting simple next and previous links.
3367 *
3368 * This is more efficient on larger data-sets, etc.
3369 *
3370 * @param int $perPage
3371 * @param array $columns
3372 * @param string $pageName
3373 * @param int|null $page
3374 * @return \FluentCommunity\Framework\Pagination\PaginatorInterface
3375 */
3376 public function simplePaginate(
3377 $perPage = 15,
3378 $columns = ['*'],
3379 $pageName = 'page',
3380 $page = null
3381 )
3382 {
3383 $page = $page ?: Paginator::resolveCurrentPage($pageName);
3384
3385 $this->offset(($page - 1) * $perPage)->limit($perPage + 1);
3386
3387 return $this->simplePaginator($this->get($columns), $perPage, $page, [
3388 'path' => Paginator::resolveCurrentPath(),
3389 'pageName' => $pageName,
3390 ]);
3391 }
3392
3393 /**
3394 * Get a cursor paginator for efficient pagination of large datasets.
3395 *
3396 * Cursor pagination uses a unique column value (or multiple columns)
3397 * as a pointer, allowing for consistent, efficient
3398 * navigation without large OFFSETs.
3399 *
3400 * @param int|null $perPage
3401 * @param array $columns
3402 * @param string $cursorName
3403 * @param \FluentCommunity\Framework\Pagination\Cursor|string|null $cursor
3404 * @return \FluentCommunity\Framework\Pagination\CursorPaginatorInterface
3405 */
3406 public function cursorPaginate(
3407 $perPage = 15,
3408 $columns = ['*'],
3409 $cursorName = 'cursor',
3410 $cursor = null
3411 )
3412 {
3413 return $this->paginateUsingCursor(
3414 $perPage, $columns, $cursorName, $cursor
3415 );
3416 }
3417
3418 /**
3419 * Ensure the proper order by required for cursor pagination.
3420 *
3421 * @param bool $shouldReverse
3422 * @return \FluentCommunity\Framework\Support\Collection
3423 */
3424 protected function ensureOrderForCursorPagination($shouldReverse = false)
3425 {
3426 if (empty($this->orders) && empty($this->unionOrders)) {
3427 $this->enforceOrderBy();
3428 }
3429
3430 $reverseDirection = function ($order) {
3431 if (! isset($order['direction'])) {
3432 return $order;
3433 }
3434
3435 $order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc';
3436
3437 return $order;
3438 };
3439
3440 if ($shouldReverse) {
3441 $this->orders = Helper::collect($this->orders)->map($reverseDirection)->toArray();
3442 $this->unionOrders = Helper::collect($this->unionOrders)->map($reverseDirection)->toArray();
3443 }
3444
3445 $orders = ! empty($this->unionOrders) ? $this->unionOrders : $this->orders;
3446
3447 return Helper::collect($orders)
3448 ->filter(fn ($order) => Arr::has($order, 'direction'))
3449 ->values();
3450 }
3451
3452 /**
3453 * Get the count of the total records for the paginator.
3454 *
3455 * @param array $columns
3456 * @return int
3457 */
3458 public function getCountForPagination($columns = ['*'])
3459 {
3460 $results = $this->runPaginationCountQuery($columns);
3461
3462 // Once we have run the pagination count query, we will get the resulting count and
3463 // take into account what type of query it was. When there is a group by we will
3464 // just return the count of the entire results set since that will be correct.
3465 if (! isset($results[0])) {
3466 return 0;
3467 } elseif (is_object($results[0])) {
3468 return (int) $results[0]->aggregate;
3469 }
3470
3471 return (int) array_change_key_case((array) $results[0])['aggregate'];
3472 }
3473
3474 /**
3475 * Run a pagination count query.
3476 *
3477 * @param array $columns
3478 * @return array
3479 */
3480 protected function runPaginationCountQuery($columns = ['*'])
3481 {
3482 if ($this->groups || $this->havings) {
3483 $clone = $this->cloneForPaginationCount();
3484
3485 if (is_null($clone->columns) && ! empty($this->joins)) {
3486 $clone->select($this->from.'.*');
3487 }
3488
3489 return $this->newQuery()
3490 ->from(new Expression('('.$clone->toSql().') as '.$this->grammar->wrap('aggregate_table')))
3491 ->mergeBindings($clone)
3492 ->setAggregate('count', $this->withoutSelectAliases($columns))
3493 ->get()->all();
3494 }
3495
3496 $without = $this->unions ? ['unionOrders', 'unionLimit', 'unionOffset'] : ['columns', 'orders', 'limit', 'offset'];
3497
3498 return $this->cloneWithout($without)
3499 ->cloneWithoutBindings($this->unions ? ['unionOrder'] : ['select', 'order'])
3500 ->setAggregate('count', $this->withoutSelectAliases($columns))
3501 ->get()->all();
3502 }
3503
3504 /**
3505 * Clone the existing query instance for usage in a pagination subquery.
3506 *
3507 * @return self
3508 */
3509 protected function cloneForPaginationCount()
3510 {
3511 return $this->cloneWithout(['orders', 'limit', 'offset'])
3512 ->cloneWithoutBindings(['order']);
3513 }
3514
3515 /**
3516 * Remove the column aliases since they will break count queries.
3517 *
3518 * @param array $columns
3519 * @return array
3520 */
3521 protected function withoutSelectAliases(array $columns)
3522 {
3523 return array_map(function ($column) {
3524 return is_string($column) && ($aliasPosition = stripos($column, ' as ')) !== false
3525 ? substr($column, 0, $aliasPosition) : $column;
3526 }, $columns);
3527 }
3528
3529 /**
3530 * Get a lazy collection for the given query.
3531 *
3532 * @return \FluentCommunity\Framework\Support\LazyCollection
3533 */
3534 public function cursor()
3535 {
3536 if (is_null($this->columns)) {
3537 $this->columns = ['*'];
3538 }
3539
3540 return (new LazyCollection(function () {
3541 yield from $this->connection->cursor(
3542 $this->toSql(), $this->getBindings(), ! $this->useWritePdo
3543 );
3544 }))->map(function ($item) {
3545 return $this->applyAfterQueryCallbacks(
3546 Helper::collect([$item])
3547 )->first();
3548 })->reject(fn ($item) => is_null($item));
3549 }
3550
3551 /**
3552 * Get a lazy collection for the given query.
3553 *
3554 * @return \FluentCommunity\Framework\Support\LazyCollection
3555 */
3556 public function rawCursor()
3557 {
3558 if (is_null($this->columns)) {
3559 $this->columns = ['*'];
3560 }
3561
3562 return (new LazyCollection(function () {
3563 yield from $this->connection->rawCursor(
3564 $this->toSql(), $this->getBindings(), ! $this->useWritePdo
3565 );
3566 }))->map(function ($item) {
3567 return $this->applyAfterQueryCallbacks(
3568 Helper::collect([$item])
3569 )->first();
3570 })->reject(fn ($item) => is_null($item));
3571 }
3572
3573 /**
3574 * Throw an exception if the query doesn't have an orderBy clause.
3575 *
3576 * @return void
3577 *
3578 * @throws \RuntimeException
3579 */
3580 protected function enforceOrderBy()
3581 {
3582 if (empty($this->orders) && empty($this->unionOrders)) {
3583 throw new RuntimeException(
3584 'You must specify an orderBy clause when using this function.'
3585 );
3586 }
3587 }
3588
3589 /**
3590 * Get a collection instance containing the values of a given column.
3591 *
3592 * @param string $column
3593 * @param string|null $key
3594 * @return \FluentCommunity\Framework\Support\Collection
3595 */
3596 public function pluck($column, $key = null)
3597 {
3598 // First, we will need to select the results of the query accounting for the
3599 // given columns / key. Once we have the results, we will be able to take
3600 // the results and get the exact data that was requested for the query.
3601 $queryResult = $this->onceWithColumns(
3602 is_null($key) ? [$column] : [$column, $key],
3603 function () {
3604 return $this->processor->processSelect(
3605 $this, $this->runSelect()
3606 );
3607 }
3608 );
3609
3610 if (empty($queryResult)) {
3611 return Helper::collect();
3612 }
3613
3614 // If the columns are qualified with a table or have an alias, we cannot use
3615 // those directly in the "pluck" operations since the results from the DB
3616 // are only keyed by the column itself. We'll strip the table out here.
3617 $column = $this->stripTableForPluck($column);
3618
3619 $key = $this->stripTableForPluck($key);
3620
3621 return $this->applyAfterQueryCallbacks(
3622 is_array($queryResult[0])
3623 ? $this->pluckFromArrayColumn($queryResult, $column, $key)
3624 : $this->pluckFromObjectColumn($queryResult, $column, $key)
3625 );
3626 }
3627
3628 /**
3629 * Strip off the table name or alias from a column identifier.
3630 *
3631 * @param string $column
3632 * @return string|null
3633 */
3634 protected function stripTableForPluck($column)
3635 {
3636 if (is_null($column)) {
3637 return $column;
3638 }
3639
3640 $columnString = $column instanceof Expression
3641 ? $this->grammar->getValue($column)
3642 : $column;
3643
3644 $separator = str_contains(strtolower($columnString), ' as ') ? ' as ' : '\.';
3645
3646 return Helper::last(preg_split('~'.$separator.'~i', $columnString));
3647 }
3648
3649 /**
3650 * Retrieve column values from rows represented as objects.
3651 *
3652 * @param array $queryResult
3653 * @param string $column
3654 * @param string $key
3655 * @return \FluentCommunity\Framework\Support\Collection
3656 */
3657 protected function pluckFromObjectColumn($queryResult, $column, $key)
3658 {
3659 $results = [];
3660
3661 if (is_null($key)) {
3662 foreach ($queryResult as $row) {
3663 $results[] = $row->$column;
3664 }
3665 } else {
3666 foreach ($queryResult as $row) {
3667 $results[$row->$key] = $row->$column;
3668 }
3669 }
3670
3671 return Helper::collect($results);
3672 }
3673
3674 /**
3675 * Retrieve column values from rows represented as arrays.
3676 *
3677 * @param array $queryResult
3678 * @param string $column
3679 * @param string $key
3680 * @return \FluentCommunity\Framework\Support\Collection
3681 */
3682 protected function pluckFromArrayColumn($queryResult, $column, $key)
3683 {
3684 $results = [];
3685
3686 if (is_null($key)) {
3687 foreach ($queryResult as $row) {
3688 $results[] = $row[$column];
3689 }
3690 } else {
3691 foreach ($queryResult as $row) {
3692 $results[$row[$key]] = $row[$column];
3693 }
3694 }
3695
3696 return Helper::collect($results);
3697 }
3698
3699 /**
3700 * Concatenate values of a given column as a string.
3701 *
3702 * @param string $column
3703 * @param string $glue
3704 * @return string
3705 */
3706 public function implode($column, $glue = '')
3707 {
3708 return $this->pluck($column)->implode($glue);
3709 }
3710
3711 /**
3712 * Determine if any rows exist for the current query.
3713 *
3714 * @return bool
3715 */
3716 public function exists()
3717 {
3718 $this->applyBeforeQueryCallbacks();
3719
3720 $results = $this->connection->select(
3721 $this->grammar->compileExists($this), $this->getBindings(), ! $this->useWritePdo
3722 );
3723
3724 // If the results has rows, we will get the row and see if the exists column is a
3725 // boolean true. If there is no results for this query we will return false as
3726 // there are no rows for this query at all and we can return that info here.
3727 if (isset($results[0])) {
3728 $results = (array) $results[0];
3729
3730 return (bool) $results['exists'];
3731 }
3732
3733 return false;
3734 }
3735
3736 /**
3737 * Determine if no rows exist for the current query.
3738 *
3739 * @return bool
3740 */
3741 public function doesntExist()
3742 {
3743 return ! $this->exists();
3744 }
3745
3746 /**
3747 * Execute the given callback if no rows exist for the current query.
3748 *
3749 * @param \Closure $callback
3750 * @return mixed
3751 */
3752 public function existsOr(Closure $callback)
3753 {
3754 return $this->exists() ? true : $callback();
3755 }
3756
3757 /**
3758 * Execute the given callback if rows exist for the current query.
3759 *
3760 * @param \Closure $callback
3761 * @return mixed
3762 */
3763 public function doesntExistOr(Closure $callback)
3764 {
3765 return $this->doesntExist() ? true : $callback();
3766 }
3767
3768 /**
3769 * Retrieve the "count" result of the query.
3770 *
3771 * @param string $columns
3772 * @return int
3773 */
3774 public function count($columns = '*')
3775 {
3776 return (int) $this->aggregate(__FUNCTION__, Arr::wrap($columns));
3777 }
3778
3779 /**
3780 * Retrieve the minimum value of a given column.
3781 *
3782 * @param string $column
3783 * @return mixed
3784 */
3785 public function min($column)
3786 {
3787 return $this->aggregate(__FUNCTION__, [$column]);
3788 }
3789
3790 /**
3791 * Retrieve the maximum value of a given column.
3792 *
3793 * @param string $column
3794 * @return mixed
3795 */
3796 public function max($column)
3797 {
3798 return $this->aggregate(__FUNCTION__, [$column]);
3799 }
3800
3801 /**
3802 * Retrieve the sum of the values of a given column.
3803 *
3804 * @param string $column
3805 * @return mixed
3806 */
3807 public function sum($column)
3808 {
3809 $result = $this->aggregate(__FUNCTION__, [$column]);
3810
3811 return $result ?: 0;
3812 }
3813
3814 /**
3815 * Retrieve the average of the values of a given column.
3816 *
3817 * @param string $column
3818 * @return mixed
3819 */
3820 public function avg($column)
3821 {
3822 return $this->aggregate(__FUNCTION__, [$column]);
3823 }
3824
3825 /**
3826 * Alias for the "avg" method.
3827 *
3828 * @param string $column
3829 * @return mixed
3830 */
3831 public function average($column)
3832 {
3833 return $this->avg($column);
3834 }
3835
3836 /**
3837 * Execute an aggregate function on the database.
3838 *
3839 * @param string $function
3840 * @param array $columns
3841 * @return mixed
3842 */
3843 public function aggregate($function, $columns = ['*'])
3844 {
3845 $results = $this->cloneWithout($this->unions || $this->havings ? [] : ['columns'])
3846 ->cloneWithoutBindings($this->unions || $this->havings ? [] : ['select'])
3847 ->setAggregate($function, $columns)
3848 ->get($columns);
3849
3850 if (! $results->isEmpty()) {
3851 return array_change_key_case((array) $results[0])['aggregate'];
3852 }
3853 }
3854
3855 /**
3856 * Execute a numeric aggregate function on the database.
3857 *
3858 * @param string $function
3859 * @param array $columns
3860 * @return float|int
3861 */
3862 public function numericAggregate($function, $columns = ['*'])
3863 {
3864 $result = $this->aggregate($function, $columns);
3865
3866 // If there is no result, we can obviously just return 0 here. Next, we will check
3867 // if the result is an integer or float. If it is already one of these two data
3868 // types we can just return the result as-is, otherwise we will convert this.
3869 if (! $result) {
3870 return 0;
3871 }
3872
3873 if (is_int($result) || is_float($result)) {
3874 return $result;
3875 }
3876
3877 // If the result doesn't contain a decimal place, we will assume it is an int then
3878 // cast it to one. When it does we will cast it to a float since it needs to be
3879 // cast to the expected data type for the developers out of pure convenience.
3880 return ! str_contains((string) $result, '.')
3881 ? (int) $result : (float) $result;
3882 }
3883
3884 /**
3885 * Set the aggregate property without running the query.
3886 *
3887 * @param string $function
3888 * @param array $columns
3889 * @return $this
3890 */
3891 protected function setAggregate($function, $columns)
3892 {
3893 $this->aggregate = compact('function', 'columns');
3894
3895 if (empty($this->groups)) {
3896 $this->orders = null;
3897
3898 $this->bindings['order'] = [];
3899 }
3900
3901 return $this;
3902 }
3903
3904 /**
3905 * Execute the given callback while selecting the given columns.
3906 *
3907 * After running the callback, the columns are reset to the original value.
3908 *
3909 * @param array $columns
3910 * @param callable $callback
3911 * @return mixed
3912 */
3913 protected function onceWithColumns($columns, $callback)
3914 {
3915 $original = $this->columns;
3916
3917 if (is_null($original)) {
3918 $this->columns = $columns;
3919 }
3920
3921 $result = $callback();
3922
3923 $this->columns = $original;
3924
3925 return $result;
3926 }
3927
3928 /**
3929 * Insert new records into the database.
3930 *
3931 * @param array $values
3932 * @return bool
3933 */
3934 public function insert(array $values)
3935 {
3936 // Since every insert gets treated like a batch insert, we will make sure the
3937 // bindings are structured in a way that is convenient when building these
3938 // inserts statements by verifying these elements are actually an array.
3939 if (empty($values)) {
3940 return true;
3941 }
3942
3943 if (! is_array(reset($values))) {
3944 $values = [$values];
3945 }
3946
3947 // Here, we will sort the insert keys for every record so that each insert is
3948 // in the same order for the record. We need to make sure this is the case
3949 // so there are not any errors or problems when inserting these records.
3950 else {
3951 foreach ($values as $key => $value) {
3952 ksort($value);
3953
3954 $values[$key] = $value;
3955 }
3956 }
3957
3958 $this->applyBeforeQueryCallbacks();
3959
3960 // Finally, we will run this query against the database connection and return
3961 // the results. We will need to also flatten these bindings before running
3962 // the query so they are all in one huge, flattened array for execution.
3963 return $this->connection->insert(
3964 $this->grammar->compileInsert($this, $values),
3965 $this->cleanBindings(Arr::flatten($values, 1))
3966 );
3967 }
3968
3969 /**
3970 * Insert new records into the database while ignoring errors.
3971 *
3972 * @param array $values
3973 * @return int
3974 */
3975 public function insertOrIgnore(array $values)
3976 {
3977 if (empty($values)) {
3978 return 0;
3979 }
3980
3981 if (! is_array(reset($values))) {
3982 $values = [$values];
3983 } else {
3984 foreach ($values as $key => $value) {
3985 ksort($value);
3986
3987 $values[$key] = $value;
3988 }
3989 }
3990
3991 $this->applyBeforeQueryCallbacks();
3992
3993 return $this->connection->affectingStatement(
3994 $this->grammar->compileInsertOrIgnore($this, $values),
3995 $this->cleanBindings(Arr::flatten($values, 1))
3996 );
3997 }
3998
3999 /**
4000 * Insert a new record and get the value of the primary key.
4001 *
4002 * @param array $values
4003 * @param string|null $sequence
4004 * @return int
4005 */
4006 public function insertGetId(array $values, $sequence = null)
4007 {
4008 $this->applyBeforeQueryCallbacks();
4009
4010 $sql = $this->grammar->compileInsertGetId($this, $values, $sequence);
4011
4012 $values = $this->cleanBindings($values);
4013
4014 return $this->processor->processInsertGetId($this, $sql, $values, $sequence);
4015 }
4016
4017 /**
4018 * Insert new records into the table using a subquery.
4019 *
4020 * @param array $columns
4021 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $query
4022 * @return int
4023 */
4024 public function insertUsing(array $columns, $query)
4025 {
4026 $this->applyBeforeQueryCallbacks();
4027
4028 [$sql, $bindings] = $this->createSub($query);
4029
4030 return $this->connection->affectingStatement(
4031 $this->grammar->compileInsertUsing($this, $columns, $sql),
4032 $this->cleanBindings($bindings)
4033 );
4034 }
4035
4036 /**
4037 * Insert new records into the table using a subquery while ignoring errors.
4038 *
4039 * @param array<string> $columns
4040 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
4041 * @return int|bool Returns the number of affected rows or false on failure
4042 */
4043 public function insertOrIgnoreUsing(array $columns, $query)
4044 {
4045 $this->applyBeforeQueryCallbacks();
4046
4047 [$sql, $bindings] = $this->createSub($query);
4048
4049 return $this->connection->affectingStatement(
4050 $this->grammar->compileInsertOrIgnoreUsing($this, $columns, $sql),
4051 $this->cleanBindings($bindings)
4052 );
4053 }
4054
4055 /**
4056 * Update records in the database.
4057 *
4058 * @param array $values
4059 * @return int
4060 */
4061 public function update(array $values)
4062 {
4063 $this->applyBeforeQueryCallbacks();
4064
4065 $values = Helper::collect($values)->map(function ($value) {
4066 if (! $value instanceof Builder) {
4067 return ['value' => $value, 'bindings' => $value];
4068 }
4069
4070 [$query, $bindings] = $this->parseSub($value);
4071
4072 return ['value' => new Expression("({$query})"), 'bindings' => fn () => $bindings];
4073 });
4074
4075 $sql = $this->grammar->compileUpdate($this, $values->map(fn ($value) => $value['value'])->all());
4076
4077 return $this->connection->update($sql, $this->cleanBindings(
4078 $this->grammar->prepareBindingsForUpdate($this->bindings, $values->map(fn ($value) => $value['bindings'])->all())
4079 ));
4080 }
4081
4082 /**
4083 * Update records in a PostgreSQL database using the update from syntax.
4084 *
4085 * @param array $values
4086 * @return int
4087 */
4088 public function updateFrom(array $values)
4089 {
4090 if (! method_exists($this->grammar, 'compileUpdateFrom')) {
4091 throw new LogicException('This database engine does not support the updateFrom method.');
4092 }
4093
4094 $this->applyBeforeQueryCallbacks();
4095
4096 $sql = $this->grammar->compileUpdateFrom($this, $values);
4097
4098 return $this->connection->update($sql, $this->cleanBindings(
4099 $this->grammar->prepareBindingsForUpdateFrom($this->bindings, $values)
4100 ));
4101 }
4102
4103 /**
4104 * Insert or update a record matching the attributes, and fill it with values.
4105 *
4106 * @param array $attributes
4107 * @param array $values
4108 * @return bool
4109 */
4110 public function updateOrInsert(array $attributes, $values = [])
4111 {
4112 $exists = $this->where($attributes)->exists();
4113
4114 if ($values instanceof Closure) {
4115 $values = $values($exists);
4116 }
4117
4118 if (! $exists) {
4119 return $this->insert(array_merge($attributes, $values));
4120 }
4121
4122 if (empty($values)) {
4123 return true;
4124 }
4125
4126 return (bool) $this->limit(1)->update($values);
4127 }
4128
4129 /**
4130 * Insert new records or update the existing ones.
4131 *
4132 * @param array $values
4133 * @param array|string $uniqueBy
4134 * @param array|null $update
4135 * @return int
4136 */
4137 public function upsert(array $values, $uniqueBy, $update = null)
4138 {
4139 if (empty($values)) {
4140 return 0;
4141 } elseif ($update === []) {
4142 return (int) $this->insert($values);
4143 }
4144
4145 if (! is_array(reset($values))) {
4146 $values = [$values];
4147 } else {
4148 foreach ($values as $key => $value) {
4149 ksort($value);
4150
4151 $values[$key] = $value;
4152 }
4153 }
4154
4155 if (is_null($update)) {
4156 $update = array_keys(reset($values));
4157 }
4158
4159 $this->applyBeforeQueryCallbacks();
4160
4161 $bindings = $this->cleanBindings(array_merge(
4162 Arr::flatten($values, 1),
4163 Helper::collect($update)->reject(function ($value, $key) {
4164 return is_int($key);
4165 })->all()
4166 ));
4167
4168 return $this->connection->affectingStatement(
4169 $this->grammar->compileUpsert($this, $values, (array) $uniqueBy, $update),
4170 $bindings
4171 );
4172 }
4173
4174 /**
4175 * Increment a column's value by a given amount.
4176 *
4177 * @param string $column
4178 * @param float|int $amount
4179 * @param array $extra
4180 * @return int
4181 *
4182 * @throws \InvalidArgumentException
4183 */
4184 public function increment($column, $amount = 1, array $extra = [])
4185 {
4186 if (! is_numeric($amount)) {
4187 throw new InvalidArgumentException('Non-numeric value passed to increment method.');
4188 }
4189
4190 return $this->incrementEach([$column => $amount], $extra);
4191 }
4192
4193 /**
4194 * Increment the given column's values by the given amounts.
4195 *
4196 * @param array<string, float|int|numeric-string> $columns
4197 * @param array<string, mixed> $extra
4198 * @return int
4199 *
4200 * @throws \InvalidArgumentException
4201 */
4202 public function incrementEach(array $columns, array $extra = [])
4203 {
4204 foreach ($columns as $column => $amount) {
4205 if (! is_numeric($amount)) {
4206 throw new InvalidArgumentException("Non-numeric value passed as increment amount for column: '$column'.");
4207 } elseif (! is_string($column)) {
4208 throw new InvalidArgumentException('Non-associative array passed to incrementEach method.');
4209 }
4210
4211 $columns[$column] = $this->raw("{$this->grammar->wrap($column)} + $amount");
4212 }
4213
4214 return $this->update(array_merge($columns, $extra));
4215 }
4216
4217 /**
4218 * Decrement a column's value by a given amount.
4219 *
4220 * @param string $column
4221 * @param float|int $amount
4222 * @param array $extra
4223 * @return int
4224 *
4225 * @throws \InvalidArgumentException
4226 */
4227 public function decrement($column, $amount = 1, array $extra = [])
4228 {
4229 if (! is_numeric($amount)) {
4230 throw new InvalidArgumentException('Non-numeric value passed to decrement method.');
4231 }
4232
4233 return $this->decrementEach([$column => $amount], $extra);
4234 }
4235
4236 /**
4237 * Decrement the given column's values by the given amounts.
4238 *
4239 * @param array<string, float|int|numeric-string> $columns
4240 * @param array<string, mixed> $extra
4241 * @return int
4242 *
4243 * @throws \InvalidArgumentException
4244 */
4245 public function decrementEach(array $columns, array $extra = [])
4246 {
4247 foreach ($columns as $column => $amount) {
4248 if (! is_numeric($amount)) {
4249 throw new InvalidArgumentException("Non-numeric value passed as decrement amount for column: '$column'.");
4250 } elseif (! is_string($column)) {
4251 throw new InvalidArgumentException('Non-associative array passed to decrementEach method.');
4252 }
4253
4254 $columns[$column] = $this->raw("{$this->grammar->wrap($column)} - $amount");
4255 }
4256
4257 return $this->update(array_merge($columns, $extra));
4258 }
4259
4260 /**
4261 * Delete records from the database.
4262 *
4263 * @param mixed $id
4264 * @return int
4265 */
4266 public function delete($id = null)
4267 {
4268 // If an ID is passed to the method, we will set the where clause to check the
4269 // ID to let developers to simply and quickly remove a single row from this
4270 // database without manually specifying the "where" clauses on the query.
4271 if (! is_null($id)) {
4272 $this->where($this->from.'.id', '=', $id);
4273 }
4274
4275 $this->applyBeforeQueryCallbacks();
4276
4277 return $this->connection->delete(
4278 $this->grammar->compileDelete($this), $this->cleanBindings(
4279 $this->grammar->prepareBindingsForDelete($this->bindings)
4280 )
4281 );
4282 }
4283
4284 /**
4285 * Run a truncate statement on the table.
4286 *
4287 * @return void
4288 */
4289 public function truncate()
4290 {
4291 $this->applyBeforeQueryCallbacks();
4292
4293 foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) {
4294 $this->connection->statement($sql, $bindings);
4295 }
4296 }
4297
4298 /**
4299 * Get a new instance of the query builder.
4300 *
4301 * @return \FluentCommunity\Framework\Database\Query\Builder
4302 */
4303 public function newQuery()
4304 {
4305 return new static($this->connection, $this->grammar, $this->processor);
4306 }
4307
4308 /**
4309 * Create a new query instance for a sub-query.
4310 *
4311 * @return \FluentCommunity\Framework\Database\Query\Builder
4312 */
4313 protected function forSubQuery()
4314 {
4315 return $this->newQuery();
4316 }
4317
4318 /**
4319 * Get all of the query builder's columns in a text-only array with all expressions evaluated.
4320 *
4321 * @return array
4322 */
4323 public function getColumns()
4324 {
4325 return ! is_null($this->columns)
4326 ? array_map(fn ($column) => $this->grammar->getValue($column), $this->columns)
4327 : [];
4328 }
4329
4330 /**
4331 * Create a raw database expression.
4332 *
4333 * @param mixed $value
4334 * @return \FluentCommunity\Framework\Database\Query\Expression
4335 */
4336 public function raw($value)
4337 {
4338 return $this->connection->raw($value);
4339 }
4340
4341 /**
4342 * Get the query builder instances that are used in the union of the query.
4343 *
4344 * @return \FluentCommunity\Framework\Support\Collection
4345 */
4346 protected function getUnionBuilders()
4347 {
4348 return isset($this->unions)
4349 ? Helper::collect($this->unions)->pluck('query')
4350 : Helper::collect();
4351 }
4352
4353 /**
4354 * Get the current query value bindings in a flattened array.
4355 *
4356 * @return array
4357 */
4358 public function getBindings()
4359 {
4360 return Arr::flatten($this->bindings);
4361 }
4362
4363 /**
4364 * Get the raw array of bindings.
4365 *
4366 * @return array
4367 */
4368 public function getRawBindings()
4369 {
4370 return $this->bindings;
4371 }
4372
4373 /**
4374 * Set the bindings on the query builder.
4375 *
4376 * @param array $bindings
4377 * @param string $type
4378 * @return $this
4379 *
4380 * @throws \InvalidArgumentException
4381 */
4382 public function setBindings(array $bindings, $type = 'where')
4383 {
4384 if (! array_key_exists($type, $this->bindings)) {
4385 throw new InvalidArgumentException("Invalid binding type: {$type}.");
4386 }
4387
4388 $this->bindings[$type] = $bindings;
4389
4390 return $this;
4391 }
4392
4393 /**
4394 * Add a binding to the query.
4395 *
4396 * @param mixed $value
4397 * @param string $type
4398 * @return $this
4399 *
4400 * @throws \InvalidArgumentException
4401 */
4402 public function addBinding($value, $type = 'where')
4403 {
4404 if (! array_key_exists($type, $this->bindings)) {
4405 throw new InvalidArgumentException("Invalid binding type: {$type}.");
4406 }
4407
4408 if (is_array($value)) {
4409 $this->bindings[$type] = array_values(array_map(
4410 [$this, 'castBinding'],
4411 array_merge($this->bindings[$type], $value),
4412 ));
4413 } else {
4414 $this->bindings[$type][] = $this->castBinding($value);
4415 }
4416
4417 return $this;
4418 }
4419
4420 /**
4421 * Cast the given binding value.
4422 *
4423 * @param mixed $value
4424 * @return mixed
4425 */
4426 public function castBinding($value)
4427 {
4428 if (function_exists('enum_exists')) {
4429 if ($value instanceof \BackedEnum) {
4430 return $value->value;
4431 }
4432 }
4433
4434 return $value;
4435 }
4436
4437 /**
4438 * Merge an array of bindings into our bindings.
4439 *
4440 * @param \FluentCommunity\Framework\Database\Query\Builder $query
4441 * @return $this
4442 */
4443 public function mergeBindings(self $query)
4444 {
4445 $this->bindings = array_merge_recursive($this->bindings, $query->bindings);
4446
4447 return $this;
4448 }
4449
4450 /**
4451 * Remove all of the expressions from a list of bindings.
4452 *
4453 * @param array $bindings
4454 * @return array
4455 */
4456 public function cleanBindings(array $bindings)
4457 {
4458 return Helper::collect($bindings)
4459 ->reject(function ($binding) {
4460 return $binding instanceof Expression;
4461 })
4462 ->map([$this, 'castBinding'])
4463 ->values()
4464 ->all();
4465 }
4466
4467 /**
4468 * Get a scalar type value from an unknown type of input.
4469 *
4470 * @param mixed $value
4471 * @return mixed
4472 */
4473 protected function flattenValue($value)
4474 {
4475 return is_array($value) ? Helper::head(Arr::flatten($value)) : $value;
4476 }
4477
4478 /**
4479 * Get the default key name of the table.
4480 *
4481 * @return string
4482 */
4483 protected function defaultKeyName()
4484 {
4485 return 'id';
4486 }
4487
4488 /**
4489 * Get the database connection instance.
4490 *
4491 * @return \FluentCommunity\Framework\Database\ConnectionInterface
4492 */
4493 public function getConnection()
4494 {
4495 return $this->connection;
4496 }
4497
4498 /**
4499 * Get the database query processor instance.
4500 *
4501 * @return \FluentCommunity\Framework\Database\Query\Processors\Processor
4502 */
4503 public function getProcessor()
4504 {
4505 return $this->processor;
4506 }
4507
4508 /**
4509 * Get the query grammar instance.
4510 *
4511 * @return \FluentCommunity\Framework\Database\Query\Grammars\Grammar
4512 */
4513 public function getGrammar()
4514 {
4515 return $this->grammar;
4516 }
4517
4518 /**
4519 * Use the write pdo for query.
4520 *
4521 * @return $this
4522 */
4523 public function useWritePdo()
4524 {
4525 $this->useWritePdo = true;
4526
4527 return $this;
4528 }
4529
4530 /**
4531 * Determine if the value is a query builder instance or a Closure.
4532 *
4533 * @param mixed $value
4534 * @return bool
4535 */
4536 protected function isQueryable($value)
4537 {
4538 return $value instanceof self ||
4539 $value instanceof OrmBuilder ||
4540 $value instanceof Relation ||
4541 $value instanceof Closure;
4542 }
4543
4544 /**
4545 * Clone the query.
4546 *
4547 * @return static
4548 */
4549 public function clone()
4550 {
4551 return clone $this;
4552 }
4553
4554 /**
4555 * Clone the query without the given properties.
4556 *
4557 * @param array $properties
4558 * @return static
4559 */
4560 public function cloneWithout(array $properties)
4561 {
4562 return Helper::tap($this->clone(), function ($clone) use ($properties) {
4563 foreach ($properties as $property) {
4564 $clone->{$property} = null;
4565 }
4566 });
4567 }
4568
4569 /**
4570 * Clone the query without the given bindings.
4571 *
4572 * @param array $except
4573 * @return static
4574 */
4575 public function cloneWithoutBindings(array $except)
4576 {
4577 return Helper::tap($this->clone(), function ($clone) use ($except) {
4578 foreach ($except as $type) {
4579 $clone->bindings[$type] = [];
4580 }
4581 });
4582 }
4583
4584 /**
4585 * Handle dynamic method calls into the method.
4586 *
4587 * @param string $method
4588 * @param array $parameters
4589 * @return mixed
4590 *
4591 * @throws \BadMethodCallException
4592 */
4593 public function __call($method, $parameters)
4594 {
4595 if (static::hasMacro($method)) {
4596 return $this->macroCall($method, $parameters);
4597 }
4598
4599 if (Str::startsWith($method, 'where')) {
4600 return $this->dynamicWhere($method, $parameters);
4601 }
4602
4603 static::throwBadMethodCallException($method);
4604 }
4605
4606 /**
4607 * Set a dynamic property.
4608 *
4609 * @param string $key
4610 * @param mixed $value
4611 */
4612 public function __set($key, $value)
4613 {
4614 $this->dynamicProperties[$key] = $value;
4615 }
4616
4617 /**
4618 * Get dynamically injected value.
4619 *
4620 * @param string $key
4621 * @return mixed
4622 */
4623 public function __get($key)
4624 {
4625 return $this->dynamicProperties[$key] ?? null;
4626 }
4627 }
4628