GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 3121b4...19602e )
by Steeven
02:46
created

AbstractQueryBuilder::countAll()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 44
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 1
Metric Value
eloc 23
c 4
b 1
f 1
dl 0
loc 44
rs 8.9297
cc 6
nc 8
nop 1
1
<?php
2
/**
3
 * This file is part of the O2System Framework package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @author         Steeve Andrian Salim
9
 * @copyright      Copyright (c) Steeve Andrian Salim
10
 */
11
12
// ------------------------------------------------------------------------
13
14
namespace O2System\Database\Sql\Abstracts;
15
16
// ------------------------------------------------------------------------
17
18
use O2System\Database\DataObjects\Result;
19
use O2System\Database\Sql\DataStructures\Query;
20
use O2System\Spl\Exceptions\RuntimeException;
21
22
/**
23
 * Class AbstractQueryBuilder
24
 *
25
 * @package O2System\Database\Sql\Abstracts
26
 */
27
abstract class AbstractQueryBuilder
28
{
29
    /**
30
     * AbstractQueryBuilder::testMode
31
     *
32
     * If true, no queries will actually be
33
     * ran against the database.
34
     *
35
     * @var bool
36
     */
37
    public $testMode = false;
38
39
    /**
40
     * AbstractQueryBuilder::testMode
41
     *
42
     * If true, no queries will actually be
43
     * ran against the database.
44
     *
45
     * @var bool
46
     */
47
    public $cacheMode = false;
48
49
    /**
50
     * AbstractQueryBuilder::$conn
51
     *
52
     * Query Builder database connection instance.
53
     *
54
     * @var AbstractConnection
55
     */
56
    protected $conn;
57
58
    /**
59
     * AbstractQueryBuilder::$builderCache
60
     *
61
     * Query builder cache instance.
62
     *
63
     * @var QueryBuilderCache
0 ignored issues
show
Bug introduced by
The type O2System\Database\Sql\Abstracts\QueryBuilderCache 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...
64
     */
65
    protected $builderCache;
66
67
    /**
68
     * AbstractQueryBuilder::$arrayObjectConversionMethod
69
     *
70
     * Query Builder insert, update array object value conversion method.
71
     *
72
     * @var string
73
     */
74
    protected $arrayObjectConversionMethod = 'json_encode';
75
76
    /**
77
     * AbstractQueryBuilder::$SqlRandomKeywords
78
     *
79
     * ORDER BY random keyword list.
80
     *
81
     * @var array
82
     */
83
    protected $SqlOrderByRandomKeywords = ['RAND()', 'RAND(%d)'];
84
85
    /**
86
     * AbstractQueryBuilder::isSubQuery
87
     *
88
     * Is Sub Query instance flag.
89
     *
90
     * @var bool
91
     */
92
    protected $isSubQuery = false;
93
94
    // ------------------------------------------------------------------------
95
96
    /**
97
     * AbstractQueryBuilder::__construct.
98
     *
99
     * @param \O2System\Database\Sql\Abstracts\AbstractConnection $conn
100
     */
101
    public function __construct(AbstractConnection &$conn)
102
    {
103
        $this->conn =& $conn;
104
        $this->builderCache = new Query\BuilderCache();
0 ignored issues
show
Documentation Bug introduced by
It seems like new O2System\Database\Sq...es\Query\BuilderCache() of type O2System\Database\Sql\Da...ures\Query\BuilderCache is incompatible with the declared type O2System\Database\Sql\Abstracts\QueryBuilderCache of property $builderCache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
105
        $this->cacheMode = $this->conn->getConfig('cacheEnable');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->conn->getConfig('cacheEnable') can also be of type array. However, the property $cacheMode is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
106
    }
107
108
    // ------------------------------------------------------------------------
109
110
    /**
111
     * AbstractQueryBuilder::cache
112
     *
113
     * @param boolean $mode
114
     *
115
     * @return static
116
     */
117
    public function cache($mode = true)
118
    {
119
        $this->cacheEnable = (bool)$mode;
0 ignored issues
show
Bug Best Practice introduced by
The property cacheEnable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
120
121
        return $this;
122
    }
123
124
    // ------------------------------------------------------------------------
125
126
    /**
127
     * AbstractQueryBuilder::distinct
128
     *
129
     * Sets a flag which tells the query string compiler to add DISTINCT
130
     * keyword on SELECT statement
131
     *
132
     * @param bool $distinct
133
     *
134
     * @return    static
135
     */
136
    public function distinct($distinct = true)
137
    {
138
        $this->builderCache->distinct = is_bool($distinct)
0 ignored issues
show
introduced by
The condition is_bool($distinct) is always true.
Loading history...
139
            ? $distinct
140
            : true;
141
142
        return $this;
143
    }
144
145
    // ------------------------------------------------------------------------
146
147
    /**
148
     * AbstractQueryBuilder::into
149
     *
150
     * Add SELECT INTO Sql statement portions into Query Builder.
151
     *
152
     * @param string      $table    Table name
153
     * @param string|null $database Other database name
154
     *
155
     * @return static
156
     */
157
    public function into($table, $database = null)
158
    {
159
        $this->builderCache->into = $this->conn->protectIdentifiers(
160
            $table
161
        ) . empty($database)
162
            ? ''
163
            : ' IN ' . $this->conn->escape($database);
164
165
        return $this;
166
    }
167
168
    //--------------------------------------------------------------------
169
170
    /**
171
     * AbstractQueryBuilder::union
172
     *
173
     * Add SELECT UNION Sql statement portions into Query Builder.
174
     *
175
     * @param \O2System\Database\Sql\Abstracts\AbstractQueryBuilder $select
176
     * @param bool                                                  $isUnionAll
177
     *
178
     * @return static
179
     */
180
    public function union(AbstractQueryBuilder $select, $isUnionAll = false)
181
    {
182
        $this->builderCache->store(($isUnionAll
183
            ? 'union_all'
184
            : 'union'), $select->getSqlStatement());
185
186
        return $this;
187
    }
188
189
    // ------------------------------------------------------------------------
190
191
    /**
192
     * AbstractQueryBuilder::getSqlStatement
193
     *
194
     * Gets Sql statement.
195
     *
196
     * @param bool $reset If sets TRUE the Query Builder cache will be reset.
197
     *
198
     * @return    string
199
     */
200
    public function getSqlStatement($reset = true)
201
    {
202
        $sqlStatementsSequence = [
203
            'Select',
204
            'Union',
205
            'Into',
206
            'From',
207
            'Join',
208
            'Where',
209
            'Having',
210
            'GroupBy',
211
            'OrderBy',
212
            'Limit',
213
        ];
214
215
        if (empty($this->builderCache->getStatement())) {
216
            $sqlStatement = [];
217
            foreach ($sqlStatementsSequence as $compileMethod) {
218
                $sqlStatement[] = trim(call_user_func([&$this, 'compile' . $compileMethod . 'Statement']));
219
            }
220
221
            $sqlStatement = implode(PHP_EOL, array_filter($sqlStatement));
222
223
            if ($reset) {
224
                $this->builderCache->reset();
225
226
                return $sqlStatement;
227
            }
228
229
            $this->builderCache->setStatement($sqlStatement);
230
        }
231
232
        return $this->builderCache->getStatement();
233
    }
234
235
    // ------------------------------------------------------------------------
236
237
    /**
238
     * AbstractQueryBuilder::first
239
     *
240
     * Add SELECT FIRST(field) AS alias statement
241
     *
242
     * @param string $field Input name
243
     * @param string $alias Input alias
244
     *
245
     * @return static
246
     */
247
    public function first($field, $alias = '')
248
    {
249
        return $this->prepareAggregateStatement($field, $alias, 'FIRST');
250
    }
251
252
    // ------------------------------------------------------------------------
253
254
    /**
255
     * AbstractQueryBuilder::prepareAggregateStatement
256
     *
257
     * Prepare string of Sql Aggregate Functions statement
258
     *
259
     * @param string $field Input name
260
     * @param string $alias Input alias
261
     * @param string $type  AVG|COUNT|FIRST|LAST|MAX|MIN|SUM
262
     *
263
     * @return static
264
     */
265
    protected function prepareAggregateStatement($field = '', $alias = '', $type = '')
266
    {
267
        $SqlAggregateFunctions = [
268
            'AVG'   => 'AVG(%s)', // Returns the average value
269
            'COUNT' => 'COUNT(%s)', // Returns the number of rows
270
            'FIRST' => 'FIRST(%s)', // Returns the first value
271
            'LAST'  => 'LAST(%s)', // Returns the largest value
272
            'MAX'   => 'MAX(%s)', // Returns the largest value
273
            'MIN'   => 'MIN(%s)', // Returns the smallest value
274
            'SUM'   => 'SUM(%s)' // Returns the sum
275
        ];
276
277
        if ($field !== '*' && $this->conn->protectIdentifiers) {
278
            $field = $this->conn->protectIdentifiers($field);
279
        }
280
281
        $alias = empty($alias)
282
            ? strtolower($type) . '_' . $field
283
            : $alias;
284
        $sqlStatement = sprintf($SqlAggregateFunctions[ $type ], $field)
285
            . ' AS '
286
            . $this->conn->escapeIdentifiers($alias);
287
288
        $this->select($sqlStatement);
289
290
        return $this;
291
    }
292
293
    // ------------------------------------------------------------------------
294
295
    /**
296
     * AbstractQueryBuilder::select
297
     *
298
     * Add SELECT Sql statement portions into Query Builder.
299
     *
300
     * @param string|array $field        String of field name
301
     *                                   Array list of string field names
302
     *                                   Array list of static
303
     * @param null|bool    $escape       Whether not to try to escape identifiers
304
     *
305
     * @return static
306
     */
307
    public function select($field = '*', $escape = null)
308
    {
309
        // If the escape value was not set, we will base it on the global setting
310
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
311
312
        if (is_string($field)) {
313
            $field = str_replace(' as ', ' AS ', $field);
314
315
            if (strpos($field, '+') !== false || strpos($field, '(') !== false) {
316
                $field = [$field];
317
            } else {
318
                $field = explode(',', $field);
319
            }
320
321
            foreach ($field as $name) {
322
                $name = trim($name);
323
324
                $this->builderCache->select[] = $name;
325
                $this->builderCache->noEscape[] = $escape;
326
            }
327
        } elseif (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
328
            foreach ($field as $fieldName => $fieldAlias) {
329
                if (is_numeric($fieldName)) {
330
                    $fieldName = $fieldAlias;
331
                } elseif (is_string($fieldName)) {
332
                    if (is_string($fieldAlias)) {
333
                        $fieldName = $fieldName . ' AS ' . $fieldAlias;
334
                    } elseif (is_array($fieldAlias)) {
335
                        $countFieldAlias = count($fieldAlias);
336
337
                        for ($i = 0; $i < $countFieldAlias; $i++) {
338
                            if ($i == 0) {
339
                                $fieldAlias[ $i ] = $fieldAlias[ $i ] . "'+";
340
                            } elseif ($i == ($countFieldAlias - 1)) {
341
                                $fieldAlias[ $i ] = "'+" . $fieldAlias[ $i ];
342
                            } else {
343
                                $fieldAlias[ $i ] = "'+" . $fieldAlias[ $i ] . "'+";
344
                            }
345
                        }
346
347
                        $fieldName = implode(', ', $fieldAlias) . ' AS ' . $fieldName;
348
                    } elseif ($fieldAlias instanceof AbstractQueryBuilder) {
349
                        $fieldName = '( ' . $fieldAlias->getSqlStatement() . ' ) AS ' . $fieldName;
350
                    }
351
                }
352
353
                $this->select($fieldName, $escape);
354
            }
355
        }
356
357
        return $this;
358
    }
359
360
    //--------------------------------------------------------------------
361
362
    /**
363
     * AbstractQueryBuilder::last
364
     *
365
     * Add SELECT LAST(field) AS alias statement
366
     *
367
     * @param string $field Input name
368
     * @param string $alias Input alias
369
     *
370
     * @return static
371
     */
372
    public function last($field, $alias = '')
373
    {
374
        return $this->prepareAggregateStatement($field, $alias, 'LAST');
375
    }
376
377
    // ------------------------------------------------------------------------
378
379
    /**
380
     * AbstractQueryBuilder::avg
381
     *
382
     * Add SELECT AVG(field) AS alias statement
383
     *
384
     * @param string $field Input name
385
     * @param string $alias Input alias
386
     *
387
     * @return static
388
     */
389
    public function avg($field, $alias = '')
390
    {
391
        return $this->prepareAggregateStatement($field, $alias, 'AVG');
392
    }
393
394
    // ------------------------------------------------------------------------
395
396
    /**
397
     * AbstractQueryBuilder::max
398
     *
399
     * Add SELECT MAX(field) AS alias statement
400
     *
401
     * @param string $field Input name
402
     * @param string $alias Input alias
403
     *
404
     * @return static
405
     */
406
    public function max($field, $alias = '')
407
    {
408
        return $this->prepareAggregateStatement($field, $alias, 'MAX');
409
    }
410
411
    // ------------------------------------------------------------------------
412
413
    /**
414
     * AbstractQueryBuilder::min
415
     *
416
     * Add SELECT MIN(field) AS alias statement
417
     *
418
     * @param string $field Input name
419
     * @param string $alias Input alias
420
     *
421
     * @return static
422
     */
423
    public function min($field, $alias = '')
424
    {
425
        return $this->prepareAggregateStatement($field, $alias, 'MIN');
426
    }
427
428
    // ------------------------------------------------------------------------
429
430
    /**
431
     * AbstractQueryBuilder::sum
432
     *
433
     * Add SELECT SUM(field) AS alias statement
434
     *
435
     * @param string $field Input name
436
     * @param string $alias Input alias
437
     *
438
     * @return static
439
     */
440
    public function sum($field, $alias = '')
441
    {
442
        return $this->prepareAggregateStatement($field, $alias, 'SUM');
443
    }
444
445
    // ------------------------------------------------------------------------
446
447
    /**
448
     * AbstractQueryBuilder::ucase
449
     *
450
     * Add SELECT UCASE(field) AS alias statement
451
     *
452
     * @see http://www.w3schools.com/Sql/Sql_func_ucase.asp
453
     *
454
     * @param string $field Input name
455
     * @param string $alias Input alias
456
     *
457
     * @return static
458
     */
459
    public function ucase($field, $alias = '')
460
    {
461
        return $this->prepareScalarStatement($field, $alias, 'UCASE');
462
    }
463
464
    // ------------------------------------------------------------------------
465
466
    /**
467
     * AbstractQueryBuilder::prepareScalarStatement
468
     *
469
     * Prepare string of Sql Scalar Functions statement
470
     *
471
     * @param string $field Input name
472
     * @param string $alias Input alias
473
     * @param string $type  UCASE|LCASE|MID|LEN|ROUND|FORMAT
474
     *
475
     * @return static
476
     */
477
    protected function prepareScalarStatement($field = '', $alias = '', $type = '')
478
    {
479
        $SqlScalarFunctions = [
480
            'UCASE'  => 'UCASE(%s)', // Converts a field to uppercase
481
            'LCASE'  => 'LCASE(%s)', // Converts a field to lowercase
482
            'LENGTH' => 'LENGTH(%s)', // Returns the length of a text field
483
        ];
484
485
        $alias = $alias === ''
486
            ? strtolower($type) . '_' . $field
487
            : $alias;
488
489
        if ($field !== '*' && $this->conn->protectIdentifiers) {
490
            $field = $this->conn->protectIdentifiers($field, true);
491
        }
492
493
        $this->select(
494
            sprintf(
495
                $SqlScalarFunctions[ $type ],
496
                $field,
497
                $this->conn->escapeIdentifiers($alias)
498
            )
499
        );
500
501
        return $this;
502
    }
503
504
    // ------------------------------------------------------------------------
505
506
    /**
507
     * AbstractQueryBuilder::lcase
508
     *
509
     * Add SELECT LCASE(field) AS alias statement
510
     *
511
     * @see http://www.w3schools.com/Sql/Sql_func_lcase.asp
512
     *
513
     * @param string $field Input name
514
     * @param string $alias Input alias
515
     *
516
     * @return static
517
     */
518
    public function lcase($field, $alias = '')
519
    {
520
        return $this->prepareScalarStatement($field, $alias, 'LCASE');
521
    }
522
523
    // ------------------------------------------------------------------------
524
525
    /**
526
     * AbstractQueryBuilder::mid
527
     *
528
     * Add SELECT MID(field) AS alias statement
529
     *
530
     * @see http://www.w3schools.com/Sql/Sql_func_mid.asp
531
     *
532
     * @param string   $field             Required. The field to extract characters from
533
     * @param int      $start             Required. Specifies the starting position (starts at 1)
534
     * @param null|int $length            Optional. The number of characters to return. If omitted, the MID() function
535
     *                                    returns the rest of the text
536
     * @param string   $alias             Input alias
537
     *
538
     * @return static
539
     */
540
    public function mid($field, $start = 1, $length = null, $alias = '')
541
    {
542
        if ($this->conn->protectIdentifiers) {
543
            $field = $this->conn->protectIdentifiers($field, true);
544
        }
545
546
        $fields = [
547
            $field,
548
            $start,
549
        ];
550
551
        if (isset($length)) {
552
            array_push($fields, intval($length));
553
        }
554
555
        $this->select(
556
            sprintf(
557
                'MID(%s)', // Extract characters from a text field
558
                implode(',', $fields)
559
            )
560
            . ' AS '
561
            . $this->conn->escapeIdentifiers(
562
                empty($alias)
563
                    ? 'mid_' . $field
564
                    : $alias
565
            )
566
        );
567
568
        return $this;
569
    }
570
571
    // ------------------------------------------------------------------------
572
573
    /**
574
     * AbstractQueryBuilder::len
575
     *
576
     * Add SELECT LEN(field) AS alias statement
577
     *
578
     * @see http://www.w3schools.com/Sql/Sql_func_len.asp
579
     *
580
     * @param string $field Input name
581
     * @param string $alias Input alias
582
     *
583
     * @return static
584
     */
585
    public function len($field, $alias = '')
586
    {
587
        return $this->prepareScalarStatement($field, $alias, 'LENGTH');
588
    }
589
590
    // ------------------------------------------------------------------------
591
592
    /**
593
     * AbstractQueryBuilder::round
594
     *
595
     * Add SELECT ROUND(field) AS alias statement
596
     *
597
     * @see http://www.w3schools.com/Sql/Sql_func_round.asp
598
     *
599
     * @param string $field    Required. The field to round.
600
     * @param int    $decimals Required. Specifies the number of decimals to be returned.
601
     * @param string $alias    Input alias
602
     *
603
     * @return static
604
     */
605
    public function round($field, $decimals = 0, $alias = '')
606
    {
607
        $this->select(
608
            sprintf(
609
                'ROUND(%s, %s)', // Rounds a numeric field to the number of decimals specified
610
                $field,
611
                $decimals
612
            )
613
            . ' AS '
614
            . $this->conn->escapeIdentifiers(
615
                empty($alias)
616
                    ? 'mid_' . $field
617
                    : $alias
618
            )
619
        );
620
621
        return $this;
622
    }
623
624
    // ------------------------------------------------------------------------
625
626
    /**
627
     * AbstractQueryBuilder::format
628
     *
629
     * Add SELECT FORMAT(field, format) AS alias statement
630
     *
631
     * @see http://www.w3schools.com/Sql/Sql_func_format.asp
632
     *
633
     * @param string $field  Input name.
634
     * @param string $format Input format.
635
     * @param string $alias  Input alias.
636
     *
637
     * @return static
638
     */
639
    public function format($field, $format, $alias = '')
640
    {
641
        $this->select(
642
            sprintf(
643
                'FORMAT(%s, %s)', // Formats how a field is to be displayed
644
                $field,
645
                $format
646
            )
647
            . ' AS '
648
            . $this->conn->escapeIdentifiers(
649
                empty($alias)
650
                    ? 'mid_' . $field
651
                    : $alias
652
            )
653
        );
654
655
        return $this;
656
    }
657
658
    // ------------------------------------------------------------------------
659
660
    /**
661
     * AbstractQueryBuilder::now
662
     *
663
     * Add / Create SELECT NOW() Sql statement.
664
     *
665
     * @return static
666
     */
667
    public function now()
668
    {
669
        $this->select('NOW()'); // Returns the current date and time
670
671
        return $this;
672
    }
673
674
    // ------------------------------------------------------------------------
675
676
    /**
677
     * AbstractQueryBuilder::extract
678
     *
679
     * Add / Create SELECT EXTRACT(unit FROM field) AS alias Sql statement
680
     *
681
     * @see http://www.w3schools.com/Sql/func_extract.asp
682
     *
683
     * @param string $field Input name
684
     * @param string $unit  UPPERCASE unit value
685
     * @param string $alias Alias field name.
686
     *
687
     * @return static|string
688
     */
689
    public function dateExtract($field, $unit, $alias = '')
690
    {
691
        $unit = strtoupper($unit);
692
693
        if (in_array($unit, $this->getDateTypes())) {
694
695
            $fieldName = $field;
696
            $fieldAlias = $alias;
697
698
            if (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always false.
Loading history...
699
                $fieldName = key($field);
700
                $fieldAlias = $field[ $fieldName ];
701
            } elseif (strpos($field, ' AS ') !== false) {
702
                $xField = explode(' AS ', $field);
703
                $xField = array_map('trim', $xField);
704
705
                @list($fieldName, $fieldAlias) = $xField;
706
            } elseif (strpos($field, ' as ') !== false) {
707
                $xField = explode(' as ', $field);
708
                $xField = array_map('trim', $xField);
709
710
                @list($fieldName, $fieldAlias) = $xField;
711
            }
712
713
            if (strpos($fieldName, '.') !== false AND empty($fieldAlias)) {
714
                $xFieldName = explode('.', $fieldName);
715
                $xFieldName = array_map('trim', $xFieldName);
716
717
                $fieldAlias = end($xFieldName);
718
            }
719
720
            $sqlStatement = sprintf(
721
                    'EXTRACT(%s FROM %s)', // Returns a single part of a date/time
722
                    $unit,
723
                    $this->conn->protectIdentifiers($fieldName)
724
                ) . ' AS ' . $this->conn->escapeIdentifiers($fieldAlias);
0 ignored issues
show
Bug introduced by
Are you sure $this->conn->escapeIdentifiers($fieldAlias) of type array|mixed can be used in concatenation? ( Ignorable by Annotation )

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

724
                ) . ' AS ' . /** @scrutinizer ignore-type */ $this->conn->escapeIdentifiers($fieldAlias);
Loading history...
725
726
            $this->select($sqlStatement);
727
        }
728
729
        return $this;
730
    }
