Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 399 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

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