Builder::mergeBindings()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php 
2
3
/**
4
 * Lenevor PHP Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2021 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
 
23
namespace Syscodes\Database\Query;
24
25
use Closure;
26
use RuntimeException;
27
use DateTimeInterface;
28
use BadMethodCallException;
29
use InvalidArgumentException;
30
use Syscodes\Collections\Arr;
31
use Syscodes\Collections\Str;
0 ignored issues
show
Bug introduced by
The type Syscodes\Collections\Str was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
32
use Syscodes\Collections\Collection;
33
use Syscodes\Database\DatabaseCache;
0 ignored issues
show
Bug introduced by
The type Syscodes\Database\DatabaseCache was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
34
use Syscodes\Database\Query\Grammar;
35
use Syscodes\Database\Query\Processor;
36
use Syscodes\Database\Query\Expression;
37
use Syscodes\Database\Query\JoinClause;
38
use Syscodes\Database\ConnectionInterface;
39
40
/**
41
 * Lenevor database query builder provides a convenient, fluent interface 
42
 * to creating and running database queries. and works on all supported 
43
 * database systems.
44
 * 
45
 * @author Alexander Campo <[email protected]>
46
 */
47
class Builder
48
{
49
    /**
50
     * An aggregate function and column to be run.
51
     * 
52
     * @var array $aggregate
53
     */
54
    public $aggregate;
55
56
    /**
57
     * The current query value bindings.
58
     * 
59
     * @var array $bindings
60
     */
61
    public $bindings = [
62
        'select' => [],
63
        'from' => [],
64
        'join' => [],
65
        'where' => [],
66
        'groupBy' => [],
67
        'having' => [],
68
        'order' => [],
69
        'union' => [],
70
    ];
71
72
    /**
73
     * Get the columns of a table.
74
     * 
75
     * @var array $columns
76
     */
77
    public $columns;
78
79
    /**
80
     * The database connection instance.
81
     * 
82
     * @var \Syscodes\Database\ConnectionInterface $connection
83
     */
84
    protected $connection;
85
86
    /**
87
     * Indicates if the query returns distinct results.
88
     * 
89
     * @var bool $distinct
90
     */
91
    public $distinct = false;
92
93
    /**
94
     * Get the table name for the query.
95
     * 
96
     * @var string $from
97
     */
98
    public $from;
99
100
    /**
101
     * The database query grammar instance.
102
     * 
103
     * @var \Syscodes\Database\Query\Grammar $grammar
104
     */
105
    protected $grammar;
106
107
    /**
108
     * Get the grouping for the query.
109
     * 
110
     * @var array $groups
111
     */
112
    public $groups;
113
114
    /**
115
     * Get the having constraints for the query.
116
     * 
117
     * @var array $havings
118
     */
119
    public $havings;
120
121
    /**
122
     * Get the table joins for the query.
123
     * 
124
     * @var array $joins
125
     */
126
    public $joins;
127
128
    /**
129
     * Get the maximum number of records to return.
130
     * 
131
     * @var int $limit
132
     */
133
    public $limit;
134
135
    /**
136
     * Indicates whether row locking is being used.
137
     * 
138
     * @var string|bool $lock
139
     */
140
    public $lock;
141
142
    /**
143
     * Get the number of records to skip.
144
     * 
145
     * @var int $offset
146
     */
147
    public $offset;
148
149
    /**
150
     * All of the available clause operators.
151
     * 
152
     * @var array $operators
153
     */
154
    public $operators = [
155
        '=', '<', '>', '<=', '>=', '<>', '!=',
156
        'like', 'not like', 'between', 'ilike',
157
        '&', '|', '^', '<<', '>>',
158
    ];
159
160
    /**
161
     * Get the orderings for the query.
162
     * 
163
     * @var array $orders
164
     */
165
    public $orders;
166
167
    /**
168
     * The database query post processor instance.
169
     * 
170
     * @var \Syscodes\Database\Query\Processor $processor
171
     */
172
    protected $processor;
173
174
    /**
175
     * Get the query union statements.
176
     * 
177
     * @var array $unions
178
     */
179
    public $unions;
180
181
    /**
182
     * Get the maximum number of union records to return.
183
     * 
184
     * @var int $unionLimit
185
     */
186
    public $unionLimit;
187
188
    /**
189
     * Get the number of union records to skip.
190
     * 
191
     * @var int $unionOffset
192
     */
193
    public $unionOffset;
194
195
    /**
196
     * Get the orderings for the union query.
197
     * 
198
     * @var array $unionOrders
199
     */
200
    public $unionOrders;
201
202
    /**
203
     * Get the where constraints for the query.
204
     * 
205
     * @var array $wheres
206
     */
207
    public $wheres;
208
209
    /**
210
     * Constructor. Create a new query builder instance.
211
     * 
212
     * @param  \Syscodes\Database\ConnectionInterface  $connection
213
     * @param  \Syscodes\Database\Query\Grammar  $grammar  
214
     * @param  \Syscodes\Database\Query\Processor  $processor  
215
     * 
216
     * return void
217
     */
218
    public function __construct(ConnectionInterface $connection, Grammar $grammar = null, Processor $processor = null)
219
    {
220
        $this->connection = $connection;
221
        $this->grammar    = $grammar ?: $this->getQueryGrammar();
222
        $this->processor  = $processor ?: $this->getQueryProcessor();
223
    }
224
225
    /**
226
     * Set the columns to be selected.
227
     * 
228
     * @param  array  $columns
229
     * 
230
     * @return $this
231
     */
232
    public function select($columns = ['*'])
233
    {
234
        $this->columns = [];
235
        $this->bindings['select'] = [];
236
        $columns = is_array($columns) ? $columns : func_get_args();
237
238
        foreach ($columns as $as => $column) {
239
            if (is_string($as)) {
240
                $this->selectSub($column, $as);
241
            } else {
242
                $this->columns[] = $column;
243
            }
244
        }
245
246
        return $this;
247
    }
248
249
    /**
250
     * Add a subselect expression to the query.
251
     * 
252
     * @param  \Syscodes\Database\Query\Builder|string  $builder
253
     * @param  string  $as
254
     * 
255
     * @return $this
256
     * 
257
     * @throws \InvalidArgumentException
258
     */
259
    public function selectSub($builder, $as)
260
    {
261
        [$builder, $bindings] = $this->makeSub($builder);
262
263
        return $this->selectRaw(
264
            '('.$builder.') as '.$this->grammar->wrap($as), $bindings
265
        );
266
    }
267
268
    /**
269
     * Makes a subquery and parse it.
270
     * 
271
     * @param  \Closure|\Syscodes\Database\Query\Builder|string  $builder
272
     * 
273
     * @return array
274
     */
275
    protected function makeSub($builder)
276
    {
277
        if ($builder instanceof Closure) {
278
            $callback = $builder;
279
280
            $callback($builder = $this->newBuilder());
281
        }
282
283
        return $this->parseSub($builder);
284
    }
285
286
    /**
287
     * Parse the subquery into SQL and bindings.
288
     * 
289
     * @param  mixed  $builder
290
     * 
291
     * @return array
292
     * 
293
     * @throws \InvalidArgumentException
294
     */
295
    protected function parseSub($builder)
296
    {
297
        if ($builder instanceof self) {
298
            return [$builder->getSql(), $builder->getBindings()];
299
        } elseif (is_string($builder)) {
300
            return [$builder->getSql(), []];
301
        } else {
302
            throw new InvalidArgumentException('A subquery must be a query builder instance, a Closure, or a string');
303
        }
304
    }
305
306
    /**
307
     *  Add a new "raw" select expression to the query.
308
     * 
309
     * @param  string  $expression
310
     * @param  array  $bindings
311
     * 
312
     * @return $this
313
     */
314
    public function selectRaw($expression, array $bindings = [])
315
    {
316
        $this->addSelect(new Expression($expression));
317
318
        if (! empty($bindings)) {
319
            $this->addBinding($bindings, 'select');
320
        }
321
322
        return $this;
323
    }
324
325
    /**
326
     * Add a new select column to the query.
327
     * 
328
     * @param  mixed  $column
329
     * 
330
     * @return $this
331
     */
332
    public function addSelect($column)
333
    {
334
        $column = is_array($column) ? $column : func_get_args();
335
336
        $this->columns = array_merge((array) $this->columns, $column);
337
338
        return $this;
339
    }
340
341
    /**
342
     * Allows force the query for return distinct results.
343
     * 
344
     * @return $this
345
     */
346
    public function distinct()
347
    {
348
        $this->distinct = true;
349
350
        return $this;
351
    }
352
    
353
    /**
354
     * Set the table which the query.
355
     * 
356
     * @param  string  $table
357
     * @param  string|null  $as
358
     * 
359
     * @return $this
360
     */
361
    public function from($table, $as = null)
362
    {
363
        $this->from = $as ? "{$table} as {$as}" : $table;
364
365
        return $this;
366
    }
367
368
    /**
369
     * Add a basic where clause to the query.
370
     * 
371
     * @param  mixed  $column
372
     * @param  mixed  $operator  
373
     * @param  mixed  $value  
374
     * @param  mixed  $boolean  
375
     * 
376
     * @return $this
377
     * 
378
     * @throws \InvalidArgumentException
379
     */
380
    public function where($column, $operator = null, $value = null, $boolean = 'and')
381
    {
382
        if (is_array($column)) {
383
            return $this->addArrayWheres($column, $boolean);
0 ignored issues
show
Bug introduced by
$column of type array is incompatible with the type string expected by parameter $column of Syscodes\Database\Query\Builder::addArrayWheres(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

383
            return $this->addArrayWheres(/** @scrutinizer ignore-type */ $column, $boolean);
Loading history...
384
        }
385
        
386
        [$value, $operator] = $this->prepareValueAndOperator(
387
            $value, $operator, func_num_args() === 2
388
        );
389
390
        if ($column instanceof Closure && is_null($operator)) {
391
            return $this->whereNested($column, $boolean);
392
        }
393
394
        if ($this->invalidOperator($operator)) {
395
            [$value, $operator] = [$operator, '='];
396
        }
397
398
        if ($value instanceof Closure) {
399
            return $this->whereSub($column, $operator, $value, $boolean);
400
        }
401
402
        if (is_null($value)) {
403
            return $this->whereNull($column, $boolean, $operator !== '=');
404
        }
405
406
        $type = 'Basic';
407
408
        $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
409
410
        if ($value instanceof Expression) {
411
            $this->addBinding($value, 'where');
412
        }
413
414
        return $this;
415
    }
416
417
    /**
418
     * Add an array of where clauses to the query.
419
     * 
420
     * @param  string  $column
421
     * @param  string  $boolean
422
     * @param  string  $method  
423
     * 
424
     * @return $this
425
     */
426
    protected function addArrayWheres($column, $boolean, $method = 'where')
427
    {
428
        return $this->whereNested(function ($query) use ($column, $method, $boolean) {
429
            foreach ($column as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $column of type string is not traversable.
Loading history...
430
                if (is_numeric($key) && is_array($value)) {
431
                    $query->{$method}(...array_values($value));
432
                } else {
433
                    $query->{$method}($key, '=', $value, $boolean);
434
                }
435
            }
436
        }, $boolean);
437
    }
438
    
439
    /**
440
     * Prepare the value and operator for a where clause.
441
     * 
442
     * @param  string  $value
443
     * @param  string  $operator
444
     * @param  bool  $useDefault
445
     * 
446
     * @return array
447
     * 
448
     * @throws \InvalidArgumentException
449
     */
450
    public function prepareValueAndOperator($value, $operator, $useDefault = false)
451
    {
452
        if ($useDefault) {
453
            return [$operator, '='];
454
        } elseif ($this->invalidOperatorValue($operator, $value)) {
455
            throw new InvalidArgumentException('Illegal operator and value combination.');
456
        }
457
            
458
        return [$value, $operator];
459
    }
460
461
    /**
462
     * Determine if the given operator and value combination is legal.
463
     * Prevents using Null values with invalid operators.
464
     * 
465
     * @param  string  $operator
466
     * @param  mixed  $value
467
     * 
468
     * @return bool
469
     */
470
    protected function invalidOperatorValue($operator, $value)
471
    {
472
        return is_null($value) && in_array($operator, $this->operators) && ! in_array($operator, ['=', '<>', '!=']);
473
    }
474
    
475
    /**
476
     * Determine if the given operator is supported.
477
     * 
478
     * @param  string  $operator
479
     * 
480
     * @return bool
481
     */
482
    protected function invalidOperator($operator)
483
    {
484
        return ! in_array(strtolower($operator), $this->operators, true);
485
    }
486
487
    /**
488
     * Add a nested where statement to the query.
489
     * 
490
     * @param  \Closure  $callback
491
     * @param  string  $boolean  
492
     * 
493
     * @return $this
494
     */
495
    public function whereNested(Closure $callback, $boolean = 'and')
496
    {
497
        $query = $this->forNestedWhere();
498
499
        call_user_func($callback, $query);
500
501
        return $this->addNestedWhere($query, $boolean);
502
    }
503
504
    /**
505
     * Create a new query instance for nested where condition.
506
     * 
507
     * @return \Syscodes\Database\Query\Builder
508
     */
509
    public function forNestedWhere()
510
    {
511
        return $this->newBuilder()->from($this->from);
512
    }
513
514
    /**
515
     * Add a query builder different from the current one.
516
     * 
517
     * @param  \Syscodes\Database\Query\Builder  $query
518
     * @param  string  $boolean  
519
     * 
520
     * @return $this
521
     */
522
    public function addNestedWhere($query, $boolean = 'and')
523
    {
524
        if (count($query->wheres)) {
525
            $type = 'Nested';
526
527
            $this->wheres[] = compact('type', 'query', 'boolean');
528
529
            $this->addBinding($query->getRawBindings()['where'], 'where');
530
        }
531
532
        return $this;
533
    }
534
535
    /**
536
     * Add a full sub-select to the query.
537
     * 
538
     * @param  string  $column
539
     * @param  string  $operator
540
     * @param  \Closure  $callback
541
     * @param  string  $boolean
542
     * 
543
     * @return $this
544
     */
545
    protected function whereSub($column, $operator, Closure $callback, $boolean)
546
    {
547
        $type = 'Sub';
548
549
        call_user_func($callback, $query = $this->forSubBuilder());
550
551
        $this->wheres[] = compact(
552
            'type', 'column', 'operator', 'query', 'boolean'
553
        );
554
555
        $this->addBinding($query->getBindings(), 'where');
556
557
        return $this;
558
    }
559
560
    /**
561
     * Add a "where null" clause to the query.
562
     * 
563
     * @param  string|array  $columns
564
     * @param  string  $boolean  
565
     * @param  bool  $not  (false by default)
566
     * 
567
     * @return $this
568
     */
569
    public function whereNull($columns, $boolean = 'and', $not = false)
570
    {
571
        $type = $not ? 'NotNull' : 'Null';
572
573
        foreach (Arr::wrap($columns) as $column) {
574
            $this->wheres[] = compact('type', 'column', 'boolean');
575
        }
576
577
        return $this;
578
    }
579
580
    /**
581
     * Get the SQL representation of the query.
582
     * 
583
     * @return string
584
     */
585
    public function getSql()
586
    {
587
        return $this->grammar->compileSelect($this);
588
    }
589
590
    /**
591
     * Get a new join clause.
592
     * 
593
     * @param  string  $type
594
     * @param  string  $table
595
     * 
596
     * @return \Syscodes\Database\Query\JoinClause
597
     */
598
    protected function newJoinClause($type, $table)
599
    {
600
        return new JoinClause($type, $table);
601
    }
602
603
    /**
604
     * Set the "offset" value of the query.
605
     * 
606
     * @param  int  $value
607
     * 
608
     * @return $this
609
     */
610
    public function offset($value)
611
    {
612
        $property = $this->unions ? 'unionOffset' : 'offset';
613
614
        $this->$property = max(0, $value);
615
        
616
        return $this;
617
    }
618
619
    /**
620
     * Set the "limit" value of the query.
621
     * 
622
     * @param  int  $value
623
     * 
624
     * @return $this
625
     */
626
    public function limit($value)
627
    {
628
        $property = $this->unions ? 'unionLimit' : 'limit';
629
630
        if ($value >= 0) {
631
            $this->$property = $value;
632
        }
633
        return $this;
634
    }
635
636
    /**
637
     * Add a union statement to the query.
638
     * 
639
     * @param  \Syscodes\Database\Query\Builder|\Closure  $builder
640
     * @param  bool  $all  (false by default)
641
     * 
642
     * @return $this
643
     */
644
    public function union($builder, $all = false)
645
    {
646
        if ($builder instanceof Closure) {
647
            call_user_func($builder, $builder = $this->newBuilder());
648
        }
649
650
        $this->unions[] = compact('builder', 'all');
651
652
        $this->addBinding($builder->getBindings(), 'union');
653
654
        return $this;
655
    }
656
657
    /**
658
     * Add a union all statement to the query.
659
     * 
660
     * @param  \Syscodes\Database\Query\Builder|\Closure  $builder
661
     * 
662
     * @return $this
663
     */
664
    public function unionAll($builder)
665
    {
666
        return $this->union($builder, true);
667
    }
668
669
    /**
670
     * Lock the selected rows in the table.
671
     * 
672
     * @param  bool  $value  (true by default)
673
     * 
674
     * @return $this
675
     */
676
    public function lock($value = true)
677
    {
678
        $this->lock = $value;
679
680
        return $this;
681
    }
682
683
    /**
684
     * Lock the selected rows in the table for updating.
685
     * 
686
     * @return \Syscodes\Database\Query\Builder
687
     */
688
    public function lockRowsUpdate()
689
    {
690
        return $this->lock(true);
691
    }
692
693
    /**
694
     * Share lock the selected rows in the table.
695
     * 
696
     * @return \Syscodes\Database\Query\Builder
697
     */
698
    public function shareRowsLock()
699
    {
700
        return $this->lock(false);
701
    }
702
703
    /**
704
     * Pluck a single column's value from the first result of a query.
705
     * 
706
     * @param  string  $column
707
     * 
708
     * @return mixed
709
     */
710
    public function pluck($column)
711
    {
712
        $sql = (array) $this->first([$column]);
713
714
        return count($sql) > 0 ? reset($sql) : null;
715
    }
716
717
    /**
718
     * Execute the query and get the first result.
719
     * 
720
     * @param  array
721
     * 
722
     * @return mixed
723
     */
724
    public function first($columns = ['*'])
725
    {
726
        $results = $this->limit(1)->get($columns);
727
728
        return count($results) > 0 ? head($results) : null;
0 ignored issues
show
Bug introduced by
$results of type Syscodes\Collections\Collection is incompatible with the type array expected by parameter $array of head(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

728
        return count($results) > 0 ? head(/** @scrutinizer ignore-type */ $results) : null;
Loading history...
729
    }
730
731
    /**
732
     * Execute a query for a single record by ID.
733
     * 
734
     * @param  int|string  $id
735
     * @param  array  $columns
736
     * 
737
     * @return mixed
738
     */
739
    public function find($id, $columns = ['*'])
740
    {
741
        return $this->where('id', '=', $id)->first($columns);
742
    }
743
    
744
    /**
745
     * Execute the query as a "select" statement.
746
     * 
747
     * @param  array  $columns
748
     * 
749
     * @return \Syscodes\Collections\Collection
750
     */
751
    public function get($columns = ['*'])
752
    {
753
        return collect($this->getFresh(Arr::wrap($columns), function () {
0 ignored issues
show
Bug introduced by
function(...) { /* ... */ } of type callable is incompatible with the type callable expected by parameter $callback of Syscodes\Database\Query\Builder::getFresh(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

753
        return collect($this->getFresh(Arr::wrap($columns), /** @scrutinizer ignore-type */ function () {
Loading history...
Bug introduced by
Syscodes\Collections\Arr::wrap($columns) of type array|string[] is incompatible with the type string expected by parameter $columns of Syscodes\Database\Query\Builder::getFresh(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

753
        return collect($this->getFresh(/** @scrutinizer ignore-type */ Arr::wrap($columns), function () {
Loading history...
754
            return $this->getWithStatement();
755
        }));
756
    }
757
    
758
    /**
759
     * Execute the given callback while selecting the given columns.
760
     * 
761
     * @param  string  $columns
762
     * @param  \callable  $callback
0 ignored issues
show
Bug introduced by
The type callable was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
763
     * 
764
     * @return mixed 
765
     */
766
    protected function getFresh($columns, $callback)
767
    {
768
        $original = $this->columns;
769
770
        if (is_null($original)) {
0 ignored issues
show
introduced by
The condition is_null($original) is always false.
Loading history...
771
            $this->columns = $columns;
772
        }
773
774
        $result = $callback();
775
776
        $this->columns = $original;
777
778
        return $result;
779
    }
780
781
    /**
782
     * Execute the query with a "select" statement.
783
     * 
784
     * @return array|static[]
785
     */
786
    protected function getWithStatement()
787
    {
788
        return $this->processor->processSelect($this, $this->runOnSelectStatement());
789
    }
790
791
    /**
792
     * Run the query as a "select" statement against the connection.
793
     * 
794
     * @return array
795
     */
796
    public function runOnSelectStatement()
797
    {
798
        return $this->connection->select($this->getSql(), $this->getBindings());
799
    }
800
801
    /**
802
     * Retrieve the "count" result of the query.
803
     * 
804
     * @param  string  $columns
805
     * 
806
     * @return mixed
807
     */
808
    public function count($columns = '*')
809
    {
810
        return (int) $this->aggregate(__FUNCTION__, Arr::wrap($columns));
811
    }
812
813
    /**
814
     * Retrieve the max of the values of a given column.
815
     * 
816
     * @param  string  $column
817
     * 
818
     * @return mixed
819
     */
820
    public function max($column)
821
    {
822
        return $this->aggregate(__FUNCTION__, [$column]);
823
    }
824
825
    /**
826
     * Retrieve the min of the values of a given column.
827
     * 
828
     * @param  string  $column
829
     * 
830
     * @return mixed
831
     */
832
    public function min($column)
833
    {
834
        return $this->aggregate(__FUNCTION__, [$column]);
835
    }
836
837
    /**
838
     * Retrieve the sum of the values of a given column.
839
     * 
840
     * @param  string  $column
841
     * 
842
     * @return mixed
843
     */
844
    public function sum($column)
845
    {
846
        $result = $this->aggregate(__FUNCTION__, [$column]);
847
848
        return $result ?: 0;
849
    }
850
851
    /**
852
     * Retrieve the average of the values of a given column.
853
     * 
854
     * @param  string  $column
855
     * 
856
     * @return mixed
857
     */
858
    public function avg($column)
859
    {
860
        return $this->aggregate(__FUNCTION__, [$column]);
861
    }
862
863
    /**
864
     * Execute an aggregate function on the database.
865
     * 
866
     * @param  string  $function
867
     * @param  array  $columns
868
     * 
869
     * @return mixed
870
     */
871
    public function aggregate($function, $columns = ['*'])
872
    {
873
        $this->aggregate = compact('function', 'columns');
874
875
        $previous = $this->columns;
876
877
        $results = $this->get($columns);
878
879
        $this->aggregate = null;
880
881
        $this->columns = $previous;
882
883
        if (isset($results[0]))  {
884
            $result = array_change_key((array) $results[0]);
0 ignored issues
show
Bug introduced by
The function array_change_key was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

884
            $result = /** @scrutinizer ignore-call */ array_change_key((array) $results[0]);
Loading history...
885
        }
886
887
        return $result['aggregate'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
888
    }
889
890
    /**
891
     * Insert a new record into the database.
892
     * 
893
     * @param  array  $values
894
     * 
895
     * @return bool
896
     */
897
    public function insert(array $values)
898
    {
899
        if (empty($values)) {
900
            return true;
901
        }
902
903
        if ( ! is_array(reset($values))) {
904
            $values = [$values]; 
905
        } else {
906
            foreach ($values as $key => $value) {
907
                ksort($value);
908
909
                $values[$key] = $value;
910
            }
911
        }
912
913
        $sql = $this->grammar->compileInsert($this, $values);
914
915
        $bindings = $this->cleanBindings($this->buildInsertBinding($values));
916
917
        return $this->connection->insert($sql, $bindings);
918
    }
919
920
    /**
921
     * It insert like a batch data so we can easily insert each 
922
     * records into the database consistenly.
923
     * 
924
     * @param  array  $values
925
     */
926
    private function buildInsertBinding(array $values)
927
    {
928
        $bindings = [];
929
930
        foreach ($values as $record) {
931
            foreach ($record as $value) {
932
                $bindings[] = $value;
933
            }
934
        }
935
936
        return $bindings;
937
    }
938
939
    /**
940
     * Insert a new record and get the value of the primary key.
941
     * 
942
     * @param  array  $values
943
     * @param  string  $sequence  
944
     * 
945
     * @return int
946
     */
947
    public function insertGetId(array $values, $sequence = null)
948
    {
949
        $sql = $this->grammar->compileInsertGetId($this, $values, $sequence);
950
951
        $values = $this->cleanBindings($values);
952
953
        return $this->processor->processInsertGetId($this, $sql, $values, $sequence);
954
    }
955
956
    /**
957
     * Update a record in the database.
958
     * 
959
     * @param  array  $values
960
     * 
961
     * @return \PDOStatement
962
     */
963
    public function update(array $values)
964
    {
965
        $bindings = array_values(array_merge($values, $this->bindings));
966
967
        $sql = $this->grammar->compileUpdate($this, $values);
968
969
        return $this->connection->query($sql, $this->cleanBindings($bindings));
0 ignored issues
show
Unused Code introduced by
The call to Syscodes\Database\ConnectionInterface::query() has too many arguments starting with $sql. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

969
        return $this->connection->/** @scrutinizer ignore-call */ query($sql, $this->cleanBindings($bindings));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
970
    }
971
972
    /**
973
     * Increment a column's value by a given amount.
974
     * 
975
     * @param  string  $column
976
     * @param  int  $amount  
977
     * @param  array  $extra
978
     * 
979
     * @return \PDOStatement
980
     */
981
    public function increment($column, $amount = 1, array $extra = [])
982
    {
983
        if ( ! is_numeric($amount)) {
0 ignored issues
show
introduced by
The condition is_numeric($amount) is always true.
Loading history...
984
            throw new InvalidArgumentException("Non-numeric value passed to increment method");
985
        }
986
987
        $wrapped = $this->grammar->wrap($column);
988
989
        $columns = array_merge([$column => $this->raw("$wrapped + $amount"), $extra]);
990
991
        return $this->update($columns);
992
    }
993
994
    /**
995
     * Decrement a column's value by a given amount.
996
     * 
997
     * @param  string  $column
998
     * @param  int  $amount  
999
     * @param  array  $extra
1000
     * 
1001
     * @return \PDOStatement
1002
     */
1003
    public function decrement($column, $amount = 1, array $extra = [])
1004
    {
1005
        if ( ! is_numeric($amount)) {
0 ignored issues
show
introduced by
The condition is_numeric($amount) is always true.
Loading history...
1006
            throw new InvalidArgumentException("Non-numeric value passed to decrement method");
1007
        }
1008
1009
        $wrapped = $this->grammar->wrap($column);
1010
1011
        $columns = array_merge([$column => $this->raw("$wrapped - $amount"), $extra]);
1012
1013
        return $this->update($columns);
1014
    }
1015
1016
    /**
1017
     * Get run a truncate statment on the table.
1018
     * 
1019
     * @return void
1020
     */
1021
    public function truncate()
1022
    {
1023
        foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) {
1024
            $this->connection->query($sql, $bindings);
0 ignored issues
show
Unused Code introduced by
The call to Syscodes\Database\ConnectionInterface::query() has too many arguments starting with $sql. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1024
            $this->connection->/** @scrutinizer ignore-call */ 
1025
                               query($sql, $bindings);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1025
        }
1026
    }
1027
1028
    /**
1029
     * Add a "group by" clause to the query.
1030
     * 
1031
     * @param  array|string  ...$groups
1032
     * 
1033
     * @return $this
1034
     */
1035
    public function groupBy(...$groups)
1036
    {
1037
        foreach ($groups as $group) {
1038
            $this->groups = array_merge(
1039
                (array) $this->groups,
1040
                Arr::wrap($group)
1041
            );
1042
        }
1043
1044
        return $this;
1045
    }
1046
1047
    /**
1048
     * Add a "having" clause to the query.
1049
     *
1050
     * @param  string  $column
1051
     * @param  string|null  $operator
1052
     * @param  string|null  $value
1053
     * @param  string  $boolean
1054
     * @return $this
1055
     */
1056
    public function having($column, $operator = null, $value = null, $boolean = 'and')
1057
    {
1058
        $type = 'basic';
1059
1060
        $this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean');
1061
1062
        if ($value instanceof Expression) {
1063
            $this->addBinding($value, 'having');
1064
        }
1065
1066
        return $this;
1067
    }
1068
    
1069
    /**
1070
     * Add an "order by" clause to the query.
1071
     * 
1072
     * @param  string  $column
1073
     * @param  string  $direction 
1074
     * 
1075
     * @return $this
1076
     */
1077
    public function orderBy($column, $direction = 'asc')
1078
    {
1079
        $property = $this->unions ? 'unionOrders' : 'orders';
1080
        
1081
        $direction = strtolower($direction);
1082
        
1083
        if ( ! in_array($direction, ['asc', 'desc'], true)) {
1084
            throw new InvalidArgumentException('Order direction must be "asc" or "desc"');
1085
        }
1086
        
1087
        $this->{$property}[] = [
1088
            'column' => $column,
1089
            'direction' => $direction,
1090
        ];
1091
        
1092
        return $this;
1093
    }
1094
1095
    /**
1096
     * Add a descending "order by" clause to the query.
1097
     * 
1098
     * @param  string  $column
1099
     * 
1100
     * @return $this
1101
     */
1102
    public function orderByDesc($column)
1103
    {
1104
        return $this->orderBy($column, 'desc');
1105
    }
1106
1107
    /**
1108
     * Add an "order by" clause for a timestamp to the query.
1109
     * 
1110
     * @param  string  $column  (created_at by default)
1111
     * 
1112
     * @return $this
1113
     */
1114
    public function latest($column = 'created_at')
1115
    {
1116
        return $this->orderBy($column, 'desc');
1117
    }
1118
1119
    /**
1120
     * Add an "order by" clause for a timestamp to the query.
1121
     * 
1122
     * @param  string  $column  (created_at by default)
1123
     * 
1124
     * @return $this
1125
     */
1126
    public function oldest($column = 'created_at')
1127
    {
1128
        return $this->orderBy($column, 'asc');
1129
    }
1130
1131
    /**
1132
     * Add a raw "order by" clause to the query.
1133
     * 
1134
     * @param  string  $sql
1135
     * @param  array  $bindings
1136
     * 
1137
     * @return $this
1138
     */
1139
    public function orderByRaw($sql, $bindings = [])
1140
    {
1141
        $type = 'Raw';
1142
1143
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'sql');
1144
1145
        $this->addBinding($bindings, $this->unions ? 'unionOrder' : 'order');
1146
1147
        return $this;
1148
    }
1149
1150
    /**
1151
     * Create a raw database expression.
1152
     *
1153
     * @param  mixed  $value
1154
     * 
1155
     * @return \Syscodes\Database\Query\Expression
1156
     */
1157
    public function raw($value)
1158
    {
1159
        return $this->connection->raw($value);
1160
    }
1161
1162
    /**
1163
     * Get a new instance of the query builder.
1164
     * 
1165
     * @return \Syscodes\Database\Query\Builder
1166
     */
1167
    public function newBuilder()
1168
    {
1169
        return new static($this->connection, $this->grammar, $this->processor);
1170
    }
1171
1172
    /**
1173
     * Create a new Builder instance for a sub-Builder.
1174
     * 
1175
     * @return \Syscodes\Database\Query\Builder
1176
     */
1177
    protected function forSubBuilder()
1178
    {
1179
        return $this->newBuilder();
1180
    }
1181
1182
    /**
1183
     * Remove all of the expressions from a lists of bindings.
1184
     * 
1185
     * @param  array  $bindings
1186
     * 
1187
     * @return array
1188
     */
1189
    public function cleanBindings(array $bindings)
1190
    {
1191
        return array_values(array_filter($bindings, function ($binding) {
1192
            return ! $binding instanceof Expression;
1193
        }));
1194
    }
1195
1196
    /**
1197
     * Get the current query value bindings in a flattened array.
1198
     * 
1199
     * @return array
1200
     */
1201
    public function getBindings()
1202
    {
1203
        return Arr::Flatten($this->bindings);
1204
    }
1205
1206
    /**
1207
     * Get the raw array of bindings.
1208
     * 
1209
     * @return array
1210
     */
1211
    public function getRawBindings()
1212
    {
1213
        return $this->bindings;
1214
    }
1215
1216
    /**
1217
     * /**
1218
     * Set the bindings on the query sql.
1219
     * 
1220
     * @param  mixed  $value
1221
     * @param  string  $type  
1222
     * 
1223
     * @return $this
1224
     * 
1225
     * @throws \InvalidArgumentException
1226
     */
1227
    public function setBindings($value, $type = 'where')
1228
    {
1229
        if ( ! array_key_exists($type, $this->bindings)) {
1230
            throw new InvalidArgumentException("Invalid binding type: {$type}");
1231
        }
1232
1233
        $this->bindings[$type] = $value;
1234
1235
        return $this;
1236
    }
1237
1238
    /**
1239
     * Add a binding to the query sql.
1240
     * 
1241
     * @param  mixed  $value
1242
     * @param  string  $type  
1243
     * 
1244
     * @return $this
1245
     * 
1246
     * @throws \InvalidArgumentException
1247
     */
1248
    public function addBinding($value, $type = 'where')
1249
    {
1250
        if ( ! array_key_exists($type, $this->bindings)) {
1251
            throw new InvalidArgumentException("Invalid binding type: {$type}");
1252
        }
1253
1254
        if (is_array($value)) {
1255
            $this->bindings[$type] = array_values(array_merge($this->bindings[$type], $value));
1256
        } else {
1257
            $this->bindings[$type][] = $value;
1258
        }
1259
1260
        return $this;
1261
    }
1262
1263
    /**
1264
     * Merge an array of bindings into our bindings.
1265
     * 
1266
     * @param  \Syscodes\Database\Query\Builder  $builder
1267
     * 
1268
     * @return $this
1269
     */
1270
    public function mergeBindings(Builder $builder)
1271
    {
1272
        $this->bindings = array_merge_recursive($this->bindings, $builder->bindings);
1273
1274
        return $this;
1275
    }
1276
1277
    /**
1278
     * Get the database connection instance.
1279
     * 
1280
     * @return \Syscodes\Database\ConnectionInterface
1281
     */
1282
    public function getConnection()
1283
    {
1284
        return $this->connection;
1285
    }
1286
1287
    /**
1288
     * Get the database query processor instance.
1289
     * 
1290
     * @return \Syscodes\Database\Query\Processor
1291
     */
1292
    public function getQueryProcessor()
1293
    {
1294
        return $this->processor;
1295
    }
1296
1297
    /**
1298
     * Get the database query grammar instance.
1299
     * 
1300
     * @return \Syscodes\Database\Query\Grammar
1301
     */
1302
    public function getQueryGrammar()
1303
    {
1304
        return $this->grammar;
1305
    }
1306
1307
    /**
1308
     * Die and dump the current SQL and bindings.
1309
     * 
1310
     * @return void
1311
     */
1312
    public function dd()
1313
    {
1314
        dd($this->getSql(), $this->getBindings());
1315
    }
1316
1317
    /**
1318
     * Dynamically handle calls to methods on the class.
1319
     * 
1320
     * @param  string  $method
1321
     * @param  array  $parameters
1322
     * 
1323
     * @return mixed
1324
     * 
1325
     * @throws \BadMethodCallException
1326
     */
1327
    public function __call($method, $parameters)
1328
    {
1329
        $classname = get_class($this);
1330
1331
        throw new BadMethodCallException("Call to undefined method {$classname}::{$method}()");
1332
    }
1333
}