731
732
    // ------------------------------------------------------------------------
733
734
    /**
735
     * AbstractQueryBuilder::getDateTypes
736
     *
737
     * Gets Generic Sql Date Types.
738
     *
739
     * @return array
740
     */
741
    protected function getDateTypes()
742
    {
743
        return [
744
            'MICROSECOND',
745
            'SECOND',
746
            'MINUTE',
747
            'HOUR',
748
            'DAY',
749
            'WEEK',
750
            'MONTH',
751
            'QUARTER',
752
            'YEAR',
753
            'SECOND_MICROSECOND',
754
            'MINUTE_MICROSECOND',
755
            'MINUTE_SECOND',
756
            'HOUR_MICROSECOND',
757
            'HOUR_SECOND',
758
            'HOUR_MINUTE',
759
            'DAY_MICROSECOND',
760
            'DAY_SECOND',
761
            'DAY_MINUTE',
762
            'DAY_HOUR',
763
            'YEAR_MONTH',
764
        ];
765
    }
766
767
    // ------------------------------------------------------------------------
768
769
    /**
770
     * AbstractQueryBuilder::date
771
     *
772
     * Add / Create SELECT DATE(field) AS alias Sql statement
773
     *
774
     * @see http://www.w3schools.com/Sql/func_date.asp
775
     *
776
     * @param string $field Input name
777
     * @param string $alias Input name alias
778
     *
779
     * @return static|string
780
     */
781
    public function date($field, $alias = '')
782
    {
783
        $this->select(
784
            sprintf(
785
                'DATE(%s)', // Extracts the date part of a date or date/time expression
786
                $field
787
            )
788
            . ' AS '
789
            . $this->conn->escapeIdentifiers(
790
                empty($alias)
791
                    ? 'mid_' . $field
792
                    : $alias
793
            )
794
        );
795
796
        return $this;
797
    }
798
799
    // ------------------------------------------------------------------------
800
801
    /**
802
     * AbstractQueryBuilder::dateAdd
803
     *
804
     * Add / Create SELECT DATE_ADD(field, INTERVAL expression type) AS alias Sql statement
805
     *
806
     * @see http://www.w3schools.com/Sql/func_date.asp
807
     *
808
     * @param string $field    Input name
809
     * @param string $interval Number of interval expression
810
     * @param string $alias    Input alias
811
     *
812
     * @return string|static
813
     */
814
    public function dateAdd($field, $interval, $alias = '')
815
    {
816
        if ($this->hasDateType($interval)) {
817
818
            $this->select(
819
                sprintf(
820
                    'DATE_ADD(%s, INTERVAL %s)', // Adds a specified time interval to a date
821
                    $field,
822
                    $interval
823
                )
824
                . ' AS '
825
                . $this->conn->escapeIdentifiers(
826
                    empty($alias)
827
                        ? 'date_add_' . $field
828
                        : $alias
829
                )
830
            );
831
832
        }
833
834
        return $this;
835
    }
836
837
    //--------------------------------------------------------------------
838
839
    /**
840
     * AbstractQueryBuilder::hasDateType
841
     *
842
     * Validate whether the string has an Sql Date unit type
843
     *
844
     * @param $string
845
     *
846
     * @return bool
847
     */
848
    protected function hasDateType($string)
849
    {
850
        return (bool)preg_match(
851
            '/(' . implode('|\s', $this->getDateTypes()) . '\s*\(|\s)/i',
852
            trim($string)
853
        );
854
    }
855
856
    //--------------------------------------------------------------------
857
858
    /**
859
     * AbstractQueryBuilder::dateSub
860
     *
861
     * Add / Create SELECT DATE_SUB(field, INTERVAL expression type) AS alias Sql statement
862
     *
863
     * @see http://www.w3schools.com/Sql/func_date.asp
864
     *
865
     * @param string $field    Input name
866
     * @param string $interval Number of interval expression
867
     * @param string $alias    Input alias
868
     *
869
     * @return static|string
870
     */
871
    public function dateSub($field, $interval, $alias = '')
872
    {
873
        $this->select(
874
            sprintf(
875
                'DATE_SUB(%s, INTERVAL %s)', // Subtracts a specified time interval from a date
876
                $field,
877
                $interval
878
            )
879
            . ' AS '
880
            . $this->conn->escapeIdentifiers(
881
                empty($alias)
882
                    ? 'date_sub_' . $field
883
                    : $alias
884
            )
885
        );
886
887
        return $this;
888
    }
889
890
    //--------------------------------------------------------------------
891
892
    /**
893
     * AbstractQueryBuilder::dateDiff
894
     *
895
     * Add / Create SELECT DATEDIFF(datetime_start, datetime_end) AS alias Sql statement
896
     *
897
     * @see http://www.w3schools.com/Sql/func_datediff_mySql.asp
898
     *
899
     * @param array       $fields [datetime_start => datetime_end]
900
     * @param string|null $alias  Input alias
901
     *
902
     * @return static|string
903
     */
904
    public function dateDiff(array $fields, $alias)
905
    {
906
        $dateTimeStart = key($fields);
907
        $dateTimeEnd = $fields[ $dateTimeStart ];
908
909
        if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $dateTimeStart)) {
910
            $dateTimeStart = $this->conn->escape($dateTimeStart);
911
        } elseif ($this->conn->protectIdentifiers) {
912
            $dateTimeStart = $this->conn->protectIdentifiers($dateTimeStart);
913
        }
914
915
        if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $dateTimeEnd)) {
916
            $dateTimeEnd = $this->conn->escape($dateTimeEnd);
917
        } elseif ($this->conn->protectIdentifiers) {
918
            $dateTimeEnd = $this->conn->protectIdentifiers($dateTimeEnd);
919
        }
920
921
        $this->select(
922
            sprintf(
923
                'DATEDIFF(%s, %s)', // Returns the number of days between two dates
924
                $dateTimeStart,
925
                $dateTimeEnd
926
            )
927
            . ' AS '
928
            . $this->conn->escapeIdentifiers($alias)
929
        );
930
931
        return $this;
932
    }
933
934
    //--------------------------------------------------------------------
935
936
    /**
937
     * AbstractQueryBuilder::table
938
     *
939
     * Used for modifier query builder process (insert, update, replace, delete).
940
     *
941
     * @param string $table Table name.
942
     *
943
     * @return  static
944
     */
945
    public function table($table)
946
    {
947
        return $this->from($table, true);
948
    }
949
950
    //--------------------------------------------------------------------
951
952
    /**
953
     * AbstractQueryBuilder::from
954
     *
955
     * Generates FROM Sql statement portions into Query Builder.
956
     *
957
     * @param string|array $table
958
     * @param bool         $overwrite Should we remove the first table existing?
959
     *
960
     * @return  static
961
     */
962
    public function from($table, $overwrite = false)
