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 ( deb718...3121b4 )
by Steeven
03:03
created

AbstractConnection::protectIdentifiers()   F

Complexity

Conditions 31
Paths 886

Size

Total Lines 158
Code Lines 70

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 70
c 2
b 0
f 0
dl 0
loc 158
rs 0.1583
cc 31
nc 886
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
namespace O2System\Database\Sql\Abstracts;
14
15
// ------------------------------------------------------------------------
16
17
use O2System\Cache\Item;
0 ignored issues
show
Bug introduced by
The type O2System\Cache\Item 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...
18
use O2System\Database\DataObjects\Result;
19
use O2System\Database\DataStructures\Config;
20
use O2System\Database\Sql\DataStructures\Query\Statement;
21
use O2System\Spl\Exceptions\RuntimeException;
22
use O2System\Spl\Traits\Collectors\ConfigCollectorTrait;
23
24
/**
25
 * Class AbstractConnection
26
 *
27
 * @package O2System\Database\Sql\Abstracts
28
 */
29
abstract class AbstractConnection
30
{
31
    use ConfigCollectorTrait;
32
33
    /**
34
     * AbstractConnection::$debugEnable
35
     *
36
     * Connection debug mode flag.
37
     *
38
     * @var bool
39
     */
40
    public $debugEnable = false;
41
42
    /**
43
     * AbstractConnection::$transactionEnable
44
     *
45
     * Connection debug mode flag.
46
     *
47
     * @var bool
48
     */
49
    public $transactionEnable = false;
50
    /**
51
     * AbstractConnection::$database
52
     *
53
     * Connection database name.
54
     *
55
     * @var string
56
     */
57
    public $database;
58
    /**
59
     * AbstractConnection::$swapTablePrefix
60
     *
61
     * Swap database table prefix.
62
     *
63
     * @var string
64
     */
65
    public $swapTablePrefix;
66
    /**
67
     * AbstractConnection::$protectIdentifiers
68
     *
69
     * Protect identifiers mode flag.
70
     *
71
     * @var bool
72
     */
73
    public $protectIdentifiers = true;
74
    /**
75
     * AbstractConnection::$disableQueryExecution
76
     *
77
     * Query execution mode flag.
78
     *
79
     * @var bool
80
     */
81
    public $disableQueryExecution = false;
82
    /**
83
     * AbstractConnection::$queriesResultCache
84
     *
85
     * Array of query objects that have executed
86
     * on this connection.
87
     *
88
     * @var array
89
     */
90
    public $queriesResultCache = [];
91
    /**
92
     * AbstractConnection::$cacheEnable
93
     *
94
     * Connection cache mode flag.
95
     *
96
     * @var bool
97
     */
98
    protected $cacheEnable = false;
99
    /**
100
     * AbstractConnection::$platform
101
     *
102
     * Database driver platform name.
103
     *
104
     * @var string
105
     */
106
    protected $platform;
107
108
    /**
109
     * AbstractConnection::$handle
110
     *
111
     * Connection handle
112
     *
113
     * @var mixed
114
     */
115
    protected $handle;
116
117
    /**
118
     * AbstractConnection::$persistent
119
     *
120
     * Connection persistent mode flag.
121
     *
122
     * @var bool
123
     */
124
    protected $persistent = true;
125
126
    /**
127
     * AbstractConnection::$connectTimeStart
128
     *
129
     * Microtime when connection was made.
130
     *
131
     * @var float
132
     */
133
    protected $connectTimeStart;
134
135
    /**
136
     * AbstractConnection::$connectTimeDuration
137
     *
138
     * How long it took to establish connection.
139
     *
140
     * @var float
141
     */
142
    protected $connectTimeDuration;
143
144
    /**
145
     * AbstractConnection::$transactionInProgress
146
     *
147
     * Transaction is in progress.
148
     *
149
     * @var bool
150
     */
151
    protected $transactionInProgress = false;
152
153
    /**
154
     * AbstractConnection::$transactionStatus
155
     *
156
     * Transaction status flag.
157
     *
158
     * @var bool
159
     */
160
    protected $transactionStatus = false;
161
162
    /**
163
     * AbstractConnection::$transactionDepth
164
     *
165
     * Transaction depth numbers.
166
     *
167
     * @var int
168
     */
169
    protected $transactionDepth = 0;
170
171
    /**
172
     * AbstractConnection::$queriesCache
173
     *
174
     * Array of query objects that have executed
175
     * on this connection.
176
     *
177
     * @var array
178
     */
179
    protected $queriesCache = [];
180
181
    /**
182
     * AbstractConnection::$queryBuilder
183
     *
184
     * Query Builder instance.
185
     *
186
     * @var AbstractQueryBuilder
187
     */
188
    protected $queryBuilder;
189
190
    /**
191
     * AbstractConnection::$forge
192
     *
193
     * Forge instance.
194
     *
195
     * @var AbstractForge
196
     */
197
    protected $forge;
198
199
    // ------------------------------------------------------------------------
200
201
    /**
202
     * AbstractConnection::__construct
203
     *
204
     * @param \O2System\Database\DataStructures\Config $config
205
     *
206
     * @throws \O2System\Spl\Exceptions\RuntimeException
207
     */
208
    public function __construct(Config $config)
209
    {
210
        language()
211
            ->addFilePath(str_replace('Sql' . DIRECTORY_SEPARATOR . 'Abstracts', '', __DIR__) . DIRECTORY_SEPARATOR)
212
            ->loadFile('database');
213
214
        $config->merge(
215
            array_merge(
216
                [
217
                    'escapeCharacter'     => '"',
218
                    'reservedIdentifiers' => ['*'],
219
                    'likeEscapeStatement' => ' ESCAPE \'%s\' ',
220
                    'likeEscapeCharacter' => '!',
221
                    'bindMarker'          => '?',
222
                ],
223
                $this->getConfig()
0 ignored issues
show
Bug introduced by
It seems like $this->getConfig() can also be of type false; however, parameter $array2 of array_merge() does only seem to accept array|null, 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

223
                /** @scrutinizer ignore-type */ $this->getConfig()
Loading history...
224
            )
225
        );
226
227
        $this->config = $config;
0 ignored issues
show
Documentation Bug introduced by
It seems like $config of type O2System\Database\DataStructures\Config is incompatible with the declared type array of property $config.

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...
228
229
        $this->debugEnable = (bool)$config->offsetGet('debugEnable');
230
        $this->transactionEnable = (bool)$config->offsetGet('transEnable');
231
        $this->database = $config->offsetGet('database');
232
233
        $this->connect(
234
            ($config->offsetExists('persistent')
235
                ? $this->persistent = $config->persistent
0 ignored issues
show
Bug Best Practice introduced by
The property persistent does not exist on O2System\Database\DataStructures\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
236
                : true
237
            )
238
        );
239
    }
240
241
    // ------------------------------------------------------------------------
242
243
    /**
244
     * AbstractConnection::connect
245
     *
246
     * Establish the connection.
247
     *
248
     * @param bool $persistent
249
     *
250
     * @return void
251
     * @throws \O2System\Spl\Exceptions\RuntimeException
252
     */
253
    final public function connect($persistent = true)
254
    {
255
        /* If an established connection is available, then there's
256
         * no need to connect and select the database.
257
         *
258
         * Depending on the database driver, conn_id can be either
259
         * boolean TRUE, a resource or an object.
260
         */
261
        if ($this->handle) {
262
            return;
263
        }
264
265
        //--------------------------------------------------------------------
266
267
        $this->persistent = $persistent;
268
        $this->connectTimeStart = microtime(true);
269
270
        // Connect to the database and set the connection ID
271
        $this->platformConnectHandler($this->config);
0 ignored issues
show
Bug introduced by
$this->config of type array is incompatible with the type O2System\Database\DataStructures\Config expected by parameter $config of O2System\Database\Sql\Ab...latformConnectHandler(). ( Ignorable by Annotation )

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

271
        $this->platformConnectHandler(/** @scrutinizer ignore-type */ $this->config);
Loading history...
272
273
        // No connection resource? Check if there is a failover else throw an error
274
        if ( ! $this->handle) {
275
            // Check if there is a failover set
276
            if ( ! empty($this->config[ 'failover' ]) && is_array($this->config[ 'failover' ])) {
277
                // Go over all the failovers
278
                foreach ($this->config[ 'failover' ] as $failover) {
279
280
                    // Try to connect
281
                    $this->platformConnectHandler($failover = new Config($failover));
282
283
                    // If a connection is made break the foreach loop
284
                    if ($this->handle) {
285
                        $this->config = $failover;
0 ignored issues
show
Documentation Bug introduced by
It seems like $failover of type O2System\Database\DataStructures\Config is incompatible with the declared type array of property $config.

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...
286
                        break;
287
                    }
288
                }
289
            }
290
291
            // We still don't have a connection?
292
            if ( ! $this->handle) {
293
                throw new RuntimeException('DB_E_UNABLE_TO_CONNECT', 0, [$this->platform]);
294
            }
295
        }
296
297
        $this->connectTimeDuration = microtime(true) - $this->connectTimeStart;
298
    }
299
300
    // ------------------------------------------------------------------------
301
302
    /**
303
     * AbstractConnection::platformConnectHandler
304
     *
305
     * Driver dependent way method for open the connection.
306
     *
307
     * @param Config $config The connection configuration.
308
     *
309
     * @return mixed
310
     */
311
    abstract protected function platformConnectHandler(Config $config);
312
313
    // ------------------------------------------------------------------------
314
315
    /**
316
     * AbstractConnection::isSupported
317
     *
318
     * Check if the platform is supported.
319
     *
320
     * @return bool
321
     */
322
    abstract public function isSupported();
323
324
    //--------------------------------------------------------------------
325
326
    /**
327
     * AbstractConnection::getPlatform
328
     *
329
     * Get the name of the database platform of this connection.
330
     *
331
     * @return string The name of the database platform.
332
     */
333
    public function getPlatform()
334
    {
335
        return $this->platform;
336
    }
337
338
    //--------------------------------------------------------------------
339
340
    /**
341
     * AbstractConnection::getVersion
342
     *
343
     * Get the version of the database platform of this connection.
344
     *
345
     * @return \O2System\Spl\DataStructures\SplArrayObject
346
     */
347
    public function getPlatformInfo()
348
    {
349
        if (isset($this->queriesResultCache[ 'platformInfo' ])) {
350
            return $this->queriesResultCache[ 'platformInfo' ];
351
        }
352
353
        return $this->queriesResultCache[ 'platformInfo' ] = $this->platformGetPlatformInfoHandler();
354
    }
355
356
    //--------------------------------------------------------------------
357
358
    /**
359
     * AbstractConnection::platformGetPlatformVersionHandler
360
     *
361
     * Platform getting version handler.
362
     *
363
     * @return \O2System\Spl\DataStructures\SplArrayObject
364
     */
365
    abstract protected function platformGetPlatformInfoHandler();
366
367
    // ------------------------------------------------------------------------
368
369
    /**
370
     * AbstractConnection::reconnect
371
     *
372
     * Keep or establish the connection if no queries have been sent for
373
     * a length of time exceeding the server's idle timeout.
374
     *
375
     * @return void
376
     */
377
    public function reconnect()
378
    {
379
        if (empty($this->handle)) {
380
            $this->platformConnectHandler($this->config);
0 ignored issues
show
Bug introduced by
$this->config of type array is incompatible with the type O2System\Database\DataStructures\Config expected by parameter $config of O2System\Database\Sql\Ab...latformConnectHandler(). ( Ignorable by Annotation )

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

380
            $this->platformConnectHandler(/** @scrutinizer ignore-type */ $this->config);
Loading history...
381
        }
382
    }
383
384
    // ------------------------------------------------------------------------
385
386
    /**
387
     * AbstractConnection::connected
388
     *
389
     * Determine if the connection is connected
390
     *
391
     * @return bool
392
     */
393
    final public function connected()
394
    {
395
        return (bool)($this->handle === false
396
            ? false
397
            : true);
398
    }
399
400
    // ------------------------------------------------------------------------
401
402
    /**
403
     * AbstractConnection::disconnect
404
     *
405
     * Disconnect database connection.
406
     *
407
     * @return void
408
     */
409
    final public function disconnect()
410
    {
411
        if ($this->handle) {
412
            $this->platformDisconnectHandler();
413
            $this->handle = false;
414
        }
415
    }
416
417
    //--------------------------------------------------------------------
418
419
    /**
420
     * AbstractConnection::disconnectHandler
421
     *
422
     * Driver dependent way method for closing the connection.
423
     *
424
     * @return mixed
425
     */
426
    abstract protected function platformDisconnectHandler();
427
428
    // ------------------------------------------------------------------------
429
430
    /**
431
     * AbstractConnection::getConnectionTimeStart
432
     *
433
     * Returns the time we started to connect to this database in
434
     * seconds with microseconds.
435
     *
436
     * Used by the Debug Toolbar's timeline.
437
     *
438
     * @return float
439
     */
440
    final public function getConnectTimeStart()
441
    {
442
        return (int)$this->connectTimeStart;
443
    }
444
445
    //--------------------------------------------------------------------
446
447
    /**
448
     * AbstractConnection::getConnectTimeDuration
449
     *
450
     * Returns the number of seconds with microseconds that it took
451
     * to connect to the database.
452
     *
453
     * Used by the Debug Toolbar's timeline.
454
     *
455
     * @param int $decimals
456
     *
457
     * @return mixed
458
     */
459
    final public function getConnectTimeDuration($decimals = 6)
460
    {
461
        return number_format($this->connectTimeDuration, $decimals);
462
    }
463
464
    // ------------------------------------------------------------------------
465
466
    /**
467
     * AbstractConnection::getQueries
468
     *
469
     * Returns Queries Collections
470
     *
471
     * @return array
472
     */
473
    final public function getQueries()
474
    {
475
        return $this->queriesCache;
476
    }
477
478
    // ------------------------------------------------------------------------
479
480
    /**
481
     * AbstractConnection::getQueriesCount
482
     *
483
     * Returns the total number of queries that have been performed
484
     * on this connection.
485
     *
486
     * @return int
487
     */
488
    final public function getQueriesCount()
489
    {
490
        return (int)count($this->queriesCache);
491
    }
492
493
    //--------------------------------------------------------------------
494
495
    /**
496
     * AbstractConnection::getLastQuery
497
     *
498
     * Returns the last query's statement object.
499
     *
500
     * @return Statement
501
     */
502
    public function getLastQuery()
503
    {
504
        return end($this->queriesCache);
505
    }
506
507
    //--------------------------------------------------------------------
508
509
    /**
510
     * AbstractConnection::setDatabase
511
     *
512
     * Set a specific database table to use.
513
     *
514
     * @param string $database Database name.
515
     *
516
     * @return static
517
     */
518
    public function setDatabase($database)
519
    {
520
        $this->database = $database;
521
522
        return $this;
523
    }
524
525
    //--------------------------------------------------------------------
526
527
    /**
528
     * AbstractConnection::hasDatabase
529
     *
530
     * Check if the database exists or not.
531
     *
532
     * @param string $databaseName The database name.
533
     *
534
     * @return bool Returns false if database doesn't exists.
535
     * @throws \O2System\Spl\Exceptions\RuntimeException
536
     */
537
    final public function hasDatabase($databaseName)
538
    {
539
        if (empty($this->queriesResultCache[ 'databaseNames' ])) {
540
            $this->getDatabases();
541
        }
542
543
        return (bool)in_array($databaseName, $this->queriesResultCache[ 'databaseNames' ]);
544
    }
545
546
    //--------------------------------------------------------------------
547
548
    /**
549
     * AbstractConnection::getDatabases
550
     *
551
     * Get list of current connection databases.
552
     *
553
     * @return array Returns an array.
554
     * @throws \O2System\Spl\Exceptions\RuntimeException
555
     */
556
    abstract public function getDatabases();
557
558
    // ------------------------------------------------------------------------
559
560
    /**
561
     * AbstractConnection::setTablePrefix
562
     *
563
     * @param string $tablePrefix The database table prefix.
564
     *
565
     * @return string
566
     */
567
    final public function setTablePrefix($tablePrefix)
568
    {
569
        return $this->config[ 'tablePrefix' ] = $tablePrefix;
570
    }
571
572
    // ------------------------------------------------------------------------
573
574
    /**
575
     * AbstractConnection::hasTable
576
     *
577
     * Check if table exists at current connection database.
578
     *
579
     * @param $table
580
     *
581
     * @return bool
582
     * @throws \O2System\Spl\Exceptions\RuntimeException
583
     */
584
    public function hasTable($table)
585
    {
586
        $table = $this->prefixTable($table);
587
588
        if (empty($this->queriesResultCache[ 'tableNames' ])) {
589
            $this->getTables();
590
        }
591
592
        return (bool)in_array($table, $this->queriesResultCache[ 'tableNames' ]);
593
    }
594
595
    // ------------------------------------------------------------------------
596
597
    /**
598
     * AbstractConnection::prefixTable
599
     *
600
     * @param string $tableName Database table name.
601
     *
602
     * @return string Returns prefixed table name.
603
     */
604
    final public function prefixTable($tableName)
605
    {
606
        $tablePrefix = $this->config[ 'tablePrefix' ];
607
608
        if (empty($tablePrefix)) {
609
            return $tableName;
610
        }
611
612
        return $tablePrefix . str_replace($tablePrefix, '', $tableName);
613
    }
614
615
    // ------------------------------------------------------------------------
616
617
    /**
618
     * AbstractConnection::getTables
619
     *
620
     * Get list of current database tables.
621
     *
622
     * @param bool $prefixLimit If sets TRUE the query will be limit using database table prefix.
623
     *
624
     * @return array Returns an array
625
     * @throws \O2System\Spl\Exceptions\RuntimeException
626
     */
627
    abstract public function getTables($prefixLimit = false);
628
629
    // ------------------------------------------------------------------------
630
631
    /**
632
     * AbstractConnection::hasColumn
633
     *
634
     * Check if table exists at current connection database.
635
     *
636
     * @param string $column Database table field name.
637
     * @param string $table  Database table name.
638
     *
639
     * @return bool
640
     * @throws \O2System\Spl\Exceptions\RuntimeException
641
     */
642
    public function hasColumn($column, $table)
643
    {
644
        $table = $this->prefixTable($table);
645
646
        if (empty($this->queriesResultCache[ 'tableColumns' ][ $table ])) {
647
            $this->getColumns($table);
648
        }
649
650
        return (bool)isset($this->queriesResultCache[ 'tableColumns' ][ $table ][ $column ]);
651
    }
652
653
    // ------------------------------------------------------------------------
654
655
    /**
656
     * AbstractConnection::getColumns
657
     *
658
     * @param string $table The database table name.
659
     *
660
     * @return array
661
     * @throws \O2System\Spl\Exceptions\RuntimeException
662
     */
663
    abstract public function getColumns($table);
664
665
    // ------------------------------------------------------------------------
666
667
    /**
668
     * AbstractConnection::execute
669
     *
670
     * Execute Sql statement against database.
671
     *
672
     * @param string $sqlStatement The Sql statement.
673
     *
674
     * @return bool
675
     * @throws \O2System\Spl\Exceptions\RuntimeException
676
     */
677
    public function execute($sqlStatement)
678
    {
679
        if (empty($this->handle)) {
680
            $this->connect();
681
        }
682
683
        $queryStatement = new Statement();
684
        $queryStatement->setSqlStatement($sqlStatement);
685
        $queryStatement->setSqlFinalStatement($sqlStatement);
686
687
        $startTime = microtime(true);
688
        $result = $this->platformExecuteHandler($queryStatement);
689
690
        $queryStatement->setDuration($startTime);
691
        $queryStatement->setAffectedRows($this->getAffectedRows());
692
        $queryStatement->setLastInsertId($this->getLastInsertId());
693
694
        if ( ! array_key_exists($queryStatement->getKey(), $this->queriesCache)) {
695
            $this->queriesCache[ $queryStatement->getKey() ] = $queryStatement;
696
        }
697
698
        if ($queryStatement->hasErrors()) {
699
            if ($this->transactionInProgress) {
700
                $this->transactionStatus = false;
701
                $this->transactionRollBack();
702
                $this->transactionInProgress = false;
703
            }
704
705
            if ($this->debugEnable) {
706
                $message = $queryStatement->getLatestErrorMessage() .
707
                    ' on sql statement: <br><pre>' . $sqlStatement . '</pre><br><br>';
708
709
                throw new RuntimeException($message, $queryStatement->getLatestErrorCode());
710
            }
711
712
            return false;
713
        }
714
715
        if ($this->transactionInProgress) {
716
            $this->transactionStatus = true;
717
        }
718
719
        return (bool)$result;
720
    }
721
722
    // ------------------------------------------------------------------------
723
724
    /**
725
     * AbstractConnection::executeHandler
726
     *
727
     * Driver dependent way method for execute the Sql statement.
728
     *
729
     * @param Statement $statement Query object.
730
     *
731
     * @return bool
732
     */
733
    abstract protected function platformExecuteHandler(Statement &$statement);
734
735
    // ------------------------------------------------------------------------
736
737
    /**
738
     * AbstractConnection::getAffectedRows
739
     *
740
     * Get the total number of affected rows from the last query execution.
741
     *
742
     * @return int  Returns total number of affected rows
743
     */
744
    abstract public function getAffectedRows();
745
746
    // ------------------------------------------------------------------------
747
748
    /**
749
     * AbstractConnection::getLastInsertId
750
     *
751
     * Get last insert id from the last insert query execution.
752
     *
753
     * @return int  Returns total number of affected rows
754
     */
755
    abstract public function getLastInsertId();
756
757
    // ------------------------------------------------------------------------
758
759
    /**
760
     * AbstractConnection::transactionRollBack
761
     *
762
     * RollBack a transaction.
763
     *
764
     * @return bool
765
     */
766
    public function transactionRollBack()
767
    {
768
        if ($this->transactionInProgress) {
769
            return $this->platformTransactionRollBackHandler();
770
        }
771
772
        return false;
773
    }
774
775
    // ------------------------------------------------------------------------
776
777
    /**
778
     * AbstractConnection::platformTransactionRollBackHandler
779
     *
780
     * Platform rolling back a transaction handler.
781
     *
782
     * @return bool
783
     */
784
    abstract protected function platformTransactionRollBackHandler();
785
786
    // ------------------------------------------------------------------------
787
788
    /**
789
     * AbstractConnection::query
790
     *
791
     * @param string $sqlStatement
792
     * @param array  $binds
793
     *
794
     * @return bool|\O2System\Database\DataObjects\Result Returns boolean if the query is contains writing syntax
795
     * @throws \O2System\Spl\Exceptions\RuntimeException
796
     * @throws \Psr\Cache\InvalidArgumentException
797
     */
798
    public function query($sqlStatement, array $binds = [])
799
    {
800
        if (empty($this->handle)) {
801
            $this->connect($this->persistent);
802
        }
803
804
        $result = false;
805
        $queryStatement = new Statement();
806
807
        $queryStatement->setSqlStatement($sqlStatement, $binds);
808
        $queryStatement->setSqlFinalStatement($this->compileSqlBinds($sqlStatement, $binds));
809
810
        if ( ! empty($this->swapTablePrefix) AND ! empty($this->config->tablePrefix)) {
811
            $queryStatement->swapTablePrefix($this->config->tablePrefix, $this->swapTablePrefix);
812
        }
813
814
        $startTime = microtime(true);
815
816
        // Run the query for real
817
        if ($this->disableQueryExecution === false) {
818
            if ($queryStatement->isWriteStatement()) {
819
                $result = $this->platformExecuteHandler($queryStatement);
820
821
                if ($this->transactionInProgress) {
822
                    $this->transactionStatus = $result;
823
                }
824
            } else {
825
                if (class_exists('O2System\Framework', false) &&
826
                    $this->cacheEnable === true
827
                ) {
828
                    $cacheKey = $queryStatement->getKey();
829
                    $cacheHandle = cache()->getItemPool('default');
0 ignored issues
show
Bug introduced by
The function cache was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

829
                    $cacheHandle = /** @scrutinizer ignore-call */ cache()->getItemPool('default');
Loading history...
830
831
                    if (cache()->hasItemPool('database')) {
832
                        $cacheHandle = cache()->getItemPool('output');
833
                    }
834
835
                    if ($cacheHandle instanceof \Psr\Cache\CacheItemPoolInterface) {
836
                        if ($cacheHandle->hasItem($cacheKey)) {
837
                            $rows = $cacheHandle->getItem($cacheKey)->get();
838
                        } else {
839
                            $rows = $this->platformQueryHandler($queryStatement);
840
                            $cacheHandle->save(new Item($cacheKey, $rows));
841
                        }
842
                    }
843
844
                    if ( ! isset($rows)) {
845
                        $rows = $this->platformQueryHandler($queryStatement);
846
                    }
847
848
                    $this->cache($this->config->cacheEnable);
849
850
                } else {
851
                    $rows = $this->platformQueryHandler($queryStatement);
852
                }
853
854
                $result = new Result($rows);
855
            }
856
        }
857
858
        $queryStatement->setDuration($startTime);
859
        $queryStatement->addHit(1);
860
861
        if ( ! array_key_exists($queryStatement->getKey(), $this->queriesCache)) {
862
            $this->queriesCache[ $queryStatement->getKey() ] = $queryStatement;
863
        }
864
865
        if ($queryStatement->hasErrors()) {
866
            if ($this->debugEnable) {
867
                $message = $queryStatement->getLatestErrorMessage() .
868
                    ' on sql statement: <br><pre>' . $sqlStatement . '</pre><br><br>';
869
870
                throw new RuntimeException($message, $queryStatement->getLatestErrorCode());
871
            }
872
873
            if ($this->transactionInProgress) {
874
                $this->transactionStatus = false;
875
                $this->transactionRollBack();
876
                $this->transactionInProgress = false;
877
            }
878
879
            return false;
880
        }
881
882
        if ($this->transactionInProgress) {
883
            $this->transactionStatus = true;
884
        }
885
886
        return $result;
887
    }
888
889
    // ------------------------------------------------------------------------
890
891
    /**
892
     * AbstractConnection::compileSqlBinds
893
     *
894
     * Escapes and inserts any binds into the final Sql statement object.
895
     *
896
     * @return string
897
     */
898
    public function compileSqlBinds($sqlStatement, array $binds = [])
899
    {
900
        $hasSqlBinders = strpos($sqlStatement, ':') !== false;
901
902
        if (empty($binds) || empty($this->config[ 'bindMarker' ]) ||
903
            (strpos($sqlStatement, $this->config[ 'bindMarker' ]) === false &&
904
                $hasSqlBinders === false)
905
        ) {
906
            return $sqlStatement;
907
        }
908
909
        if ( ! is_array($binds)) {
0 ignored issues
show
introduced by
The condition is_array($binds) is always true.
Loading history...
910
            $sqlBinds = [$binds];
911
            $bindCount = 1;
912
        } else {
913
            $sqlBinds = $binds;
914
            $bindCount = count($sqlBinds);
915
        }
916
917
        // Reverse the binds so that duplicate named binds
918
        // will be processed prior to the original binds.
919
        if ( ! is_numeric(key(array_slice($sqlBinds, 0, 1)))) {
920
            $sqlBinds = array_reverse($sqlBinds);
921
        }
922
923
        // We'll need marker length later
924
        $markerLength = strlen($this->config[ 'bindMarker' ]);
925
926
        if ($hasSqlBinders) {
927
            $sqlStatement = $this->replaceNamedBinds($sqlStatement, $sqlBinds);
928
        } else {
929
            $sqlStatement = $this->replaceSimpleBinds($sqlStatement, $sqlBinds, $bindCount, $markerLength);
930
        }
931
932
        return $sqlStatement . ';';
933
    }
934
935
    //--------------------------------------------------------------------
936
937
    /**
938
     * AbstractConnection::replaceNamedBinds
939
     *
940
     * Match bindings.
941
     *
942
     * @param string $sqlStatement
943
     * @param array  $sqlBinds
944
     *
945
     * @return string
946
     */
947
    protected function replaceNamedBinds($sqlStatement, array $sqlBinds)
948
    {
949
        foreach ($sqlBinds as $bindSearch => $bindReplace) {
950
            $escapedValue = $this->escape($bindReplace);
951
952
            // In order to correctly handle backlashes in saved strings
953
            // we will need to preg_quote, so remove the wrapping escape characters
954
            // otherwise it will get escaped.
955
            if (is_array($bindReplace)) {
956
                foreach ($bindReplace as &$bindReplaceItem) {
957
                    $bindReplaceItem = preg_quote($bindReplaceItem);
958
                }
959
960
                $escapedValue = implode(',', $escapedValue);
0 ignored issues
show
Bug introduced by
$escapedValue of type integer|string is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

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

960
                $escapedValue = implode(',', /** @scrutinizer ignore-type */ $escapedValue);
Loading history...
961
            } elseif (strpos($bindReplace, ' AND ') !== false) {
962
                $escapedValue = $bindReplace;
963
            } else {
964
                $escapedValue = preg_quote(trim($escapedValue, $this->config[ 'escapeCharacter' ]));
965
            }
966
967
            if (preg_match("/\(.+?\)/", $bindSearch)) {
968
                $bindSearch = str_replace('(', '\(', str_replace(')', '\)', $bindSearch));
969
            }
970
971
            $sqlStatement = preg_replace('/:' . $bindSearch . '(?!\w)/', $escapedValue, $sqlStatement);
972
        }
973
974
        return $sqlStatement;
975
    }
976
977
    //--------------------------------------------------------------------
978
979
    /**
980
     * AbstractConnection::escape
981
     *
982
     * Escape string
983
     *
984
     * @param $string
985
     *
986
     * @return int|string
987
     */
988
    final public function escape($string)
989
    {
990
        if (is_array($string)) {
991
            $string = array_map([&$this, 'escape'], $string);
992
993
            return $string;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $string returns the type array which is incompatible with the documented return type integer|string.
Loading history...
994
        } else {
995
            if (is_string($string) OR (is_object($string) && method_exists($string, '__toString'))) {
996
                return "'" . $this->escapeString($string) . "'";
0 ignored issues
show
Bug introduced by
It seems like $string can also be of type object; however, parameter $string of O2System\Database\Sql\Ab...nection::escapeString() does only seem to accept string|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

996
                return "'" . $this->escapeString(/** @scrutinizer ignore-type */ $string) . "'";
Loading history...
997
            } else {
998
                if (is_bool($string)) {
999
                    return ($string === false)
1000
                        ? 0
1001
                        : 1;
1002
                } else {
1003
                    if ($string === null) {
1004
                        return 'NULL';
1005
                    }
1006
                }
1007
            }
1008
        }
1009
1010
        return $string;
1011
    }
1012
1013
    //--------------------------------------------------------------------
1014
1015
    /**
1016
     * AbstractConnection::escapeString
1017
     *
1018
     * Escape String
1019
     *
1020
     * @param string|\string[] $string
1021
     * @param bool             $like
1022
     *
1023
     * @return array|string|\string[]
1024
     */
1025
    final public function escapeString($string, $like = false)
1026
    {
1027
        if (is_array($string)) {
1028
            foreach ($string as $key => $value) {
1029
                $string[ $key ] = $this->escapeString($value, $like);
1030
            }
1031
1032
            return $string;
1033
        }
1034
1035
        $string = $this->platformEscapeStringHandler($string);
1036
1037
        // escape LIKE condition wildcards
1038
        if ($like === true) {
1039
            $string = str_replace(
1040
                [$this->config[ 'likeEscapeCharacter' ], '%', '_'],
1041
                [
1042
                    $this->config[ 'likeEscapeCharacter' ] . $this->config[ 'likeEscapeCharacter' ],
1043
                    $this->config[ 'likeEscapeCharacter' ] . '%',
1044
                    $this->config[ 'likeEscapeCharacter' ] . '_',
1045
                ],
1046
                $string
1047
            );
1048
        }
1049
1050
        // fixed escaping string bugs !_
1051
        $string = str_replace('!_', '_', $string);
1052
1053
        return $string;
1054
    }
1055
1056
    // ------------------------------------------------------------------------
1057
1058
    /**
1059
     * AbstractConnection::platformEscapeStringHandler
1060
     *
1061
     * Platform escape string handler.
1062
     *
1063
     * @param string $string
1064
     *
1065
     * @return string
1066
     */
1067
    protected function platformEscapeStringHandler($string)
1068
    {
1069
        return str_replace("'", "''", remove_invisible_characters($string));
1070
    }
1071
1072
    // ------------------------------------------------------------------------
1073
1074
    /**
1075
     * AbstractConnection::replaceSimpleBinds
1076
     *
1077
     * Match bindings
1078
     *
1079
     * @param string $sqlStatement
1080
     * @param array  $sqlBinds
1081
     * @param int    $bindCount
1082
     * @param int    $markerLength
1083
     *
1084
     * @return string
1085
     */
1086
    protected function replaceSimpleBinds($sqlStatement, array $sqlBinds, $bindCount, $markerLength)
1087
    {
1088
        // Make sure not to replace a chunk inside a string that happens to match the bind marker
1089
        if ($chunk = preg_match_all("/'[^']*'/i", $sqlStatement, $matches)) {
1090
            $chunk = preg_match_all(
1091
                '/' . preg_quote($this->config[ 'bindMarker' ], '/') . '/i',
1092
                str_replace(
1093
                    $matches[ 0 ],
1094
                    str_replace($this->config[ 'bindMarker' ], str_repeat(' ', $markerLength), $matches[ 0 ]),
1095
                    $sqlStatement,
1096
                    $chunk
1097
                ),
1098
                $matches,
1099
                PREG_OFFSET_CAPTURE
1100
            );
1101
1102
            // Bind values' count must match the count of markers in the query
1103
            if ($bindCount !== $chunk) {
1104
                return $sqlStatement;
1105
            }
1106
        } // Number of binds must match bindMarkers in the string.
1107
        else {
1108
            if (($chunk = preg_match_all(
1109
                    '/' . preg_quote($this->config[ 'bindMarker' ], '/') . '/i',
1110
                    $sqlStatement,
1111
                    $matches,
1112
                    PREG_OFFSET_CAPTURE
1113
                )) !== $bindCount
1114
            ) {
1115
                return $sqlStatement;
1116
            }
1117
        }
1118
1119
        do {
1120
            $chunk--;
1121
            $escapedValue = $this->escape($sqlBinds[ $chunk ]);
1122
            if (is_array($escapedValue)) {
1123
                $escapedValue = '(' . implode(',', $escapedValue) . ')';
1124
            }
1125
            $sqlStatement = substr_replace($sqlStatement, $escapedValue, $matches[ 0 ][ $chunk ][ 1 ], $markerLength);
1126
        } while ($chunk !== 0);
1127
1128
        return $sqlStatement;
1129
    }
1130
1131
    //--------------------------------------------------------------------
1132
1133
    /**
1134
     * AbstractConnection::platformQueryHandler
1135
     *
1136
     * Driver dependent way method for execute the Sql statement.
1137
     *
1138
     * @param Statement $statement Query object.
1139
     *
1140
     * @return array
1141
     */
1142
    abstract protected function platformQueryHandler(Statement &$statement);
1143
1144
    // ------------------------------------------------------------------------
1145
1146
    /**
1147
     * AbstractConnection::cache
1148
     *
1149
     * @param  boolean $mode
1150
     *
1151
     * @return static
1152
     */
1153
    public function cache($mode = true)
1154
    {
1155
        $this->cacheEnable = (bool)$mode;
1156
1157
        return $this;
1158
    }
1159
1160
    //--------------------------------------------------------------------
1161
1162
    /**
1163
     * AbstractConnection::getTransactionStatus
1164
     *
1165
     * Get transaction status.
1166
     *
1167
     * @return bool
1168
     */
1169
    public function getTransactionStatus()
1170
    {
1171
        return (bool)$this->transactionStatus;
1172
    }
1173
1174
    //--------------------------------------------------------------------
1175
1176
    /**
1177
     * AbstractConnection::transactionBegin
1178
     *
1179
     * Starting a transaction.
1180
     *
1181
     * @return bool
1182
     */
1183
    public function transactionBegin()
1184
    {
1185
        /**
1186
         * checks if the transaction already started
1187
         * then we only increment the transaction depth.
1188
         */
1189
        if ($this->transactionDepth > 0) {
1190
            $this->transactionDepth++;
1191
1192
            return true;
1193
        }
1194
1195
        if ($this->platformTransactionBeginHandler()) {
1196
            $this->transactionInProgress = true;
1197
            $this->transactionDepth++;
1198
1199
            return true;
1200
        }
1201
1202
        return false;
1203
    }
1204
1205
    // ------------------------------------------------------------------------
1206
1207
    /**
1208
     * AbstractConnection::platformTransactionBeginHandler
1209
     *
1210
     * Platform beginning a transaction handler.
1211
     *
1212
     * @return bool
1213
     */
1214
    abstract protected function platformTransactionBeginHandler();
1215
1216
    // ------------------------------------------------------------------------
1217
1218
    /**
1219
     * AbstractConnection::transactionCommit
1220
     *
1221
     * Commit a transaction.
1222
     *
1223
     * @return bool
1224
     */
1225
    public function transactionCommit()
1226
    {
1227
        if ($this->transactionInProgress) {
1228
            if ($this->transactionStatus) {
1229
                $this->platformTransactionCommitHandler();
1230
1231
                return true;
1232
            }
1233
        }
1234
1235
        return $this->transactionRollBack();
1236
    }
1237
1238
    // ------------------------------------------------------------------------
1239
1240
    /**
1241
     * AbstractConnection::platformTransactionCommitHandler
1242
     *
1243
     * Platform committing a transaction handler.
1244
     *
1245
     * @return bool
1246
     */
1247
    abstract protected function platformTransactionCommitHandler();
1248
1249
    // ------------------------------------------------------------------------
1250
1251
    /**
1252
     * AbstractConnection::likeString
1253
     *
1254
     * Escape Like String
1255
     *
1256
     * @param $string
1257
     *
1258
     * @return array|string|\string[]
1259
     */
1260
    final public function escapeLikeString($string)
1261
    {
1262
        return $this->escapeString($string, true);
1263
    }
1264
1265
    // ------------------------------------------------------------------------
1266
1267
    /**
1268
     * AbstractConnection::protectIdentifiers
1269
     *
1270
     * This function is used extensively by the Query Builder class, and by
1271
     * a couple functions in this class.
1272
     * It takes a column or table name (optionally with an alias) and inserts
1273
     * the table prefix onto it. Some logic is necessary in order to deal with
1274
     * column names that include the path. Consider a query like this:
1275
     *
1276
     * SELECT hostname.database.table.column AS c FROM hostname.database.table
1277
     *
1278
     * Or a query with aliasing:
1279
     *
1280
     * SELECT m.member_id, m.member_name FROM members AS m
1281
     *
1282
     * Since the column name can include up to four segments (host, DB, table, column)
1283
     * or also have an alias prefix, we need to do a bit of work to figure this out and
1284
     * insert the table prefix (if it exists) in the proper position, and escape only
1285
     * the correct identifiers.
1286
     *
1287
     * @param    string|array
1288
     * @param    bool
1289
     * @param    mixed
1290
     * @param    bool
1291
     *
1292
     * @return  mixed
1293
     */
1294
    final public function protectIdentifiers(
1295
        $item,
1296
        $prefixSingle = false,
1297
        $protectIdentifiers = null,
1298
        $fieldExists = true
1299
    ) {
1300
        if ( ! is_bool($protectIdentifiers)) {
1301
            $protectIdentifiers = $this->protectIdentifiers;
1302
        }
1303
1304
        if (is_array($item)) {
1305
            $escapedArray = [];
1306
            foreach ($item as $key => $value) {
1307
                $escapedArray[ $this->protectIdentifiers($key) ] = $this->protectIdentifiers(
1308
                    $value,
1309
                    $prefixSingle,
1310
                    $protectIdentifiers,
1311
                    $fieldExists
1312
                );
1313
            }
1314
1315
            return $escapedArray;
1316
        }
1317
1318
        // This is basically a bug fix for queries that use MAX, MIN, etc.
1319
        // If a parenthesis is found we know that we do not need to
1320
        // escape the data or add a prefix.
1321
        //
1322
        // Added exception for single quotes as well, we don't want to alter
1323
        // literal strings.
1324
        if (strcspn($item, "()'") !== strlen($item)) {
1325
            return $item;
1326
        }
1327
1328
        // Convert tabs or multiple spaces into single spaces
1329
        $item = preg_replace('/\s+/', ' ', trim($item));
1330
1331
        // If the item has an alias declaration we remove it and set it aside.
1332
        // Note: strripos() is used in order to support spaces in table names
1333
        if ($offset = strripos($item, ' AS ')) {
1334
            $alias = ($protectIdentifiers)
1335
                ? substr($item, $offset, 4) . $this->escapeIdentifiers(substr($item, $offset + 4))
1336
                : substr($item, $offset);
1337
            $item = substr($item, 0, $offset);
1338
        } elseif ($offset = strrpos($item, ' ')) {
1339
            $alias = ($protectIdentifiers)
1340
                ? ' ' . $this->escapeIdentifiers(substr($item, $offset + 1))
1341
                : substr($item, $offset);
1342
            $item = substr($item, 0, $offset);
1343
        } else {
1344
            $alias = '';
1345
        }
1346
1347
        // Break the string apart if it contains periods, then insert the table prefix
1348
        // in the correct location, assuming the period doesn't indicate that we're dealing
1349
        // with an alias. While we're at it, we will escape the components
1350
        if (strpos($item, '.') !== false) {
1351
            $parts = explode('.', $item);
1352
1353
            $aliasedTables = [];
1354
1355
            if ($this->queryBuilder instanceof AbstractQueryBuilder) {
0 ignored issues
show
introduced by
$this->queryBuilder is always a sub-type of O2System\Database\Sql\Ab...ts\AbstractQueryBuilder.
Loading history...
1356
                $aliasedTables = $this->queryBuilder->getAliasedTables();
1357
            }
1358
1359
            // Does the first segment of the exploded item match
1360
            // one of the aliases previously identified? If so,
1361
            // we have nothing more to do other than escape the item
1362
            //
1363
            // NOTE: The ! empty() condition prevents this method
1364
            //       from breaking when Query Builder isn't enabled.
1365
            if ( ! empty($aliasedTables) AND in_array($parts[ 0 ], $aliasedTables)) {
1366
                if ($protectIdentifiers === true) {
1367
                    foreach ($parts as $key => $val) {
1368
                        if ( ! in_array($val, $this->config[ 'reservedIdentifiers' ])) {
1369
                            $parts[ $key ] = $this->escapeIdentifiers($val);
1370
                        }
1371
                    }
1372
1373
                    $item = implode('.', $parts);
1374
                }
1375
1376
                return $item . $alias;
1377
            }
1378
1379
            // Is there a table prefix defined in the config file? If not, no need to do anything
1380
            if ($this->config->tablePrefix !== '') {
1381
                // We now add the table prefix based on some logic.
1382
                // Do we have 4 segments (hostname.database.table.column)?
1383
                // If so, we add the table prefix to the column name in the 3rd segment.
1384
                if (isset($parts[ 3 ])) {
1385
                    $i = 2;
1386
                }
1387
                // Do we have 3 segments (database.table.column)?
1388
                // If so, we add the table prefix to the column name in 2nd position
1389
                elseif (isset($parts[ 2 ])) {
1390
                    $i = 1;
1391
                }
1392
                // Do we have 2 segments (table.column)?
1393
                // If so, we add the table prefix to the column name in 1st segment
1394
                else {
1395
                    $i = 0;
1396
                }
1397
1398
                // This flag is set when the supplied $item does not contain a field name.
1399
                // This can happen when this function is being called from a JOIN.
1400
                if ($fieldExists === false) {
1401
                    $i++;
1402
                }
1403
1404
                // Verify table prefix and replace if necessary
1405
                if ($this->swapTablePrefix !== '' && strpos($parts[ $i ], $this->swapTablePrefix) === 0) {
1406
                    $parts[ $i ] = preg_replace(
1407
                        '/^' . $this->swapTablePrefix . '(\S+?)/',
1408
                        $this->config->tablePrefix . '\\1',
1409
                        $parts[ $i ]
1410
                    );
1411
                } // We only add the table prefix if it does not already exist
1412
                elseif (strpos($parts[ $i ], $this->config->tablePrefix) !== 0) {
1413
                    $parts[ $i ] = $this->config->tablePrefix . $parts[ $i ];
1414
                }
1415
1416
                // Put the parts back together
1417
                $item = implode('.', $parts);
1418
            }
1419
1420
            if ($protectIdentifiers === true) {
1421
                $item = $this->escapeIdentifiers($item);
1422
            }
1423
1424
            return $item . $alias;
1425
        }
1426
1427
        // In some cases, especially 'from', we end up running through
1428
        // protect_identifiers twice. This algorithm won't work when
1429
        // it contains the escapeChar so strip it out.
1430
        $item = trim($item, $this->config[ 'escapeCharacter' ]);
1431
1432
        // Is there a table prefix? If not, no need to insert it
1433
        if ($this->config->tablePrefix !== '') {
1434
            // Verify table prefix and replace if necessary
1435
            if ($this->swapTablePrefix !== '' && strpos($item, $this->swapTablePrefix) === 0) {
1436
                $item = preg_replace(
1437
                    '/^' . $this->swapTablePrefix . '(\S+?)/',
1438
                    $this->config->tablePrefix . '\\1',
1439
                    $item
1440
                );
1441
            } // Do we prefix an item with no segments?
1442
            elseif ($prefixSingle === true && strpos($item, $this->config->tablePrefix) !== 0) {
1443
                $item = $this->config->tablePrefix . $item;
1444
            }
1445
        }
1446
1447
        if ($protectIdentifiers === true && ! in_array($item, $this->config[ 'reservedIdentifiers' ])) {
1448
            $item = $this->escapeIdentifiers($item);
1449
        }
1450
1451
        return $item . $alias;
1452
    }
1453
1454
    // ------------------------------------------------------------------------
1455
1456
    /**
1457
     * AbstractConnection::escapeIdentifiers
1458
     *
1459
     * Escape the Sql Identifiers
1460
     *
1461
     * This function escapes column and table names
1462
     *
1463
     * @param    mixed
1464
     *
1465
     * @return    mixed
1466
     */
1467
    final public function escapeIdentifiers($item)
1468
    {
1469
        if ($this->config[ 'escapeCharacter' ] === '' OR empty($item) OR in_array(
1470
                $item,
1471
                $this->config[ 'reservedIdentifiers' ]
1472
            )
1473
        ) {
1474
            return $item;
1475
        } elseif (is_array($item)) {
1476
            foreach ($item as $key => $value) {
1477
                $item[ $key ] = $this->escapeIdentifiers($value);
1478
            }
1479
1480
            return $item;
1481
        } // Avoid breaking functions and literal values inside queries
1482
        elseif (ctype_digit(
1483
                $item
1484
            ) OR $item[ 0 ] === "'" OR ($this->config[ 'escapeCharacter' ] !== '"' && $item[ 0 ] === '"') OR
1485
            strpos($item, '(') !== false
1486
        ) {
1487
            return $item;
1488
        }
1489
1490
        static $pregEscapeCharacters = [];
1491
1492
        if (empty($pregEscapeCharacters)) {
1493
            if (is_array($this->config[ 'escapeCharacter' ])) {
1494
                $pregEscapeCharacters = [
1495
                    preg_quote($this->config[ 'escapeCharacter' ][ 0 ], '/'),
1496
                    preg_quote($this->config[ 'escapeCharacter' ][ 1 ], '/'),
1497
                    $this->config[ 'escapeCharacter' ][ 0 ],
1498
                    $this->config[ 'escapeCharacter' ][ 1 ],
1499
                ];
1500
            } else {
1501
                $pregEscapeCharacters[ 0 ]
1502
                    = $pregEscapeCharacters[ 1 ] = preg_quote($this->config[ 'escapeCharacter' ], '/');
1503
                $pregEscapeCharacters[ 2 ] = $pregEscapeCharacters[ 3 ] = $this->config[ 'escapeCharacter' ];
1504
            }
1505
        }
1506
1507
        foreach ($this->config[ 'reservedIdentifiers' ] as $id) {
1508
            if (strpos($item, '.' . $id) !== false) {
1509
                return preg_replace(
1510
                    '/'
1511
                    . $pregEscapeCharacters[ 0 ]
1512
                    . '?([^'
1513
                    . $pregEscapeCharacters[ 1 ]
1514
                    . '\.]+)'
1515
                    . $pregEscapeCharacters[ 1 ]
1516
                    . '?\./i',
1517
                    $pregEscapeCharacters[ 2 ] . '$1' . $pregEscapeCharacters[ 3 ] . '.',
1518
                    $item
1519
                );
1520
            }
1521
        }
1522
1523
        return preg_replace(
1524
            '/'
1525
            . $pregEscapeCharacters[ 0 ]
1526
            . '?([^'
1527
            . $pregEscapeCharacters[ 1 ]
1528
            . '\.]+)'
1529
            . $pregEscapeCharacters[ 1 ]
1530
            . '?(\.)?/i',
1531
            $pregEscapeCharacters[ 2 ] . '$1' . $pregEscapeCharacters[ 3 ] . '$2',
1532
            $item
1533
        );
1534
    }
1535
1536
    // ------------------------------------------------------------------------
1537
1538
    /**
1539
     * AbstractConnection::table
1540
     *
1541
     * Get connection query builder.
1542
     *
1543
     * @return AbstractQueryBuilder
1544
     */
1545
    public function table($tableName)
1546
    {
1547
        return $this->getQueryBuilder()->from($tableName);
1548
    }
1549
1550
    // ------------------------------------------------------------------------
1551
1552
    /**
1553
     * AbstractConnection::getQueryBuilder
1554
     *
1555
     * Get connection query builder.
1556
     *
1557
     * @return AbstractQueryBuilder
1558
     */
1559
    public function getQueryBuilder()
1560
    {
1561
        if ( ! $this->queryBuilder instanceof AbstractQueryBuilder) {
0 ignored issues
show
introduced by
$this->queryBuilder is always a sub-type of O2System\Database\Sql\Ab...ts\AbstractQueryBuilder.
Loading history...
1562
            $className = str_replace('Connection', 'QueryBuilder', get_called_class());
1563
1564
            if (class_exists($className)) {
1565
                $this->queryBuilder = new $className($this);
1566
            }
1567
        }
1568
1569
        return $this->queryBuilder;
1570
    }
1571
1572
    // ------------------------------------------------------------------------
1573
1574
    /**
1575
     * AbstractConnection::getForge
1576
     *
1577
     * Get connection forge.
1578
     *
1579
     * @return AbstractForge
1580
     */
1581
    public function getForge()
1582
    {
1583
        if ( ! $this->forge instanceof AbstractForge) {
0 ignored issues
show
introduced by
$this->forge is always a sub-type of O2System\Database\Sql\Abstracts\AbstractForge.
Loading history...
1584
            $className = str_replace('Connection', 'Forge', get_called_class());
1585
1586
            if (class_exists($className)) {
1587
                $this->forge = new $className($this);
1588
            }
1589
        }
1590
1591
        return $this->forge;
1592
    }
1593
1594
    // ------------------------------------------------------------------------
1595
1596
    /**
1597
     * AbstractConnection::prepareSqlStatement
1598
     *
1599
     * Platform preparing a Sql statement.
1600
     *
1601
     * @param string $sqlStatement Sql Statement to be prepared.
1602
     * @param array  $options      Preparing Sql statement options.
1603
     *
1604
     * @return string
1605
     */
1606
    abstract protected function platformPrepareSqlStatement($sqlStatement, array $options = []);
1607
}
1608