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.

AbstractConnection::execute()   B
last analyzed

Complexity

Conditions 7
Paths 24

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 25
c 2
b 0
f 0
dl 0
loc 43
rs 8.5866
cc 7
nc 24
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
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: \r\n" . $sqlStatement . "\r\n";
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;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
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
        $result = new Result([]);
816
817
        // Run the query for real
818
        if ($this->disableQueryExecution === false) {
819
            if ($queryStatement->isWriteStatement()) {
820
                $result = $this->platformExecuteHandler($queryStatement);
821
822
                if ($this->transactionInProgress) {
823
                    $this->transactionStatus = $result;
824
                }
825
            } else {
826
                if (class_exists('O2System\Framework', false) &&
827
                    $this->cacheEnable === true
828
                ) {
829
                    $cacheKey = $queryStatement->getKey();
830
                    $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

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

961
                $escapedValue = implode(',', /** @scrutinizer ignore-type */ $escapedValue);
Loading history...
962
            } elseif (strpos($bindReplace, ' AND ') !== false) {
963
                $escapedValue = $bindReplace;
964
            } else {
965
                $escapedValue = preg_quote(trim($escapedValue, $this->config[ 'escapeCharacter' ]));
966
            }
967
968
            if (preg_match("/\(.+?\)/", $bindSearch)) {
969
                $bindSearch = str_replace('(', '\(', str_replace(')', '\)', $bindSearch));
970
            }
971
972
            $sqlStatement = preg_replace('/:' . $bindSearch . '(?!\w)/', $escapedValue, $sqlStatement);
973
        }
974
975
        return $sqlStatement;
976
    }
977
978
    //--------------------------------------------------------------------
979
980
    /**
981
     * AbstractConnection::escape
982
     *
983
     * Escape string
984
     *
985
     * @param $string
986
     *
987
     * @return int|string
988
     */
989
    final public function escape($string)
990
    {
991
        if (is_array($string)) {
992
            $string = array_map([&$this, 'escape'], $string);
993
994
            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...
995
        } else {
996
            if (is_string($string) OR (is_object($string) && method_exists($string, '__toString'))) {
997
                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

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