963
    {
964
        if ($overwrite === true) {
965
            $this->builderCache->from = [];
966
            $this->builderCache->aliasedTables = [];
967
        }
968
969
        if (is_string($table)) {
970
            $table = explode(',', $table);
971
972
            foreach ($table as $name) {
973
                $name = trim($name);
974
975
                // Extract any aliases that might exist. We use this information
976
                // in the protectIdentifiers to know whether to add a table prefix
977
                $this->trackAliases($name);
978
979
                $this->builderCache->from[] = $this->conn->protectIdentifiers($name, true, null, false);
980
            }
981
        } elseif (is_array($table)) {
0 ignored issues
show
introduced by
The condition is_array($table) is always true.
Loading history...
982
            foreach ($table as $alias => $name) {
983
                $name = trim($name) . ' AS ' . trim($alias);
984
985
                // Extract any aliases that might exist. We use this information
986
                // in the protectIdentifiers to know whether to add a table prefix
987
                $this->trackAliases($name);
988
989
                $this->builderCache->from[] = $this->conn->protectIdentifiers($name, true, null, false);
990
            }
991
        }
992
993
        return $this;
994
    }
995
996
    //--------------------------------------------------------------------
997
998
    /**
999
     * AbstractQueryBuilder::trackAliases
1000
     *
1001
     * Used to track Sql statements written with aliased tables.
1002
     *
1003
     * @param string|array $table Inspected table name.
1004
     *
1005
     * @return  void
1006
     */
1007
    protected function trackAliases($table)
1008
    {
1009
        if (is_array($table)) {
1010
            foreach ($table as $name) {
1011
                $this->trackAliases($name);
1012
            }
1013
1014
            return;
1015
        }
1016
1017
        // Does the string contain a comma?  If so, we need to separate
1018
        // the string into discreet statements
1019
        if (strpos($table, ',') !== false) {
1020
            $this->trackAliases(explode(',', $table));
1021
1022
            return;
1023
        }
1024
1025
        // if a table alias is used we can recognize it by a space
1026
        if (strpos($table, ' ') !== false) {
1027
            // if the alias is written with the AS keyword, remove it
1028
            //$table = preg_replace('/\s+AS\s+/i', ' ', $table);
1029
1030
            // Grab the alias
1031
            //$table = trim(strrchr($table, ' '));
1032
            $table = str_replace([' AS ', ' as '], '.', $table);
1033
1034
            // Store the alias, if it doesn't already exist
1035
            if ( ! in_array($table, $this->builderCache->aliasedTables)) {
1036
                $this->builderCache->aliasedTables[] = $table;
1037
            }
1038
        }
1039
    }
1040
1041
    //--------------------------------------------------------------------
1042
1043
    /**
1044
     * AbstractQueryBuilder::getAliasedTables
1045
     *
1046
     * Returns list of tracked aliased tables.
1047
     *
1048
     * @return array
1049
     */
1050
    public function getAliasedTables()
1051
    {
1052
        if (empty($this->builderCache->aliasedTables)) {
1053
            return [];
1054
        }
1055
1056
        return $this->builderCache->aliasedTables;
1057
    }
1058
1059
    // --------------------------------------------------------------------
1060
1061
    /**
1062
     * AbstractQueryBuilder::join
1063
     *
1064
     * Add JOIN Sql statement portions into Query Builder.
1065
     *
1066
     * @param string    $table     Table name
1067
     * @param null      $condition Join conditions: table.column = other_table.column
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $condition is correct as it would always require null to be passed?
Loading history...
1068
     * @param string    $type      UPPERCASE join type LEFT|LEFT_OUTER|RIGHT|RIGHT_OUTER|INNER|OUTER|FULL|JOIN
1069
     * @param null|bool $escape    Whether not to try to escape identifiers
1070
     *
1071
     * @return static
1072
     */
1073
    public function join($table, $condition = null, $type = 'LEFT', $escape = null)
1074
    {
1075
        if ($type !== '') {
1076
            $type = strtoupper(trim($type));
1077
1078
            if ( ! in_array($type, ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'], true)) {
1079
                $type = '';
1080
            } else {
1081
                $type .= ' ';
1082
            }
1083
        }
1084
1085
        // Extract any aliases that might exist. We use this information
1086
        // in the protectIdentifiers to know whether to add a table prefix
1087
        $this->trackAliases($table);
1088
1089
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
1090
1091
        if ( ! $this->hasOperator($condition)) {
1092
            $condition = ' USING (' . ($escape
0 ignored issues
show
Bug introduced by
Are you sure $escape ? $this->conn->e...condition) : $condition of type array|mixed|null can be used in concatenation? ( Ignorable by Annotation )

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

1092
            $condition = ' USING (' . (/** @scrutinizer ignore-type */ $escape
Loading history...
1093
                    ? $this->conn->escapeIdentifiers($condition)
1094
                    : $condition) . ')';
1095
        } elseif ($escape === false) {
1096
            $condition = ' ON ' . $condition;
1097
        } else {
1098
            // Split multiple conditions
1099
            if (preg_match_all('/\sAND\s|\sOR\s/i', $condition, $joints, PREG_OFFSET_CAPTURE)) {
1100
                $conditions = [];
1101
                $joints = $joints[ 0 ];
1102
                array_unshift($joints, ['', 0]);
1103
1104
                for ($i = count($joints) - 1, $pos = strlen($condition); $i >= 0; $i--) {
1105
                    $joints[ $i ][ 1 ] += strlen($joints[ $i ][ 0 ]); // offset
1106
                    $conditions[ $i ] = substr($condition, $joints[ $i ][ 1 ], $pos - $joints[ $i ][ 1 ]);
1107
                    $pos = $joints[ $i ][ 1 ] - strlen($joints[ $i ][ 0 ]);
1108
                    $joints[ $i ] = $joints[ $i ][ 0 ];
1109
                }
1110
            } else {
1111
                $conditions = [$condition];
1112
                $joints = [''];
1113
            }
1114
1115
            $condition = ' ON ';
1116
            for ($i = 0, $c = count($conditions); $i < $c; $i++) {
1117
                $operator = $this->getOperator($conditions[ $i ]);
1118
                $condition .= $joints[ $i ];
1119
                $condition .= preg_match(
1120
                    "/(\(*)?([\[\]\w\.'-]+)" . preg_quote($operator) . "(.*)/i",
1121
                    $conditions[ $i ],
1122
                    $match
1123
                )
1124
                    ? $match[ 1 ] . $this->conn->protectIdentifiers(
1125
                        $match[ 2 ]
1126
                    ) . $operator . $this->conn->protectIdentifiers($match[ 3 ])
1127
                    : $conditions[ $i ];
1128
            }
1129
        }
1130
1131
        // Do we want to escape the table name?
1132
        if ($escape === true) {
1133
            $table = $this->conn->protectIdentifiers($table, true, null, false);
1134
        }
1135
1136
        // Assemble the JOIN statement
1137
        $this->builderCache->join[] = $type . 'JOIN ' . $table . $condition;
1138
1139
        return $this;
1140
    }
1141
1142
    //--------------------------------------------------------------------
1143
1144
    /**
1145
     * AbstractQueryBuilder::hasOperator
1146
     *
1147
     * Tests whether the string has an Sql operator
1148
     *
1149
     * @param string
1150
     *
1151
     * @return    bool
1152
     */
1153
    protected function hasOperator($string)
1154
    {
1155
        return (bool)preg_match(
1156
            '/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i',
1157
            trim($string)
1158
        );
1159
    }
1160
1161
    //--------------------------------------------------------------------
1162
1163
    /**
1164
     * AbstractQueryBuilder::getOperator
1165
     *
1166
     * Returns the Sql string operator
1167
     *
1168
     * @param string
1169
     *
1170
     * @return    string
1171
     */
1172
    protected function getOperator($string)
1173
    {
1174
        static $operator;
1175
1176
        if (empty($operator)) {
1177
1178
            $likeEscapeString = ($this->conn->getConfig('likeEscapeString') !== '')
1179
                ? '\s+' . preg_quote(
1180
                    trim(
1181
                        sprintf(
1182
                            $this->conn->getConfig('likeEscapeString'),
1183
                            $this->conn->getConfig('likeEscapeCharacter')
1184
                        )
1185
                    ),
1186
                    '/'
1187
                )
1188
                : '';
1189
1190
            $operator = [
1191
                '\s*(?:<|>|!)?=\s*',             // =, <=, >=, !=
1192
                '\s*<>?\s*',                     // <, <>
1193
                '\s*>\s*',                       // >
1194
                '\s+IS NULL',                    // IS NULL
1195
                '\s+IS NOT NULL',                // IS NOT NULL
1196
                '\s+EXISTS\s*\(.*\)',        // EXISTS(Sql)
1197
                '\s+NOT EXISTS\s*\(.*\)',    // NOT EXISTS(Sql)
1198
                '\s+BETWEEN\s+',                 // BETWEEN value AND value
1199
                '\s+IN\s*\(.*\)',            // IN(list)
1200
                '\s+NOT IN\s*\(.*\)',        // NOT IN (list)
1201
                '\s+LIKE\s+\S.*(' . $likeEscapeString . ')?',    // LIKE 'expr'[ ESCAPE '%s']
1202
                '\s+NOT LIKE\s+\S.*(' . $likeEscapeString . ')?' // NOT LIKE 'expr'[ ESCAPE '%s']
1203
            ];
1204
        }
1205
1206
        return preg_match('/' . implode('|', $operator) . '/i', $string, $match)
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('/' . ...ch) ? $match[0] : false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1207
            ? $match[ 0 ]
1208
            : false;
1209
    }
1210
1211
    //--------------------------------------------------------------------
1212
1213
    /**
1214
     * AbstractQueryBuilder::orWhere
1215
     *
1216
     * Add OR WHERE Sql statement portions into Query Builder
1217
     *
1218
     * @param string|array $field  Input name, array of [field => value] (grouped where)
1219
     * @param null|string  $value  Input criteria or UPPERCASE grouped type AND|OR
1220
     * @param null|bool    $escape Whether not to try to escape identifiers
1221
     *
1222
     * @return static
1223
     */
1224
    public function orWhere($field, $value = null, $escape = null)
1225
    {
1226
        return $this->prepareWhereStatement($field, $value, 'OR ', $escape, 'where');
1227
    }
1228
1229
    //--------------------------------------------------------------------
1230
1231
    /**
1232
     * AbstractQueryBuilder::prepareWhereHavingStatement
1233
     *
1234
     * Add WHERE, HAVING Sql statement portion into Query Builder.
1235
     *
1236
     * @used-by    where()
1237
     * @used-by    orWhere()
1238
     * @used-by    having()
1239
     * @used-by    orHaving()
1240
     *
1241
     * @param string    $cacheKey 'QBWhere' or 'QBHaving'
1242
     * @param mixed     $field
1243
     * @param mixed     $value
1244
     * @param string    $type
1245
     * @param null|bool $escape   Whether not to try to escape identifiers
1246
     *
1247
     * @return    static
1248
     */
1249
    protected function prepareWhereStatement($field, $value = null, $type = 'AND ', $escape = null, $cacheKey)
1250
    {
1251
        if ( ! is_array($field)) {
1252
            $field = [$field => $value];
1253
        }
1254
1255
        // If the escape value was not set will base it on the global setting
1256
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
1257
1258
        foreach ($field as $fieldName => $fieldValue) {
1259
            if ($fieldValue !== null) {
1260
                $operator = $this->getOperator($fieldName);
1261
                $fieldName = trim(str_replace($operator, '', $fieldName));
1262
1263
                $fieldBind = $this->bind($fieldName, $fieldValue);
1264
1265
                if (empty($operator) && ! in_array($cacheKey, ['between', 'notBetween'])) {
1266
                    $fieldName .= ' =';
1267
                } else {
1268
                    $fieldName .= $operator;
1269
                }
1270
            } elseif ( ! $this->hasOperator($fieldName)) {
1271
                // value appears not to have been set, assign the test to IS NULL
1272
                $fieldName .= ' IS NULL';
1273
            } elseif (preg_match('/\s*(!?=|<>|IS(?:\s+NOT)?)\s*$/i', $fieldName, $match, PREG_OFFSET_CAPTURE)) {
1274
                $fieldName = substr(
1275
                        $fieldName,
1276
                        0,
1277
                        $match[ 0 ][ 1 ]
1278
                    ) . ($match[ 1 ][ 0 ] === '='
1279
                        ? ' IS NULL'
1280
                        : ' IS NOT NULL');
1281
            } elseif ($fieldValue instanceof AbstractQueryBuilder) {
1282
                $fieldValue = $fieldValue->getSqlStatement();
1283
            }
1284
1285
            $fieldValue = ! is_null($fieldValue)
1286
                ? ' :' . $fieldBind
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fieldBind does not seem to be defined for all execution paths leading up to this point.
Loading history...
1287
                : $fieldValue;
1288
1289
            if ($cacheKey === 'having') {
1290
                $prefix = (count($this->builderCache->having) === 0)
1291
                    ? $this->getBracketType('')
1292
                    : $this->getBracketType($type);
1293
1294
                $this->builderCache->having[] = [
1295
                    'condition' => $prefix . $fieldName . $fieldValue,
1296
                    'escape'    => $escape,
1297
                ];
1298
            } else {
1299
                $prefix = (count($this->builderCache->where) === 0)
1300
                    ? $this->getBracketType('')
1301
                    : $this->getBracketType($type);
1302
1303
                if ($cacheKey === 'between') {
1304
                    $condition = $prefix . $fieldName . ' BETWEEN' . $fieldValue;
1305
                } elseif ($cacheKey === 'notBetween') {
1306
                    $condition = $prefix . $fieldName . ' NOT BETWEEN' . $fieldValue;
1307
                } else {
1308
                    $condition = $prefix . $fieldName . $fieldValue;
1309
                }
1310
1311
                $this->builderCache->where[] = [
1312
                    'condition' => $condition,
1313
                    'escape'    => $escape,
1314
                ];
1315
            }
1316
        }
1317
1318
        return $this;
1319
    }
1320
1321
    //--------------------------------------------------------------------
1322
1323
    /**
1324
     * AbstractQueryBuilder::binds
1325
     *
1326
     * @param $field
1327
     * @param $value
1328
     *
1329
     * @return string
1330
     */
1331
    public function bind($field, $value)
1332
    {
1333
        if ( ! array_key_exists($field, $this->builderCache->binds)) {
1334
            $this->builderCache->binds[ $field ] = $value;
1335
1336
            return $field;
1337
        }
1338
1339
        $count = 0;
1340
1341
        while (array_key_exists($field . '_' . $count, $this->builderCache->binds)) {
1342
            ++$count;
1343
        }
1344
1345
        $this->builderCache->binds[ $field . '_' . $count ] = $value;
1346
1347
        return $field . '_' . $count;
1348
    }
1349
1350
    //--------------------------------------------------------------------
1351
1352
    /**
1353
     * AbstractQueryBuilder::getBracketType
1354
     *
1355
     * @used-by    bracketOpen()
1356
     * @used-by    prepareLikeStatement()
1357
     * @used-by    whereHaving()
1358
     * @used-by    prepareWhereInStatement()
1359
     *
1360
     * @param string $type
1361
     *
1362
     * @return  string
1363
     */
1364
    protected function getBracketType($type)
1365
    {
1366
        if ($this->builderCache->bracketOpen) {
1367
            $type = '';
1368
            $this->builderCache->bracketOpen = false;
1369
        }
1370
1371
        return $type;
1372
    }
1373
1374
    //--------------------------------------------------------------------
1375
1376
    /**
1377
     * AbstractQueryBuilder::having
1378
     *
1379
     * Separates multiple calls with 'AND'.
1380
     *
1381
     * @param string    $field
1382
     * @param string    $value
1383
     * @param null|bool $escape Whether not to try to escape identifiers
1384
     *
1385
     * @return    static
1386
     */
1387
    public function having($field, $value = null, $escape = null)
1388
    {
1389
        return $this->prepareWhereStatement($field, $value, 'AND ', $escape, 'having');
1390
    }
1391
1392
    //--------------------------------------------------------------------
1393
1394
    /**
1395
     * AbstractQueryBuilder::orHaving
1396
     *
1397
     * Separates multiple calls with 'OR'.
1398
     *
1399
     * @param string    $field
1400
     * @param string    $value
1401
     * @param null|bool $escape Whether not to try to escape identifiers
1402
     *
1403
     * @return    static
1404
     */
1405
    public function orHaving($field, $value = null, $escape = null)
1406
    {
1407
        return $this->prepareWhereStatement($field, $value, 'OR ', $escape, 'having');
1408
    }
1409
1410
    //--------------------------------------------------------------------
1411
1412
    /**
1413
     * AbstractQueryBuilder::whereBetween
1414
     *
1415
     * Add WHERE BETWEEN Sql statement portions into Query Builder
1416
     *
1417
     * @param string $field  Input name
1418
     * @param array  $values Array of between values
1419
     *
1420
     * @return static
1421
     */
1422
    public function whereBetween($field, array $values = [], $escape = null)
1423
    {
1424
        return $this->prepareWhereStatement($field, implode(' AND ', $values), 'AND ', $escape, 'between');
1425
    }
1426
1427
    //--------------------------------------------------------------------
1428
1429
    /**
1430
     * AbstractQueryBuilder::orWhereBetween
1431
     *
1432
     * Add OR WHERE BETWEEN Sql statement portions into Query Builder
1433
     *
1434
     * @param string $field  Input name
1435
     * @param array  $values Array of between values
1436
     *
1437
     * @return static
1438
     */
1439
    public function orWhereBetween($field, array $values = [], $escape = null)
1440
    {
1441
        return $this->prepareWhereStatement($field, implode(' AND ', $values), 'OR ', $escape, 'between');
1442
    }
1443
1444
    //--------------------------------------------------------------------
1445
1446
    /**
1447
     * AbstractQueryBuilder::whereNotBetween
1448
     *
1449
     * Add WHERE NOT BETWEEN Sql statement portions into Query Builder
1450
     *
1451
     * @param string $field  Input name
1452
     * @param array  $values Array of between values
1453
     *
1454
     * @return static
1455
     */
1456
    public function whereNotBetween($field, array $values = [], $escape = null)
1457
    {
1458
        return $this->prepareWhereStatement($field, implode(' AND ', $values), 'OR ', $escape, 'notBetween');
1459
    }
1460
1461
    //--------------------------------------------------------------------
1462
1463
    /**
1464
     * AbstractQueryBuilder::whereIn
1465
     *
1466
     * Add WHERE IN Sql statement portions into Query Builder
1467
     *
1468
     * @param string    $field  Input name
1469
     * @param array     $values Array of values criteria
1470
     * @param null|bool $escape Whether not to try to escape identifiers
1471
     *
1472
     * @return static
1473
     */
1474
    public function whereIn($field, $values = [], $escape = null)
1475
    {
1476
        return $this->prepareWhereInStatement($field, $values, false, 'AND ', $escape);
1477
    }
1478
1479
    //--------------------------------------------------------------------
1480
1481
    /**
1482
     * AbstractQueryBuilder::prepareWhereInStatement
1483
     *
1484
     * Internal WHERE IN
1485
     *
1486
     * @used-by    WhereIn()
1487
     * @used-by    orWhereIn()
1488
     * @used-by    whereNotIn()
1489
     * @used-by    orWhereNotIn()
1490
     *
1491
     * @param string    $field  The field to search
1492
     * @param array     $values The values searched on
1493
     * @param bool      $not    If the statement would be IN or NOT IN
1494
     * @param string    $type   AND|OR
1495
     * @param null|bool $escape Whether not to try to escape identifiers
1496
     *
1497
     * @return    static
1498
     */
1499
    protected function prepareWhereInStatement(
1500
        $field = null,
1501
        $values = null,
1502
        $not = false,
1503
        $type = 'AND ',
1504
        $escape = null
1505
    ) {
1506
        if ($field === null OR $values === null) {
1507
            return $this;
1508
        }
1509
1510
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
1511
1512
        $fieldKey = $field;
1513
1514
        if (is_string($values) || is_numeric($values)) {
0 ignored issues
show
introduced by
The condition is_numeric($values) is always false.
Loading history...
1515
            $values = [$values];
1516
        }
1517
1518
        $not = ($not)
1519
            ? ' NOT'
1520
            : '';
1521
1522
        $prefix = (count($this->builderCache->where) === 0)
1523
            ? $this->getBracketType('')
1524
            : $this->getBracketType($type);
1525
1526
        if (is_array($values)) {
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
1527
            $fieldValue = array_values($values);
1528
            $fieldBind = $this->bind($fieldKey, $fieldValue);
1529
1530
            if ($escape === true) {
1531
                $fieldKey = $this->conn->protectIdentifiers($field);
1532
            }
1533
1534
            $whereIn = [
1535
                'condition' => $prefix . $fieldKey . $not . ' IN (:' . $fieldBind . ')',
1536
                'escape'    => false,
1537
            ];
1538
1539
        } elseif ($values instanceof AbstractQueryBuilder) {
1540
1541
            if ($escape === true) {
1542
                $fieldKey = $this->conn->protectIdentifiers($field);
1543
            }
1544
1545
            $importBindsPattern = [];
1546
            $importBindsReplacement = [];
1547
            foreach ($values->builderCache->binds as $bindKey => $bindValue) {
1548
                $importBindKey = $this->bind($bindKey, $bindValue);
1549
1550
                $importBindsPattern[] = ':' . $bindKey;
1551
                $importBindsReplacement[] = ':' . $importBindKey;
1552
            }
1553
1554
            $sqlStatement = $values->getSqlStatement();
1555
            $sqlStatement = str_replace($importBindsPattern, $importBindsReplacement, $sqlStatement);
1556
1557
            $whereIn = [
1558
                'condition' => $prefix . $fieldKey . $not . ' IN (' . $sqlStatement . ')',
1559
                'escape'    => false,
1560
            ];
1561
        }
1562
1563
        if (isset($whereIn)) {
1564
            $this->builderCache->where[] = $whereIn;
1565
        }
1566
1567
        return $this;
1568
    }
1569
1570
    //--------------------------------------------------------------------
1571
1572
    /**
1573
     * AbstractQueryBuilder::orWhereIn
1574
     *
1575
     * Add OR WHERE IN Sql statement portions into Query Builder
1576
     *
1577
     * @param string    $field  Input name
1578
     * @param array     $values Array of values criteria
1579
     * @param null|bool $escape Whether not to try to escape identifiers
1580
     *
1581
     * @return static
1582
     */
1583
    public function orWhereIn($field, $values = [], $escape = null)
1584
    {
1585
        return $this->prepareWhereInStatement($field, $values, false, 'OR ', $escape);
1586
    }
1587
1588
    //--------------------------------------------------------------------
1589
1590
    /**
1591
     * AbstractQueryBuilder::whereNotIn
1592
     *
1593
     * Add WHERE NOT IN Sql statement portions into Query Builder
1594
     *
1595
     * @param string    $field  Input name
1596
     * @param array     $values Array of values criteria
1597
     * @param null|bool $escape Whether not to try to escape identifiers
1598
     *
1599
     * @return static
1600
     */
1601
    public function whereNotIn($field, $values = [], $escape = null)
1602
    {
1603
        return $this->prepareWhereInStatement($field, $values, true, 'AND ', $escape);
1604
    }
1605
1606
    //--------------------------------------------------------------------
1607
1608
    /**
1609
     * AbstractQueryBuilder::orWhereNotIn
1610
     *
1611
     * Add OR WHERE NOT IN Sql statement portions into Query Builder
1612
     *
1613
     * @param string    $field  Input name
1614
     * @param array     $values Array of values criteria
1615
     * @param null|bool $escape Whether not to try to escape identifiers
1616
     *
1617
     * @return static
1618
     */
1619
    public function orWhereNotIn($field, $values = [], $escape = null)
1620
    {
1621
        return $this->prepareWhereInStatement($field, $values, true, 'OR ', $escape);
1622
    }
1623
1624
    //--------------------------------------------------------------------
1625
1626
    /**
1627
     * AbstractQueryBuilder::orWhereNotBetween
1628
     *
1629
     * Add OR WHERE NOT BETWEEN Sql statement portions into Query Builder
1630
     *
1631
     * @param string $field  Input name
1632
     * @param array  $values Array of between values
1633
     *
1634
     * @return static
1635
     */
1636
    public function orWhereNotBetween($field, array $values = [], $escape = null)
1637
    {
1638
        return $this->prepareWhereStatement($field, implode(' OR ', $values), 'OR ', $escape, 'notBetween');
1639
    }
1640
1641
    //--------------------------------------------------------------------
1642
1643
    /**
1644
     * AbstractQueryBuilder::like
1645
     *
1646
     * Generates a %LIKE% Sql statement portions of the query.
1647
     * Separates multiple calls with 'AND'.
1648
     *
1649
     * @param string    $field         Input name
1650
     * @param string    $match         Input criteria match
1651
     * @param string    $wildcard      UPPERCASE positions of wildcard character BOTH|BEFORE|AFTER
1652
     * @param bool      $caseSensitive Whether perform case sensitive LIKE or not
1653
     * @param null|bool $escape        Whether not to try to escape identifiers
1654
     *
1655
     * @return static
1656
     */
1657
    public function like($field, $match = '', $wildcard = 'BOTH', $caseSensitive = true, $escape = null)
1658
    {
1659
        return $this->prepareLikeStatement($field, $match, 'AND ', $wildcard, '', $caseSensitive, $escape);
1660
    }
1661
1662
    //--------------------------------------------------------------------
1663
1664
    /**
1665
     * Internal LIKE
1666
     *
1667
     * @used-by    like()
1668
     * @used-by    orLike()
1669
     * @used-by    notLike()
1670
     * @used-by    orNotLike()
1671
     *
1672
     * @param mixed     $field
1673
     * @param string    $match
1674
     * @param string    $type
1675
     * @param string    $side
1676
     * @param string    $not
1677
     * @param bool      $caseSensitive IF true, will force a case-insensitive search
1678
     * @param null|bool $escape        Whether not to try to escape identifiers
1679
     *
1680
     * @return    static
1681
     */
1682
    protected function prepareLikeStatement(
1683
        $field,
1684
        $match = '',
1685
        $type = 'AND ',
1686
        $side = 'both',
1687
        $not = '',
1688
        $escape = null,
1689
        $caseSensitive = false
1690
    ) {
1691
        if ( ! is_array($field)) {
1692
            $field = [$field => $match];
1693
        }
1694
1695
        $escape = is_bool($escape)
1696
            ? $escape
1697
            : $this->conn->protectIdentifiers;
1698
1699
        // lowercase $side in case somebody writes e.g. 'BEFORE' instead of 'before' (doh)
1700
        $side = strtolower($side);
1701
1702
        foreach ($field as $fieldName => $fieldValue) {
1703
            $prefix = (count($this->builderCache->where) === 0)
1704
                ? $this->getBracketType('')
1705
                : $this->getBracketType($type);
1706
1707
            if ($caseSensitive === true) {
1708
                $fieldValue = strtolower($fieldValue);
1709
            }
1710
1711
            if ($side === 'none') {
1712
                $bind = $this->bind($fieldName, $fieldValue);
1713
            } elseif ($side === 'before') {
1714
                $bind = $this->bind($fieldName, "%$fieldValue");
1715
            } elseif ($side === 'after') {
1716
                $bind = $this->bind($fieldName, "$fieldValue%");
1717
            } else {
1718
                $bind = $this->bind($fieldName, "%$fieldValue%");
1719
            }
1720
1721
            $likeStatement = $this->platformPrepareLikeStatement($prefix, $fieldName, $not, $bind, $caseSensitive);
1722
1723
            // some platforms require an escape sequence definition for LIKE wildcards
1724
            if ($escape === true && $this->conn->getConfig('likeEscapeString') !== '') {
1725
                $likeStatement .= sprintf(
1726
                    $this->conn->getConfig('likeEscapeString'),
1727
                    $this->conn->getConfig('likeEscapeCharacter')
1728
                );
1729
            }
1730
1731
            $this->builderCache->where[] = ['condition' => $likeStatement, 'escape' => $escape];
1732
        }
1733
1734
        return $this;
1735
    }
1736
1737
    //--------------------------------------------------------------------
1738
1739
    /**
1740
     * AbstractQueryBuilder::platformPrepareLikeStatement
1741
     *
1742
     * Platform independent LIKE statement builder.
1743
     *
1744
     * @param string|null $prefix
1745
     * @param string      $column
1746
     * @param string|null $not
1747
     * @param string      $bind
1748
     * @param bool        $caseSensitive
1749
     *
1750
     * @return string
1751
     */
1752
    abstract protected function platformPrepareLikeStatement(
1753
        $prefix = null,
1754
        $column,
1755
        $not = null,
1756
        $bind,
1757
        $caseSensitive = false
1758
    );
1759
1760
    //--------------------------------------------------------------------
1761
1762
    /**
1763
     * AbstractQueryBuilder::orLike
1764
     *
1765
     * Add OR LIKE Sql statement portions into Query Builder
1766
     *
1767
     * @param string    $field         Input name
1768
     * @param string    $match         Input criteria match
1769
     * @param string    $wildcard      UPPERCASE positions of wildcard character BOTH|BEFORE|AFTER
1770
     * @param bool      $caseSensitive Whether perform case sensitive LIKE or not
1771
     * @param null|bool $escape        Whether not to try to escape identifiers
1772
     *
1773
     * @return static
1774
     */
1775
    public function orLike($field, $match = '', $wildcard = 'BOTH', $caseSensitive = true, $escape = null)
1776
    {
1777
        return $this->prepareLikeStatement($field, $match, 'OR ', $wildcard, '', $caseSensitive, $escape);
1778
    }
1779
1780
    //--------------------------------------------------------------------
1781
1782
    /**
1783
     * AbstractQueryBuilder::notLike
1784
     *
1785
     * Add NOT LIKE Sql statement portions into Query Builder
1786
     *
1787
     * @param string    $field         Input name
1788
     * @param string    $match         Input criteria match
1789
     * @param string    $wildcard      UPPERCASE positions of wildcard character BOTH|BEFORE|AFTER
1790
     * @param bool      $caseSensitive Whether perform case sensitive LIKE or not
1791
     * @param null|bool $escape        Whether not to try to escape identifiers
1792
     *
1793
     * @return static
1794
     */
1795
    public function notLike($field, $match = '', $wildcard = 'BOTH', $caseSensitive = true, $escape = null)
1796
    {
1797
        return $this->prepareLikeStatement($field, $match, 'AND ', $wildcard, 'NOT', $caseSensitive, $escape);
1798
    }
1799
1800
    //--------------------------------------------------------------------
1801
1802
    /**
1803
     * AbstractQueryBuilder::orNotLike
1804
     *
1805
     * Add OR NOT LIKE Sql statement portions into Query Builder
1806
     *
1807
     * @param string    $field         Input name
1808
     * @param string    $match         Input criteria match
1809
     * @param string    $wildcard      UPPERCASE positions of wildcard character BOTH|BEFORE|AFTER
1810
     * @param bool      $caseSensitive Whether perform case sensitive LIKE or not
1811
     * @param null|bool $escape        Whether not to try to escape identifiers
1812
     *
1813
     * @return static
1814
     */
1815
    public function orNotLike($field, $match = '', $wildcard = 'BOTH', $caseSensitive = true, $escape = null)
1816
    {
1817
        return $this->prepareLikeStatement($field, $match, 'OR ', $wildcard, 'NOT', $caseSensitive, $escape);
1818
    }
1819
1820
    //--------------------------------------------------------------------
1821
1822
    /**
1823
     * AbstractQueryBuilder::groupBy
1824
     *
1825
     * Add GROUP BY Sql statement into Query Builder.
1826
     *
1827
     * @param string    $field
1828
     * @param null|bool $escape Whether not to try to escape identifiers
1829
     *
1830
     * @return $this
1831
     */
1832
    public function groupBy($field, $escape = null)
1833
    {
1834
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
1835
1836
        if (is_string($field)) {
0 ignored issues
show
introduced by
The condition is_string($field) is always true.
Loading history...
1837
            $field = ($escape === true)
1838
                ? explode(',', $field)
1839
                : [$field];
1840
        }
1841
1842
        foreach ($field as $fieldName) {
1843
            $fieldName = trim($fieldName);
1844
1845
            if ($fieldName !== '') {
1846
                $fieldName = ['field' => $fieldName, 'escape' => $escape];
1847
1848
                $this->builderCache->groupBy[] = $fieldName;
1849
            }
1850
        }
1851
1852
        return $this;
1853
    }
1854
1855
    //--------------------------------------------------------------------
1856
1857
    /**
1858
     * AbstractQueryBuilder::orderBy
1859
     *
1860
     * Add ORDER BY Sql statement portions into Query Builder.
1861
     *
1862
     * @param string|array $fields
1863
     * @param string       $direction
1864
     * @param null|bool    $escape Whether not to try to escape identifiers
1865
     *
1866
     * @return $this
1867
     */
1868
    public function orderBy($fields, $direction = 'ASC', $escape = null)
1869
    {
1870
        $orderBy = [];
1871
        $direction = strtoupper(trim($direction));
1872
1873
        if ($direction === 'RANDOM') {
1874
            $direction = '';
1875
1876
            // Do we have a seed value?
1877
            $fields = ctype_digit((string)$fields)
1878
                ? sprintf($this->SqlOrderByRandomKeywords[ 1 ], $fields)
0 ignored issues
show
Bug introduced by
It seems like $fields can also be of type array; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1878
                ? sprintf($this->SqlOrderByRandomKeywords[ 1 ], /** @scrutinizer ignore-type */ $fields)
Loading history...
1879
                : $this->SqlOrderByRandomKeywords[ 0 ];
1880
        } elseif (empty($fields)) {
1881
            return $this;
1882
        } elseif ($direction !== '') {
1883
            $direction = in_array($direction, ['ASC', 'DESC'], true)
1884
                ? ' ' . $direction
1885
                : '';
1886
        }
1887
1888
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
1889
1890
        if ($escape === false) {
1891
            $orderBy[] = ['field' => $fields, 'direction' => $direction, 'escape' => false];
1892
        } elseif (is_array($fields)) {
1893
            foreach ($fields as $field_name => $field_direction) {
1894
                $field_direction = is_numeric($field_name) ? $direction : $field_direction;
1895
                $orderBy[] = ['field' => trim($field_name), 'direction' => $field_direction, 'escape' => true];
1896
            }
1897
        } else {
1898
            foreach (explode(',', $fields) as $fields) {
0 ignored issues
show
introduced by
$fields is overwriting one of the parameters of this function.
Loading history...
1899
                $orderBy[] = ($direction === ''
1900
                    && preg_match(
1901
                        '/\s+(ASC|DESC)$/i',
1902
                        rtrim($fields),
1903
                        $match,
1904
                        PREG_OFFSET_CAPTURE
1905
                    ))
1906
                    ? [
1907
                        'field'     => ltrim(substr($fields, 0, $match[ 0 ][ 1 ])),
1908
                        'direction' => ' ' . $match[ 1 ][ 0 ],
1909
                        'escape'    => true,
1910
                    ]
1911
                    : ['field' => trim($fields), 'direction' => $direction, 'escape' => true];
1912
            }
1913
        }
1914
1915
        $this->builderCache->orderBy = array_merge($this->builderCache->orderBy, $orderBy);
1916
1917
        return $this;
1918
    }
1919
1920
    // --------------------------------------------------------------------
1921
1922
    /**
1923
     * AbstractQueryBuilder::page
1924
     *
1925
     * Add Set LIMIT, OFFSET Sql statement by page number and entries.
1926
     *
1927
     * @param int  $page  Page number
1928
     * @param null $limit Num entries of each page
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $limit is correct as it would always require null to be passed?
Loading history...
1929
     *
1930
     * @return static
1931
     */
1932
    public function page($page = 1, $limit = null)
1933
    {
1934
        $page = (int)intval($page);
1935
1936
        $limit = (int)(isset($limit)
1937
            ? $limit
1938
            : ($this->builderCache->limit === false
1939
                ? 5
1940
                : $this->builderCache->limit
1941
            )
1942
        );
1943
1944
        $offset = ($page - 1) * $limit;
1945
1946
        $this->limit($limit, $offset);
1947
1948
        return $this;
1949
    }
1950
1951
    //--------------------------------------------------------------------
1952
1953
    /**
1954
     * AbstractQueryBuilder::limit
1955
     *
1956
     * Add LIMIT,OFFSET Sql statement into Query Builder.
1957
     *
1958
     * @param int $limit  LIMIT value
1959
     * @param int $offset OFFSET value
1960
     *
1961
     * @return    static
1962
     */
1963
    public function limit($limit, $offset = null)
1964
    {
1965
        if ( ! is_null($limit)) {
0 ignored issues
show
introduced by
The condition is_null($limit) is always false.
Loading history...
1966
            $this->builderCache->limit = (int)$limit;
1967
        }
1968
1969
        $this->offset($offset);
1970
1971
        return $this;
1972
    }
1973
1974
    //--------------------------------------------------------------------
1975
1976
    /**
1977
     * AbstractQueryBuilder::offset
1978
     *
1979
     * Add OFFSET Sql statement into Query Builder.
1980
     *
1981
     * @param int $offset OFFSET value
1982
     *
1983
     * @return    static
1984
     */
1985
    public function offset($offset)
1986
    {
1987
        if ( ! empty($offset)) {
1988
            $this->builderCache->offset = (int)$offset;
1989
        }
1990
1991
        return $this;
1992
    }
1993
1994
    //--------------------------------------------------------------------
1995
1996
    /**
1997
     * AbstractQueryBuilder::get
1998
     *
1999
     * Perform execution of Sql Query Builder and run ConnectionInterface::query()
2000
     *
2001
     * @param null|int $limit
2002
     * @param null|int $offset
2003
     *
2004
     * @return Result
2005
     * @throws \O2System\Spl\Exceptions\RuntimeException
2006
     * @throws \Psr\Cache\InvalidArgumentException
2007
     */
2008
    public function get($limit = null, $offset = null)
2009
    {
2010
        if ( ! empty($limit)) {
2011
            $this->limit($limit, $offset);
2012
        }
2013
2014
        if ($this->cacheMode) {
2015
            $this->conn->cache($this->cacheMode);
2016
        }
2017
2018
        $result = $this->testMode
2019
            ? $this->getSqlStatement(false)
2020
            : $this->conn->query($this->getSqlStatement(false), $this->builderCache->binds);
2021
2022
        if ($result) {
2023
            $result->setNumPerPage($this->builderCache->limit);
2024
            $result->setNumFounds($this->countAllResults(false));
2025
            $result->setNumTotal($this->countAll(false));
2026
        }
2027
2028
        $this->builderCache->reset();
2029
2030
        $this->cacheMode = $this->conn->getConfig('cacheEnable');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->conn->getConfig('cacheEnable') can also be of type array. However, the property $cacheMode is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2031
2032
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type boolean|string which is incompatible with the documented return type O2System\Database\DataObjects\Result.
Loading history...
2033
    }
2034
2035
    //--------------------------------------------------------------------
2036
2037
    /**
2038
     * AbstractQueryBuilder::countAllResult
2039
     *
2040
     * Perform execution of count all result from Query Builder along with WHERE, LIKE, HAVING, GROUP BY, and LIMIT Sql
2041
     * statement.
2042
     *
2043
     * @return int
2044
     * @throws \O2System\Spl\Exceptions\RuntimeException
2045
     * @throws \Psr\Cache\InvalidArgumentException
2046
     */
2047
    public function countAllResults($reset = true)
2048
    {
2049
        // save previous
2050
        $previousSelect = $this->builderCache->select;
2051
        $previousLimit = $this->builderCache->limit;
2052
        $previousOffset = $this->builderCache->offset;
2053
2054
        // add select counter
2055
        array_unshift($this->builderCache->select, 'COUNT(*) AS numrows');
2056
2057
        // generate Sql statement
2058
        $sqlStatement = $this->getSqlStatement();
2059
2060
        // restore previous select
2061
        $this->builderCache->select = $previousSelect;
2062
        $this->builderCache->limit = $previousLimit;
2063
        $this->builderCache->offset = $previousOffset;
2064
2065
        if ($this->testMode) {
2066
            return $sqlStatement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sqlStatement returns the type string which is incompatible with the documented return type integer.
Loading history...
2067
        } elseif ($this->isSubQuery) {
2068
            $statement = new Query\Statement();
2069
            $statement->setSqlStatement($sqlStatement, $this->builderCache->binds);
2070
            $statement->setSqlFinalStatement($this->conn->compileSqlBinds($sqlStatement,
2071
                $this->builderCache->binds));
2072
2073
            $sqlStatement = $statement->getSqlFinalStatement();
2074
2075
            return '( ' . $sqlStatement . ' )';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '( ' . $sqlStatement . ' )' returns the type string which is incompatible with the documented return type integer.
Loading history...
2076
        }
2077
2078
        $result = $this->conn->query($sqlStatement, $this->builderCache->binds);
2079
2080
        if($reset) {
2081
            $this->builderCache->reset();
2082
        }
2083
2084
        if ($result->count() == 0) {
2085
            return 0;
2086
        }
2087
2088
        return (int)$result->first()->numrows;
0 ignored issues
show
Bug Best Practice introduced by
The property numrows does not exist on O2System\Database\DataObjects\Result\Row. Since you implemented __get, consider adding a @property annotation.
Loading history...
2089
    }
2090
2091
    //--------------------------------------------------------------------
2092
2093
    /**
2094
     * AbstractQueryBuilder::getWhere
2095
     *
2096
     * Perform execution of Sql Query Builder and run ConnectionInterface::query()
2097
     *
2098
     * @param array    $where
2099
     * @param null|int $limit
2100
     * @param null|int $offset
2101
     *
2102
     * @return Result
2103
     * @throws \O2System\Spl\Exceptions\RuntimeException
2104
     * @throws \Psr\Cache\InvalidArgumentException
2105
     */
2106
    public function getWhere(array $where = [], $limit = null, $offset = null)
2107
    {
2108
        $this->where($where);
2109
2110
        if ( ! empty($limit)) {
2111
            $this->limit($limit, $offset);
2112
        }
2113
2114
        if ($this->cacheMode) {
2115
            $this->conn->cache($this->cacheMode);
2116
        }
2117
2118
        $result = $this->testMode
2119
            ? $this->getSqlStatement()
2120
            : $this->conn->query($this->getSqlStatement(false), $this->builderCache->binds);
2121
2122
        if ($result) {
2123
            $result->setNumPerPage($this->builderCache->limit);
2124
            $result->setNumFounds($this->countAllResults(false));
2125
            $result->setNumTotal($this->countAll(false));
2126
        }
2127
2128
        $this->builderCache->reset();
2129
2130
        $this->cacheMode = $this->conn->getConfig('cacheEnable');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->conn->getConfig('cacheEnable') can also be of type array. However, the property $cacheMode is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2131
2132
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type boolean|string which is incompatible with the documented return type O2System\Database\DataObjects\Result.
Loading history...
2133
    }
2134
2135
    //--------------------------------------------------------------------
2136
2137
    /**
2138
     * AbstractQueryBuilder::where
2139
     *
2140
     * Add WHERE Sql statement portions into Query Builder
2141
     *
2142
     * @param string|array $field  Input name, array of [field => value] (grouped where)
2143
     * @param null|string  $value  Input criteria or UPPERCASE grouped type AND|OR
2144
     * @param null|bool    $escape Whether not to try to escape identifiers
2145
     *
2146
     * @return static
2147
     */
2148
    public function where($field, $value = null, $escape = null)
2149
    {
2150
        return $this->prepareWhereStatement($field, $value, 'AND ', $escape, 'where');
2151
    }
2152
2153
    //--------------------------------------------------------------------
2154
2155
    /**
2156
     * AbstractQueryBuilder::countAll
2157
     *
2158
     * Perform execution of count all records of a table.
2159
     *
2160
     * @access  public
2161
     * @return int
2162
     * @throws \O2System\Spl\Exceptions\RuntimeException
2163
     * @throws \Psr\Cache\InvalidArgumentException
2164
     */
2165
    public function countAll($reset = true)
2166
    {
2167
        // save previous cache
2168
        $previousQueryBuilderCache = clone $this->builderCache;
2169
2170
        // reset cache
2171
        $this->builderCache->reset();
2172
2173
        $this->builderCache->from = $previousQueryBuilderCache->from;
2174
2175
        $this->count('*', 'numrows');
2176
2177
        // generate Sql statement
2178
        $sqlStatement = $this->getSqlStatement();
2179
2180
        // restore previous cache
2181
        $this->builderCache = $previousQueryBuilderCache;
2182
        unset($previousQueryBuilderCache);
2183
2184
        if ($this->testMode) {
2185
            return $sqlStatement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sqlStatement returns the type string which is incompatible with the documented return type integer.
Loading history...
2186
        } elseif ($this->isSubQuery) {
2187
            $statement = new Query\Statement();
2188
            $statement->setSqlStatement($sqlStatement, $this->builderCache->binds);
2189
            $statement->setSqlFinalStatement($this->conn->compileSqlBinds($sqlStatement,
2190
                $this->builderCache->binds));
2191
2192
            $sqlStatement = $statement->getSqlFinalStatement();
2193
2194
            return '( ' . $sqlStatement . ' )';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '( ' . $sqlStatement . ' )' returns the type string which is incompatible with the documented return type integer.
Loading history...
2195
        }
2196
2197
        $numrows = 0;
2198
        if($result = $this->conn->query($sqlStatement, $this->builderCache->binds)) {
2199
            if($result->count()) {
2200
                $numrows = $result->first()->numrows;
0 ignored issues
show
Bug Best Practice introduced by
The property numrows does not exist on O2System\Database\DataObjects\Result\Row. Since you implemented __get, consider adding a @property annotation.
Loading history...
2201
            }
2202
        }
2203
2204
        if($reset) {
2205
            $this->builderCache->reset();
2206
        }
2207
2208
        return $numrows;
2209
    }
2210
2211
    //--------------------------------------------------------------------
2212
2213
    /**
2214
     * AbstractQueryBuilder::count
2215
     *
2216
     * Add SELECT COUNT(field) AS alias statement
2217
     *
2218
     * @param string $field Input name
2219
     * @param string $alias Input alias
2220
     *
2221
     * @return static
2222
     */
2223
    public function count($field, $alias = '')
2224
    {
2225
        return $this->prepareAggregateStatement($field, $alias, 'COUNT');
2226
    }
2227
2228
    //--------------------------------------------------------------------
2229
2230
    /**
2231
     * AbstractQueryBuilder::orBracketOpen
2232
     *
2233
     * Starts a query group, but ORs the group
2234
     *
2235
     * @return    static
2236
     */
2237
    public function orBracketOpen()
2238
    {
2239
        return $this->bracketOpen('', 'OR ');
2240
    }
2241
2242
    //--------------------------------------------------------------------
2243
2244
    /**
2245
     * AbstractQueryBuilder::bracketOpen
2246
     *
2247
     * Starts a query group.
2248
     *
2249
     * @param string $not  (Internal use only)
2250
     * @param string $type (Internal use only)
2251
     *
2252
     * @return    static
2253
     */
2254
    public function bracketOpen($not = '', $type = 'AND ')
2255
    {
2256
        $type = $this->getBracketType($type);
2257
2258
        $this->builderCache->bracketOpen = true;
2259
        $prefix = count($this->builderCache->where) === 0
2260
            ? ''
2261
            : $type;
2262
        $where = [
2263
            'condition' => $prefix . $not . str_repeat(' ', ++$this->builderCache->bracketCount) . ' (',
2264
            'escape'    => false,
2265
        ];
2266
2267
        $this->builderCache->where[] = $where;
2268
2269
        return $this;
2270
    }
2271
2272
    //--------------------------------------------------------------------
2273
2274
    /**
2275
     * AbstractQueryBuilder::notBracketOpen
2276
     *
2277
     * Starts a query group, but NOTs the group
2278
     *
2279
     * @return    static
2280
     */
2281
    public function notBracketOpen()
2282
    {
2283
        return $this->bracketOpen('NOT ', 'AND ');
2284
    }
2285
2286
    //--------------------------------------------------------------------
2287
2288
    /**
2289
     * AbstractQueryBuilder::orNotBracketOpen
2290
     *
2291
     * Starts a query group, but OR NOTs the group
2292
     *
2293
     * @return    static
2294
     */
2295
    public function orNotBracketOpen()
2296
    {
2297
        return $this->bracketOpen('NOT ', 'OR ');
2298
    }
2299
2300
    //--------------------------------------------------------------------
2301
2302
    /**
2303
     * AbstractQueryBuilder::bracketClose
2304
     *
2305
     * Ends a query group
2306
     *
2307
     * @return    static
2308
     */
2309
    public function bracketClose()
2310
    {
2311
        $this->builderCache->bracketOpen = false;
2312
2313
        $where = [
2314
            'condition' => str_repeat(' ', $this->builderCache->bracketCount--) . ')',
2315
            'escape'    => false,
2316
        ];
2317
2318
        $this->builderCache->where[] = $where;
2319
2320
        return $this;
2321
    }
2322
2323
    //--------------------------------------------------------------------
2324
2325
    /**
2326
     * AbstractQueryBuilder::insert
2327
     *
2328
     * Execute INSERT Sql Query
2329
     *
2330
     * @param array $sets     An associative array of set values.
2331
     *                        sets[][field => value]
2332
     * @param bool  $escape   Whether to escape values and identifiers
2333
     *
2334
     * @return bool
2335
     * @throws \O2System\Spl\Exceptions\RuntimeException
2336
     * @throws \Psr\Cache\InvalidArgumentException
2337
     */
2338
    public function insert(array $sets, $escape = null)
2339
    {
2340
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
2341
2342
        $this->set($sets, null, $escape);
2343
2344
        $result = false;
2345
2346
        /**
2347
         * @bugs
2348
         *
2349
         * @issued_by: Triyana Suryapraja Sukmana <https://github.com/tss182>
2350
         * @fixed_by : Mohamad Rafi Randoni <https://github.com/rafirandoni>
2351
         */
2352
        if (count($this->builderCache->sets)) {
2353
            $sqlStatement = $this->platformInsertStatement(
2354
                $this->conn->protectIdentifiers(
2355
                    $this->builderCache->from[ 0 ],
2356
                    true,
2357
                    $escape,
2358
                    false
2359
                ),
2360
                array_keys($this->builderCache->sets),
2361
                array_values($this->builderCache->sets)
2362
            );
2363
2364
            if ($this->testMode) {
2365
                return $sqlStatement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sqlStatement returns the type string which is incompatible with the documented return type boolean.
Loading history...
2366
            }
2367
2368
            $result = $this->conn->query($sqlStatement, $this->builderCache->binds);
2369
        }
2370
2371
        $this->builderCache->resetModifier();
2372
2373
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type O2System\Database\DataObjects\Result which is incompatible with the documented return type boolean.
Loading history...
2374
    }
2375
2376
    //--------------------------------------------------------------------
2377
2378
    /**
2379
     * AbstractQueryBuilder::set
2380
     *
2381
     * Allows key/value pairs to be set for inserting or updating
2382
     *
2383
     * @param string|array $field
2384
     * @param mixed        $value
2385
     * @param null         $escape
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $escape is correct as it would always require null to be passed?
Loading history...
2386
     *
2387
     * @return static|array
2388
     */
2389
    public function set($field, $value = '', $escape = null)
2390
    {
2391
        $field = $this->objectToArray($field);
2392
2393
        if ( ! is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
2394
            $field = [$field => $value];
2395
        }
2396
2397
        $escape = is_bool($escape)
0 ignored issues
show
introduced by
The condition is_bool($escape) is always false.
Loading history...
2398
            ? $escape
2399
            : $this->conn->protectIdentifiers;
2400
2401
        foreach ($field as $key => $value) {
2402
            if ($key === 'birthday' || $key === 'date') {
2403
                if (is_array($value)) {
2404
                    $value = $value[ 'year' ] . '-' . $value[ 'month' ] . '-' . $value[ 'date' ];
2405
                } elseif (is_object($value)) {
2406
                    $value = $value->year . '-' . $value->month . '-' . $value->date;
2407
                }
2408
            } elseif (is_array($value) || is_object($value)) {
2409
                $value = call_user_func_array($this->arrayObjectConversionMethod, [$value]);
2410
            }
2411
2412
            $this->builderCache->binds[ $key ] = $value;
2413
            $this->builderCache->sets[ $this->conn->protectIdentifiers($key, false,
2414
                $escape) ] = ':' . $key;
2415
        }
2416
2417
        return $this;
2418
    }
2419
2420
    //--------------------------------------------------------------------
2421
2422
    /**
2423
     * AbstractQueryBuilder::objectToArray
2424
     *
2425
     * Takes an object as input and converts the class variables to array key/vals
2426
     *
2427
     * @param mixed $object
2428
     *
2429
     * @return  array
2430
     */
2431
    protected function objectToArray($object)
2432
    {
2433
        if ( ! is_object($object)) {
2434
            return $object;
2435
        }
2436
2437
        $array = [];
2438
        foreach (get_object_vars($object) as $key => $value) {
2439
            // There are some built in keys we need to ignore for this conversion
2440
            if ( ! is_object($value) && ! is_array($value) && $key !== '_parent_name') {
2441
                $array[ $key ] = $value;
2442
            }
2443
        }
2444
2445
        return $array;
2446
    }
2447
2448
    //--------------------------------------------------------------------
2449
2450
    /**
2451
     * AbstractQueryBuilder::platformInsertStatement
2452
     *
2453
     * Generates a platform-specific insert string from the supplied data.
2454
     *
2455
     * @param string $table  Table name.
2456
     * @param array  $keys   Insert keys.
2457
     * @param array  $values Insert values.
2458
     *
2459
     * @return string
2460
     */
2461
    abstract protected function platformInsertStatement($table, array $keys, array $values);
2462
2463
    //--------------------------------------------------------------------
2464
2465
    /**
2466
     * AbstractQueryBuilder::insertBatch
2467
     *
2468
     * Execute INSERT batch Sql Query
2469
     *
2470
     * @param array $sets        An associative array of set values.
2471
     *                           sets[][field => value]
2472
     * @param int   $batchSize   Maximum batch size
2473
     * @param bool  $escape      Whether to escape values and identifiers
2474
     *
2475
     * @return bool
2476
     * @throws \Psr\Cache\InvalidArgumentException
2477
     * @throws \O2System\Spl\Exceptions\RuntimeException
2478
     */
2479
    public function insertBatch(array $sets, $batchSize = 1000, $escape = null)
2480
    {
2481
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
2482
2483
        $this->setInsertReplaceBatch($sets);
2484
2485
        // Batch this baby
2486
        $affectedRows = 0;
2487
        for ($i = 0, $total = count($sets); $i < $total; $i += $batchSize) {
2488
            $Sql = $this->platformInsertBatchStatement(
2489
                $this->conn->protectIdentifiers($this->builderCache->from[ 0 ], true, $escape, false),
2490
                $this->builderCache->keys,
2491
                array_slice($this->builderCache->sets, $i, $batchSize)
2492
            );
2493
2494
            if ($this->testMode) {
2495
                ++$affectedRows;
2496
            } else {
2497
                $this->conn->query($Sql, $this->builderCache->binds);
2498
                $affectedRows += $this->conn->getAffectedRows();
2499
            }
2500
        }
2501
2502
        if ( ! $this->testMode) {
2503
            $this->builderCache->resetModifier();
2504
        }
2505
2506
        return $affectedRows;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $affectedRows returns the type integer which is incompatible with the documented return type boolean.
Loading history...
2507
    }
2508
2509
    //--------------------------------------------------------------------
2510
2511
    /**
2512
     * AbstractQueryBuilder::setInsertBatch
2513
     *
2514
     * The "setInsertBatch" function.  Allows key/value pairs to be set for batch inserts
2515
     *
2516
     * @param mixed  $field
2517
     * @param string $value
2518
     * @param bool   $escape Whether to escape values and identifiers
2519
     *
2520
     * @return  void
2521
     */
2522
    protected function setInsertReplaceBatch($field, $value = '', $escape = null)
2523
    {
2524
        $field = $this->batchObjectToArray($field);
2525
2526
        if ( ! is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
2527
            $field = [$field => $value];
2528
        }
2529
2530
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
2531
2532
        $rowKeys = array_keys($this->objectToArray(current($field)));
2533
        sort($rowKeys);
2534
2535
        foreach ($field as $row) {
2536
            $row = $this->objectToArray($row);
2537
            if (count(array_diff($rowKeys, array_keys($row))) > 0
2538
                || count(
2539
                    array_diff(array_keys($row), $rowKeys)
2540
                ) > 0
2541
            ) {
2542
                // batch function above returns an error on an empty array
2543
                $this->builderCache->sets[] = [];
2544
2545
                return;
2546
            }
2547
2548
            ksort($row); // puts $row in the same order as our keys
2549
2550
            $clean = [];
2551
            foreach ($row as $key => $value) {
2552
                $clean[] = ':' . $this->bind($key, $value);
2553
            }
2554
2555
            $row = $clean;
2556
2557
            $this->builderCache->sets[] = '(' . implode(',', $row) . ')';
2558
        }
2559
2560
        foreach ($rowKeys as $rowKey) {
2561
            $this->builderCache->keys[] = $this->conn->protectIdentifiers($rowKey, false, $escape);
2562
        }
2563
    }
2564
2565
    //--------------------------------------------------------------------
2566
2567
    /**
2568
     * Object to Array
2569
     *
2570
     * Takes an object as input and converts the class variables to array key/vals
2571
     *
2572
     * @param object
2573
     *
2574
     * @return    array
2575
     */
2576
    protected function batchObjectToArray($object)
2577
    {
2578
        if ( ! is_object($object)) {
2579
            return $object;
2580
        }
2581
2582
        $array = [];
2583
        $out = get_object_vars($object);
2584
        $fields = array_keys($out);
2585
2586
        foreach ($fields as $field) {
2587
            // There are some built in keys we need to ignore for this conversion
2588
            if ($field !== '_parent_name') {
2589
                $i = 0;
2590
                foreach ($out[ $field ] as $data) {
2591
                    $array[ $i++ ][ $field ] = $data;
2592
                }
2593
            }
2594
        }
2595
2596
        return $array;
2597
    }
2598
2599
    //--------------------------------------------------------------------
2600
2601
    /**
2602
     * AbstractQueryBuilder::platformInsertBatchStatement
2603
     *
2604
     * @param string $table
2605
     * @param array  $keys
2606
     * @param array  $values
2607
     *
2608
     * @return mixed
2609
     */
2610
    abstract protected function platformInsertBatchStatement($table, array $keys, array $values);
2611
2612
    //--------------------------------------------------------------------
2613
2614
    /**
2615
     * AbstractQueryBuilder::replace
2616
     *
2617
     * Compiles an replace into string and runs the query
2618
     *
2619
     * @param array $sets     An associative array of set values.
2620
     *                        sets[][field => value]
2621
     * @param bool  $escape   Whether to escape values and identifiers
2622
     *
2623
     * @return bool
2624
     * @throws \Psr\Cache\InvalidArgumentException
2625
     * @throws \O2System\Spl\Exceptions\RuntimeException
2626
     */
2627
    public function replace(array $sets, $escape = null)
2628
    {
2629
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
2630
2631
        $this->set($sets, null, $escape);
2632
2633
        /**
2634
         * @bugs
2635
         *
2636
         * @issued_by: Triyana Suryapraja Sukmana <https://github.com/tss182>
2637
         * @fixed_by : Mohamad Rafi Randoni <https://github.com/rafirandoni>
2638
         */
2639
        if (count($this->builderCache->sets)) {
2640
            $sqlStatement = $this->platformReplaceStatement(
2641
                $this->conn->protectIdentifiers(
2642
                    $this->builderCache->from[ 0 ],
2643
                    true,
2644
                    $escape,
2645
                    false
2646
                ),
2647
                array_keys($this->builderCache->sets),
2648
                array_values($this->builderCache->sets)
2649
            );
2650
2651
            if ($this->testMode) {
2652
                return $sqlStatement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sqlStatement returns the type string which is incompatible with the documented return type boolean.
Loading history...
2653
            }
2654
2655
            $sqlBinds = $this->builderCache->binds;
2656
            $this->builderCache->resetModifier();
2657
2658
            return $this->conn->query($sqlStatement, $sqlBinds);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->quer...qlStatement, $sqlBinds) also could return the type O2System\Database\DataObjects\Result which is incompatible with the documented return type boolean.
Loading history...
2659
        }
2660
2661
        return false;
2662
    }
2663
2664
    //--------------------------------------------------------------------
2665
2666
    /**
2667
     * AbstractQueryBuilder::platformReplaceStatement
2668
     *
2669
     * Generates a platform-specific update string from the supplied data.
2670
     *
2671
     * @param string $table  Table name.
2672
     * @param array  $keys   Insert keys.
2673
     * @param array  $values Insert values.
2674
     *
2675
     * @return string
2676
     */
2677
    abstract protected function platformReplaceStatement($table, array $keys, array $values);
2678
2679
    //--------------------------------------------------------------------
2680
2681
    /**
2682
     * AbstractQueryBuilder::replaceBatch
2683
     *
2684
     * Execute REPLACE batch Sql Query
2685
     *
2686
     * @param array $sets        An associative array of set values.
2687
     *                           sets[][field => value]
2688
     * @param int   $batchSize   Maximum batch size
2689
     * @param bool  $escape      Whether to escape values and identifiers
2690
     *
2691
     * @return bool
2692
     * @throws \Psr\Cache\InvalidArgumentException
2693
     * @throws \O2System\Spl\Exceptions\RuntimeException
2694
     */
2695
    public function replaceBatch(array $sets, $batchSize = 1000, $escape = null)
2696
    {
2697
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
2698
2699
        $this->setInsertReplaceBatch($sets);
2700
2701
        // Batch this baby
2702
        $affectedRows = 0;
2703
        for ($i = 0, $total = count($sets); $i < $total; $i += $batchSize) {
2704
            $Sql = $this->platformReplaceStatement(
2705
                $this->conn->protectIdentifiers($this->builderCache->from[ 0 ], true, $escape, false),
2706
                $this->builderCache->keys,
2707
                array_slice($this->builderCache->sets, $i, $batchSize)
2708
            );
2709
2710
            if ($this->testMode) {
2711
                ++$affectedRows;
2712
            } else {
2713
                $this->conn->query($Sql, $this->builderCache->binds);
2714
                $affectedRows += $this->conn->getAffectedRows();
2715
            }
2716
        }
2717
2718
        if ( ! $this->testMode) {
2719
            $this->builderCache->resetModifier();
2720
        }
2721
2722
        return $affectedRows;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $affectedRows returns the type integer which is incompatible with the documented return type boolean.
Loading history...
2723
    }
2724
2725
    //--------------------------------------------------------------------
2726
2727
    /**
2728
     * AbstractQueryBuilder::update
2729
     *
2730
     * Compiles an update string and runs the query.
2731
     *
2732
     * @param array $sets      An associative array of set values.
2733
     *                         sets[][field => value]
2734
     * @param array $where     WHERE [field => match]
2735
     * @param bool  $escape    Whether to escape values and identifiers
2736
     *
2737
     * @return bool
2738
     * @throws \Psr\Cache\InvalidArgumentException
2739
     * @throws \O2System\Spl\Exceptions\RuntimeException
2740
     */
2741
    public function update(array $sets, array $where = [], $escape = null)
2742
    {
2743
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
2744
2745
        $this->set($sets, null, $escape);
2746
        $this->where($where);
2747
2748
        /**
2749
         * @bugs
2750
         *
2751
         * @issued_by: Triyana Suryapraja Sukmana <https://github.com/tss182>
2752
         * @fixed_by : Mohamad Rafi Randoni <https://github.com/rafirandoni>
2753
         */
2754
        if (count($this->builderCache->sets) && count($this->builderCache->from)) {
2755
            $sqlStatement = $this->platformUpdateStatement(
2756
                $this->conn->protectIdentifiers(
2757
                    $this->builderCache->from[ 0 ],
2758
                    true,
2759
                    $escape,
2760
                    false
2761
                ),
2762
                $this->builderCache->sets
2763
            );
2764
2765
            if ($this->testMode) {
2766
                return $sqlStatement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sqlStatement returns the type string which is incompatible with the documented return type boolean.
Loading history...
2767
            }
2768
2769
            $sqlBinds = $this->builderCache->binds;
2770
            $this->builderCache->resetModifier();
2771
2772
            return $this->conn->query($sqlStatement, $sqlBinds);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->quer...qlStatement, $sqlBinds) also could return the type O2System\Database\DataObjects\Result which is incompatible with the documented return type boolean.
Loading history...
2773
        }
2774
2775
        return false;
2776
    }
2777
2778
    //--------------------------------------------------------------------
2779
2780
    /**
2781
     * AbstractQueryBuilder::platformUpdateStatement
2782
     *
2783
     * Generates a platform-specific update string from the supplied data.
2784
     *
2785
     * @param string $table    Table name.
2786
     * @param array  $sets     An associative array of set values.
2787
     *                         sets[][field => value]
2788
     *
2789
     * @return string
2790
     */
2791
    abstract protected function platformUpdateStatement($table, array $sets);
2792
2793
    //--------------------------------------------------------------------
2794
2795
    /**
2796
     * AbstractQueryBuilder::updateBatch
2797
     *
2798
     * Execute UPDATE batch Sql Query
2799
     *
2800
     * @param array  $sets      Array of data sets[][field => value]
2801
     * @param string $index     Index field
2802
     * @param int    $batchSize Maximum batch size
2803
     * @param bool   $escape    Whether to escape values and identifiers
2804
     *
2805
     * @return bool
2806
     * @throws \Psr\Cache\InvalidArgumentException
2807
     * @throws \O2System\Spl\Exceptions\RuntimeException
2808
     */
2809
    public function updateBatch(array $sets, $index = null, $batchSize = 1000, $escape = null)
2810
    {
2811
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
2812
2813
        $this->setUpdateBatch($sets, $index);
2814
2815
        $affectedRows = 0;
2816
        for ($i = 0, $total = count($this->builderCache->sets); $i < $total; $i += $batchSize) {
2817
            $sql = $this->platformUpdateBatchStatement(
2818
                $this->builderCache->from[ 0 ],
2819
                array_slice($this->builderCache->sets, $i, $batchSize),
2820
                $this->conn->protectIdentifiers($index, false, $escape, false)
2821
            );
2822
2823
            if ($this->testMode) {
2824
                ++$affectedRows;
2825
            } else {
2826
                $this->conn->query($sql, $this->builderCache->binds);
2827
                $affectedRows += $this->conn->getAffectedRows();
2828
            }
2829
2830
            $this->builderCache[ 'where' ] = [];
2831
        }
2832
2833
        if ( ! $this->testMode) {
2834
            $this->builderCache->resetModifier();
2835
        }
2836
2837
        return $affectedRows;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $affectedRows returns the type integer which is incompatible with the documented return type boolean.
Loading history...
2838
    }
2839
2840
    //--------------------------------------------------------------------
2841
2842
    /**
2843
     * AbstractQueryBuilder::setUpdateBatch
2844
     *
2845
     * The "setUpdateBatch" function.  Allows key/value pairs to be set for batch updating
2846
     *
2847
     * @param mixed  $sets
2848
     * @param string $index
2849
     * @param bool   $escape
2850
     *
2851
     * @return static
2852
     * @throws \O2System\Spl\Exceptions\RuntimeException
2853
     */
2854
    protected function setUpdateBatch(array $sets, $index = '', $escape = null)
2855
    {
2856
        $sets = $this->batchObjectToArray($sets);
2857
2858
        if ( ! is_array($sets)) {
0 ignored issues
show
introduced by
The condition is_array($sets) is always true.
Loading history...
2859
            // @todo error
2860
        }
2861
2862
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
2863
2864
        foreach ($sets as $set) {
2865
            $indexSet = false;
2866
            $row = [];
2867
            foreach ($set as $key => $value) {
2868
                if ($key === $index) {
2869
                    $indexSet = true;
2870
                }
2871
2872
                $bind = $this->bind($key, $value);
2873
2874
                $row[ $this->conn->protectIdentifiers($key, false, $escape) ] = ':' . $bind;
2875
            }
2876
2877
            if ($indexSet === false) {
2878
                // 'One or more rows submitted for batch updating is missing the specified index.'
2879
                throw new RuntimeException('E_DATABASE_BATCH_UPDATE_MISSING_INDEX');
2880
            }
2881
2882
            $this->builderCache->sets[] = $row;
2883
        }
2884
2885
        return $this;
2886
    }
2887
2888
    /**
2889
     * AbstractQueryBuilder::platformUpdateBatchStatement
2890
     *
2891
     * Generates a platform-specific batch update string from the supplied data.
2892
     *
2893
     * @param string $table  Table name
2894
     * @param array  $values Update data
2895
     * @param string $index  WHERE key
2896
     *
2897
     * @return    string
2898
     */
2899
    abstract protected function platformUpdateBatchStatement($table, $values, $index);
2900
2901
    //--------------------------------------------------------------------
2902
2903
    /**
2904
     * AbstractQueryBuilder::delete
2905
     *
2906
     * Compiles a delete string and runs the query
2907
     *
2908
     * @param array $where Where clause.
2909
     * @param int   $limit Limit clause.
2910
     *
2911
     * @return string
2912
     * @throws \O2System\Spl\Exceptions\RuntimeException
2913
     * @throws \Psr\Cache\InvalidArgumentException
2914
     */
2915
    public function delete($where = [], $limit = null)
2916
    {
2917
        $this->where($where);
2918
2919
        if (isset($limit)) {
2920
            $this->limit($limit);
2921
        }
2922
2923
        $sqlStatement = $this->platformDeleteStatement(
2924
            $this->conn->protectIdentifiers(
2925
                $this->builderCache->from[ 0 ],
2926
                true,
2927
                $this->conn->protectIdentifiers,
2928
                false
2929
            )
2930
        );
2931
2932
        if ($this->testMode) {
2933
            return $sqlStatement;
2934
        }
2935
2936
        $sqlBinds = $this->builderCache->binds;
2937
        $this->builderCache->resetModifier();
2938
2939
        return $this->conn->query($sqlStatement, $sqlBinds);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->quer...qlStatement, $sqlBinds) also could return the type boolean which is incompatible with the documented return type string.
Loading history...
2940
    }
2941
2942
    //--------------------------------------------------------------------
2943
2944
    /**
2945
     * AbstractQueryBuilder::platformDeleteStatement
2946
     *
2947
     * Generates a platform-specific delete string from the supplied data
2948
     *
2949
     * @param string $table The table name.
2950
     *
2951
     * @return  string
2952
     */
2953
    abstract protected function platformDeleteStatement($table);
2954
2955
    //--------------------------------------------------------------------
2956
2957
    /**
2958
     * AbstractQueryBuilder::flush
2959
     *
2960
     * Compiles a truncate string and runs the query
2961
     * If the database does not support the truncate() command
2962
     * This function maps to "DELETE FROM table"
2963
     *
2964
     * @param bool $escape Whether to table identifiers
2965
     *
2966
     * @return bool TRUE on success, FALSE on failure
2967
     * @throws \O2System\Spl\Exceptions\RuntimeException
2968
     * @throws \Psr\Cache\InvalidArgumentException
2969
     */
2970
    public function flush($table, $escape = null)
2971
    {
2972
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
2973
2974
        if ($escape) {
2975
            $table = $this->conn->protectIdentifiers($table, true, true);
2976
        }
2977
2978
        $sqlStatement = $this->platformDeleteStatement($table);
2979
2980
        if ($this->testMode === true) {
2981
            return $sqlStatement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sqlStatement returns the type string which is incompatible with the documented return type boolean.
Loading history...
2982
        }
2983
2984
        $this->builderCache->resetModifier();
2985
2986
        return $this->conn->query($sqlStatement);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->query($sqlStatement) also could return the type O2System\Database\DataObjects\Result which is incompatible with the documented return type boolean.
Loading history...
2987
    }
2988
2989
    //--------------------------------------------------------------------
2990
2991
    /**
2992
     * AbstractQueryBuilder::truncate
2993
     *
2994
     * Compiles a truncate string and runs the query
2995
     * If the database does not support the truncate() command
2996
     * This function maps to "DELETE FROM table"
2997
     *
2998
     * @param bool $escape Whether to table identifiers
2999
     *
3000
     * @return bool TRUE on success, FALSE on failure
3001
     * @throws \O2System\Spl\Exceptions\RuntimeException
3002
     * @throws \Psr\Cache\InvalidArgumentException
3003
     */
3004
    public function truncate($table, $escape = null)
3005
    {
3006
        is_bool($escape) || $escape = $this->conn->protectIdentifiers;
3007
3008
        if ($escape) {
3009
            $table = $this->conn->protectIdentifiers($table, true, true);
3010
        }
3011
3012
        $sqlStatement = $this->platformTruncateStatement($table);
3013
3014
        if ($this->testMode === true) {
3015
            return $sqlStatement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sqlStatement returns the type string which is incompatible with the documented return type boolean.
Loading history...
3016
        }
3017
3018
        $this->builderCache->resetModifier();
3019
3020
        return $this->conn->query($sqlStatement);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->query($sqlStatement) also could return the type O2System\Database\DataObjects\Result which is incompatible with the documented return type boolean.
Loading history...
3021
    }
3022
3023
    //--------------------------------------------------------------------
3024
3025
    /**
3026
     * AbstractQueryBuilder::platformTruncateStatement
3027
     *
3028
     * Generates a platform-specific truncate statement.
3029
     *
3030
     * @param string    the table name
0 ignored issues
show
Bug introduced by
The type O2System\Database\Sql\Abstracts\the 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...
3031
     *
3032
     * @return    string
3033
     */
3034
    abstract protected function platformTruncateStatement($table);
3035
3036
    //--------------------------------------------------------------------
3037
3038
    /**
3039
     * AbstractQueryBuilder::binds
3040
     *
3041
     * @param array $binds
3042
     *
3043
     * @return static
3044
     */
3045
    public function binds(array $binds)
3046
    {
3047
        foreach ($binds as $field => $value) {
3048
            $this->bind($field, $value);
3049
        }
3050
3051
        return $this;
3052
    }
3053
3054
    //--------------------------------------------------------------------
3055
3056
    /**
3057
     * AbstractQueryBuilder::subQuery
3058
     *
3059
     * Performs Query Builder sub query mode.
3060
     *
3061
     * @return \O2System\Database\Sql\Abstracts\AbstractQueryBuilder
3062
     */
3063
    public function subQuery()
3064
    {
3065
        $subQuery = clone $this;
3066
        $subQuery->builderCache = new Query\BuilderCache();
0 ignored issues
show
Documentation Bug introduced by
It seems like new O2System\Database\Sq...es\Query\BuilderCache() of type O2System\Database\Sql\Da...ures\Query\BuilderCache is incompatible with the declared type O2System\Database\Sql\Abstracts\QueryBuilderCache of property $builderCache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
3067
3068
        $subQuery->isSubQuery = true;
3069
3070
        return $subQuery;
3071
    }
3072
3073
    // ------------------------------------------------------------------------
3074
3075
    /**
3076
     * AbstractQueryBuilder::compileSelectStatement
3077
     *
3078
     * Compile the SELECT statement
3079
     *
3080
     * Generates a query string based on which functions were used.
3081
     * Should not be called directly.
3082
     *
3083
     * @param bool $selectOverride
3084
     *
3085
     * @return    string
3086
     */
3087
    protected function compileSelectStatement($selectOverride = false)
3088
    {
3089
        // Write the "select" portion of the query
3090
        if ($selectOverride !== false) {
3091
            $sqlStatement = $selectOverride;
3092
        } else {
3093
            $sqlStatement = ( ! $this->builderCache->distinct)
3094
                ? 'SELECT %s'
3095
                : 'SELECT DISTINCT %s';
3096
3097
            if (count($this->builderCache->select) === 0) {
3098
                $SqlSelectStatement = "*";
3099
            } else {
3100
                // Cycle through the "select" portion of the query and prep each column name.
3101
                // The reason we protect identifiers here rather than in the select() function
3102
                // is because until the user calls the from() function we don't know if there are aliases
3103
                foreach ($this->builderCache->select as $selectKey => $selectField) {
3104
                    $noEscape = isset($this->builderCache->noEscape [ $selectKey ])
3105
                        ? $this->builderCache->noEscape [ $selectKey ]
3106
                        : null;
3107
                    $this->builderCache->select [ $selectKey ] = $this->conn->protectIdentifiers(
3108
                        $selectField,
3109
                        false,
3110
                        $noEscape
3111
                    );
3112
                }
3113
3114
                $SqlSelectStatement = implode(", \n\t", $this->builderCache->select);
3115
            }
3116
3117
            $sqlStatement = sprintf($sqlStatement, $SqlSelectStatement);
3118
        }
3119
3120
        return trim($sqlStatement);
0 ignored issues
show
Bug introduced by
It seems like $sqlStatement can also be of type true; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

3120
        return trim(/** @scrutinizer ignore-type */ $sqlStatement);
Loading history...
3121
    }
3122
3123
    //--------------------------------------------------------------------
3124
3125
    /**
3126
     * AbstractQueryBuilder::compileIntoStatement
3127
     *
3128
     * @return string
3129
     */
3130
    protected function compileUnionStatement()
3131
    {
3132
        $sqlStatement = '';
3133
3134
        if (count($this->builderCache->union)) {
3135
            foreach ($this->builderCache->union as $union) {
3136
                $sqlStatement .= "\n UNION \n" . $union;
3137
            }
3138
        }
3139
3140
        if (count($this->builderCache->unionAll)) {
3141
            foreach ($this->builderCache->unionAll as $union) {
3142
                $sqlStatement .= "\n UNION ALL \n" . $union;
3143
            }
3144
        }
3145
3146
        return trim($sqlStatement);
3147
    }
3148
3149
    //--------------------------------------------------------------------
3150
3151
    /**
3152
     * AbstractQueryBuilder::compileIntoStatement
3153
     *
3154
     * @return string
3155
     */
3156
    protected function compileIntoStatement()
3157
    {
3158
        return "\n" . $this->builderCache->into;
3159
    }
3160
3161
    //--------------------------------------------------------------------
3162
3163
    /**
3164
     * AbstractQueryBuilder::compileFromStatement
3165
     *
3166
     * @return string
3167
     */
3168
    protected function compileFromStatement()
3169
    {
3170
        if (count($this->builderCache->from) > 0) {
3171
            return "\n" . sprintf(
3172
                    'FROM %s',
3173
                    implode(',', array_unique($this->builderCache->from))
3174
                );
3175
        }
3176
    }
3177
3178
    //--------------------------------------------------------------------
3179
3180
    /**
3181
     * AbstractQueryBuilder::compileJoinStatement
3182
     *
3183
     * @return string
3184
     */
3185
    protected function compileJoinStatement()
3186
    {
3187
        if (count($this->builderCache->join) > 0) {
3188
            return "\n" . implode("\n", $this->builderCache->join);
3189
        }
3190
    }
3191
3192
    //--------------------------------------------------------------------
3193
3194
    /**
3195
     * AbstractQueryBuilder::compileWhereStatement
3196
     *
3197
     * @return string
3198
     */
3199
    protected function compileWhereStatement()
3200
    {
3201
        return $this->compileWhereHavingStatement('where');
3202
    }
3203
3204
    //--------------------------------------------------------------------
3205
3206
    /**
3207
     * AbstractQueryBuilder::compileWhereHavingStatement
3208
     *
3209
     * Compile WHERE, HAVING statements
3210
     *
3211
     * Escapes identifiers in WHERE and HAVING statements at execution time.
3212
     *
3213
     * Required so that aliases are tracked properly, regardless of whether
3214
     * where(), orWhere(), having(), orHaving are called prior to from(),
3215
     * join() and prefixTable is added only if needed.
3216
     *
3217
     * @param string $cacheKey 'QBWhere' or 'QBHaving'
3218
     *
3219
     * @return    string    Sql statement
3220
     */
3221
    protected function compileWhereHavingStatement($cacheKey)
3222
    {
3223
        if (count($this->builderCache->{$cacheKey}) > 0) {
3224
            for ($i = 0, $c = count($this->builderCache->{$cacheKey}); $i < $c; $i++) {
3225
                // Is this condition already compiled?
3226
                if (is_string($this->builderCache->{$cacheKey}[ $i ])) {
3227
                    continue;
3228
                } elseif ($this->builderCache->{$cacheKey}[ $i ][ 'escape' ] === false) {
3229
                    $this->builderCache->{$cacheKey}[ $i ]
3230
                        = $this->builderCache->{$cacheKey}[ $i ][ 'condition' ];
3231
                    continue;
3232
                }
3233
3234
                // Split multiple conditions
3235
                $conditions = preg_split(
3236
                    '/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i',
3237
                    $this->builderCache->{$cacheKey}[ $i ][ 'condition' ],
3238
                    -1,
3239
                    PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
3240
                );
3241
3242
                for ($ci = 0, $cc = count($conditions); $ci < $cc; $ci++) {
0 ignored issues
show
Bug introduced by
It seems like $conditions can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

3242
                for ($ci = 0, $cc = count(/** @scrutinizer ignore-type */ $conditions); $ci < $cc; $ci++) {
Loading history...
3243
                    if (($op = $this->getOperator($conditions[ $ci ])) === false
3244
                        OR
3245
                        ! preg_match(
3246
                            '/^(\(?)(.*)(' . preg_quote($op, '/') . ')\s*(.*(?<!\)))?(\)?)$/i',
3247
                            $conditions[ $ci ],
3248
                            $matches
3249
                        )
3250
                    ) {
3251
                        continue;
3252
                    }
3253
3254
                    // $matches = array(
3255
                    //  0 => '(test <= foo)',   /* the whole thing */
3256
                    //  1 => '(',       /* optional */
3257
                    //  2 => 'test',        /* the field name */
3258
                    //  3 => ' <= ',        /* $op */
3259
                    //  4 => 'foo',     /* optional, if $op is e.g. 'IS NULL' */
3260
                    //  5 => ')'        /* optional */
3261
                    // );
3262
3263
                    if ( ! empty($matches[ 4 ])) {
3264
                        //$this->isLiteral($matches[4]) OR $matches[4] = $this->protectIdentifiers(trim($matches[4]));
3265
                        $matches[ 4 ] = ' ' . $matches[ 4 ];
3266
                    }
3267
3268
                    $conditions[ $ci ] = $matches[ 1 ] . $this->conn->protectIdentifiers(trim($matches[ 2 ]))
3269
                        . ' ' . trim($matches[ 3 ]) . $matches[ 4 ] . $matches[ 5 ];
3270
                }
3271
3272
                $this->builderCache->{$cacheKey}[ $i ] = implode('', $conditions);
0 ignored issues
show
Bug introduced by
It seems like $conditions can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

3272
                $this->builderCache->{$cacheKey}[ $i ] = implode('', /** @scrutinizer ignore-type */ $conditions);
Loading history...
3273
            }
3274
3275
            if ($cacheKey === 'having') {
3276
                return "\n" . sprintf(
3277
                        'HAVING %s',
3278
                        implode("\n", $this->builderCache->having)
3279
                    );
3280
            }
3281
3282
            return "\n" . sprintf(
3283
                    'WHERE %s',
3284
                    implode("\n", $this->builderCache->{$cacheKey})
3285
                );
3286
        }
3287
3288
        return '';
3289
    }
3290
3291
    //--------------------------------------------------------------------
3292
3293
    /**
3294
     * AbstractQueryBuilder::compileGroupByStatement
3295
     *
3296
     * Compile GROUP BY
3297
     *
3298
     * Escapes identifiers in GROUP BY statements at execution time.
3299
     *
3300
     * Required so that aliases are tracked properly, regardless of wether
3301
     * groupBy() is called prior to from(), join() and prefixTable is added
3302
     * only if needed.
3303
     *
3304
     * @return    string    Sql statement
3305
     */
3306
    protected function compileGroupByStatement()
3307
    {
3308
        if (count($this->builderCache->groupBy) > 0) {
3309
            for ($i = 0, $c = count($this->builderCache->groupBy); $i < $c; $i++) {
3310
                // Is it already compiled?
3311
                if (is_string($this->builderCache->groupBy[ $i ])) {
3312
                    continue;
3313
                }
3314
3315
                $this->builderCache->groupBy[ $i ] = ($this->builderCache->groupBy[ $i ][ 'escape' ]
3316
                    === false OR
3317
                    $this->isLiteral(
3318
                        $this->builderCache->groupBy[ $i ][ 'field' ]
3319
                    ))
3320
                    ? $this->builderCache->groupBy[ $i ][ 'field' ]
3321
                    : $this->conn->protectIdentifiers($this->builderCache->groupBy[ $i ][ 'field' ]);
3322
            }
3323
3324
            return "\n" . sprintf(
3325
                    'GROUP BY %s',
3326
                    implode(', ', $this->builderCache->groupBy)
3327
                );
3328
        }
3329
3330
        return '';
3331
    }
3332
3333
    //--------------------------------------------------------------------
3334
3335
    /**
3336
     * AbstractQueryBuilder::isLiteral
3337
     *
3338
     * Determines if a string represents a literal value or a field name
3339
     *
3340
     * @param string $string
3341
     *
3342
     * @return    bool
3343
     */
3344
    protected function isLiteral($string)
3345
    {
3346
        $string = trim($string);
3347
3348
        if (empty($string) || ctype_digit($string) || (string)(float)$string === $string
3349
            || in_array(
3350
                strtoupper($string),
3351
                ['TRUE', 'FALSE'],
3352
                true
3353
            )
3354
        ) {
3355
            return true;
3356
        }
3357
3358
        static $stringArray;
3359
3360
        if (empty($stringArray)) {
3361
            $stringArray = ($this->conn->getConfig('escapeCharacter') !== '"')
3362
                ? ['"', "'"]
3363
                : ["'"];
3364
        }
3365
3366
        return in_array($string[ 0 ], $stringArray, true);
3367
    }
3368
3369
    //--------------------------------------------------------------------
3370
3371
    /**
3372
     * AbstractQueryBuilder::compileHavingStatement
3373
     *
3374
     * @return string
3375
     */
3376
    protected function compileHavingStatement()
3377
    {
3378
        return $this->compileWhereHavingStatement('having');
3379
    }
3380
3381
    //--------------------------------------------------------------------
3382
3383
    /**
3384
     * AbstractQueryBuilder::compileHavingStatement
3385
     *
3386
     * @return string
3387
     */
3388
    protected function compileBetweenStatement()
3389
    {
3390
        return $this->compileWhereHavingStatement('between');
3391
    }
3392
3393
    //--------------------------------------------------------------------
3394
3395
    /**
3396
     * AbstractQueryBuilder::compileHavingStatement
3397
     *
3398
     * @return string
3399
     */
3400
    protected function compileNotBetweenStatement()
3401
    {
3402
        return $this->compileWhereHavingStatement('notBetween');
3403
    }
3404
3405
    //--------------------------------------------------------------------
3406
3407
    /**
3408
     * AbstractQueryBuilder::compileOrderByStatement
3409
     *
3410
     * Compile ORDER BY
3411
     *
3412
     * Escapes identifiers in ORDER BY statements at execution time.
3413
     *
3414
     * Required so that aliases are tracked properly, regardless of wether
3415
     * orderBy() is called prior to from(), join() and prefixTable is added
3416
     * only if needed.
3417
     *
3418
     * @return    string    Sql statement
3419
     */
3420
    protected function compileOrderByStatement()
3421
    {
3422
        if (is_array($this->builderCache->orderBy) && count($this->builderCache->orderBy) > 0) {
3423
            for ($i = 0, $c = count($this->builderCache->orderBy); $i < $c; $i++) {
3424
                if ($this->builderCache->orderBy[ $i ][ 'escape' ] !== false
3425
                    && ! $this->isLiteral(
3426
                        $this->builderCache->orderBy[ $i ][ 'field' ]
3427
                    )
3428
                ) {
3429
                    $this->builderCache->orderBy[ $i ][ 'field' ] = $this->conn->protectIdentifiers(
3430
                        $this->builderCache->orderBy[ $i ][ 'field' ]
3431
                    );
3432
                }
3433
3434
                $this->builderCache->orderBy[ $i ] = $this->builderCache->orderBy[ $i ][ 'field' ]
3435
                    . $this->builderCache->orderBy[ $i ][ 'direction' ];
3436
            }
3437
3438
            return $this->builderCache->orderBy = "\n" . sprintf(
3439
                    'ORDER BY %s',
3440
                    implode(', ', $this->builderCache->orderBy)
3441
                );
3442
        } elseif (is_string($this->builderCache->orderBy)) {
3443
            return $this->builderCache->orderBy;
3444
        }
3445
3446
        return '';
3447
    }
3448
3449
    //--------------------------------------------------------------------
3450
3451
    /**
3452
     * AbstractQueryBuilder::compileLimitStatement
3453
     *
3454
     * @return string
3455
     */
3456
    protected function compileLimitStatement()
3457
    {
3458
        if ($this->builderCache->limit) {
3459
            if ($this->builderCache->offset) {
3460
                return sprintf(
3461
                    'LIMIT %s OFFSET %s',
3462
                    $this->builderCache->limit,
3463
                    $this->builderCache->offset
3464
                );
3465
            }
3466
3467
            return "\n" . sprintf(
3468
                    'LIMIT %s',
3469
                    $this->builderCache->limit
3470
                );
3471
        }
3472
    }
3473
}
3474