Completed
Push — develop ( 361a2b...a96e4b )
by Marco
20s queued 14s
created

Connection::errorCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 0
cts 1
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL;
6
7
use Closure;
8
use Doctrine\Common\EventManager;
9
use Doctrine\DBAL\Cache\ArrayStatement;
10
use Doctrine\DBAL\Cache\CacheException;
11
use Doctrine\DBAL\Cache\QueryCacheProfile;
12
use Doctrine\DBAL\Cache\ResultCacheStatement;
13
use Doctrine\DBAL\Driver\Connection as DriverConnection;
14
use Doctrine\DBAL\Driver\DriverException;
15
use Doctrine\DBAL\Driver\PingableConnection;
16
use Doctrine\DBAL\Driver\ResultStatement;
17
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
18
use Doctrine\DBAL\Driver\Statement as DriverStatement;
19
use Doctrine\DBAL\Exception\InvalidArgumentException;
20
use Doctrine\DBAL\Platforms\AbstractPlatform;
21
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
22
use Doctrine\DBAL\Query\QueryBuilder;
23
use Doctrine\DBAL\Schema\AbstractSchemaManager;
24
use Doctrine\DBAL\Types\Type;
25
use Exception;
26
use Throwable;
27
use function array_key_exists;
28
use function assert;
29
use function implode;
30
use function is_int;
31
use function is_string;
32
use function key;
33
34
/**
35
 * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
36
 * events, transaction isolation levels, configuration, emulated transaction nesting,
37
 * lazy connecting and more.
38
 */
39
class Connection implements DriverConnection
40
{
41
    /**
42
     * Constant for transaction isolation level READ UNCOMMITTED.
43
     *
44
     * @deprecated Use TransactionIsolationLevel::READ_UNCOMMITTED.
45
     */
46
    public const TRANSACTION_READ_UNCOMMITTED = TransactionIsolationLevel::READ_UNCOMMITTED;
47
48
    /**
49
     * Constant for transaction isolation level READ COMMITTED.
50
     *
51
     * @deprecated Use TransactionIsolationLevel::READ_COMMITTED.
52
     */
53
    public const TRANSACTION_READ_COMMITTED = TransactionIsolationLevel::READ_COMMITTED;
54
55
    /**
56
     * Constant for transaction isolation level REPEATABLE READ.
57
     *
58
     * @deprecated Use TransactionIsolationLevel::REPEATABLE_READ.
59
     */
60
    public const TRANSACTION_REPEATABLE_READ = TransactionIsolationLevel::REPEATABLE_READ;
61
62
    /**
63
     * Constant for transaction isolation level SERIALIZABLE.
64
     *
65
     * @deprecated Use TransactionIsolationLevel::SERIALIZABLE.
66
     */
67
    public const TRANSACTION_SERIALIZABLE = TransactionIsolationLevel::SERIALIZABLE;
68
69
    /**
70
     * Represents an array of ints to be expanded by Doctrine SQL parsing.
71
     */
72
    public const PARAM_INT_ARRAY = ParameterType::INTEGER + self::ARRAY_PARAM_OFFSET;
73
74
    /**
75
     * Represents an array of strings to be expanded by Doctrine SQL parsing.
76
     */
77
    public const PARAM_STR_ARRAY = ParameterType::STRING + self::ARRAY_PARAM_OFFSET;
78
79
    /**
80
     * Offset by which PARAM_* constants are detected as arrays of the param type.
81
     */
82
    public const ARRAY_PARAM_OFFSET = 100;
83
84
    /**
85
     * The wrapped driver connection.
86
     *
87
     * @var \Doctrine\DBAL\Driver\Connection|null
88
     */
89
    protected $_conn;
90
91
    /** @var Configuration */
92
    protected $_config;
93
94
    /** @var EventManager */
95
    protected $_eventManager;
96
97
    /** @var ExpressionBuilder */
98
    protected $_expr;
99
100
    /**
101
     * Whether or not a connection has been established.
102
     *
103
     * @var bool
104
     */
105
    private $isConnected = false;
106
107
    /**
108
     * The current auto-commit mode of this connection.
109
     *
110
     * @var bool
111
     */
112
    private $autoCommit = true;
113
114
    /**
115
     * The transaction nesting level.
116
     *
117
     * @var int
118
     */
119
    private $transactionNestingLevel = 0;
120
121
    /**
122
     * The currently active transaction isolation level.
123
     *
124
     * @var int
125
     */
126
    private $transactionIsolationLevel;
127
128
    /**
129
     * If nested transactions should use savepoints.
130
     *
131
     * @var bool
132
     */
133
    private $nestTransactionsWithSavepoints = false;
134
135
    /**
136
     * The parameters used during creation of the Connection instance.
137
     *
138
     * @var mixed[]
139
     */
140
    private $params = [];
141
142
    /**
143
     * The DatabasePlatform object that provides information about the
144
     * database platform used by the connection.
145
     *
146
     * @var AbstractPlatform
147
     */
148
    private $platform;
149
150
    /**
151
     * The schema manager.
152
     *
153
     * @var AbstractSchemaManager|null
154
     */
155
    protected $_schemaManager;
156
157
    /**
158
     * The used DBAL driver.
159
     *
160
     * @var Driver
161
     */
162
    protected $_driver;
163
164
    /**
165
     * Flag that indicates whether the current transaction is marked for rollback only.
166
     *
167
     * @var bool
168
     */
169
    private $isRollbackOnly = false;
170
171
    /** @var int */
172
    protected $defaultFetchMode = FetchMode::ASSOCIATIVE;
173
174
    /**
175
     * Initializes a new instance of the Connection class.
176
     *
177
     * @param mixed[]            $params       The connection parameters.
178
     * @param Driver             $driver       The driver to use.
179
     * @param Configuration|null $config       The configuration, optional.
180
     * @param EventManager|null  $eventManager The event manager, optional.
181
     *
182 8936
     * @throws DBALException
183
     */
184
    public function __construct(
185
        array $params,
186
        Driver $driver,
187
        ?Configuration $config = null,
188 8936
        ?EventManager $eventManager = null
189 8936
    ) {
190
        $this->_driver = $driver;
191 8936
        $this->params  = $params;
192 8077
193 8077
        if (isset($params['pdo'])) {
194 8077
            $this->_conn       = $params['pdo'];
195
            $this->isConnected = true;
196
            unset($this->params['pdo']);
197 8936
        }
198 8248
199 7826
        if (isset($params['platform'])) {
200
            if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
201
                throw DBALException::invalidPlatformType($params['platform']);
202 8247
            }
203 8247
204
            $this->platform = $params['platform'];
205
            unset($this->params['platform']);
206
        }
207 8936
208 8575
        // Create default config and event manager if none given
209
        if (! $config) {
210
            $config = new Configuration();
211 8936
        }
212 8358
213
        if (! $eventManager) {
214
            $eventManager = new EventManager();
215 8936
        }
216 8936
217
        $this->_config       = $config;
218 8936
        $this->_eventManager = $eventManager;
219
220 8936
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
221 8936
222
        $this->autoCommit = $config->getAutoCommit();
223
    }
224
225
    /**
226
     * Gets the parameters used during instantiation.
227
     *
228 8168
     * @return mixed[]
229
     */
230 8168
    public function getParams()
231
    {
232
        return $this->params;
233
    }
234
235
    /**
236
     * Gets the name of the database this Connection is connected to.
237
     *
238 7661
     * @return string
239
     */
240 7661
    public function getDatabase()
241
    {
242
        return $this->_driver->getDatabase($this);
243
    }
244
245
    /**
246
     * Gets the hostname of the currently connected database.
247
     *
248 8666
     * @return string|null
249
     */
250 8666
    public function getHost()
251
    {
252
        return $this->params['host'] ?? null;
253
    }
254
255
    /**
256
     * Gets the port of the currently connected database.
257
     *
258 8642
     * @return mixed
259
     */
260 8642
    public function getPort()
261
    {
262
        return $this->params['port'] ?? null;
263
    }
264
265
    /**
266
     * Gets the username used by this connection.
267
     *
268 8643
     * @return string|null
269
     */
270 8643
    public function getUsername()
271
    {
272
        return $this->params['user'] ?? null;
273
    }
274
275
    /**
276
     * Gets the password used by this connection.
277
     *
278 8618
     * @return string|null
279
     */
280 8618
    public function getPassword()
281
    {
282
        return $this->params['password'] ?? null;
283
    }
284
285
    /**
286
     * Gets the DBAL driver instance.
287
     *
288 8658
     * @return Driver
289
     */
290 8658
    public function getDriver()
291
    {
292
        return $this->_driver;
293
    }
294
295
    /**
296
     * Gets the Configuration used by the Connection.
297
     *
298 8961
     * @return Configuration
299
     */
300 8961
    public function getConfiguration()
301
    {
302
        return $this->_config;
303
    }
304
305
    /**
306
     * Gets the EventManager used by the Connection.
307
     *
308 8591
     * @return EventManager
309
     */
310 8591
    public function getEventManager()
311
    {
312
        return $this->_eventManager;
313
    }
314
315
    /**
316
     * Gets the DatabasePlatform for the connection.
317
     *
318
     * @return AbstractPlatform
319
     *
320 8768
     * @throws DBALException
321
     */
322 8768
    public function getDatabasePlatform()
323 8572
    {
324
        if ($this->platform === null) {
325
            $this->detectDatabasePlatform();
326 8767
        }
327
328
        return $this->platform;
329
    }
330
331
    /**
332
     * Gets the ExpressionBuilder for the connection.
333
     *
334
     * @return ExpressionBuilder
335
     */
336
    public function getExpressionBuilder()
337
    {
338
        return $this->_expr;
339
    }
340
341
    /**
342
     * Establishes the connection with the database.
343
     *
344 8850
     * @throws DriverException
345
     */
346 8850
    public function connect() : void
347 8594
    {
348
        if ($this->isConnected) {
349
            return;
350 8613
        }
351 8613
352 8613
        $driverOptions = $this->params['driverOptions'] ?? [];
353
        $user          = $this->params['user'] ?? null;
354 8613
        $password      = $this->params['password'] ?? null;
355 8609
356
        $this->_conn       = $this->_driver->connect($this->params, $user, $password, $driverOptions);
357 8609
        $this->isConnected = true;
358 8332
359
        if ($this->autoCommit === false) {
360
            $this->beginTransaction();
361 8609
        }
362 8537
363
        if (! $this->_eventManager->hasListeners(Events::postConnect)) {
364
            return;
365 8593
        }
366 8593
367 8593
        $eventArgs = new Event\ConnectionEventArgs($this);
368
        $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
369
    }
370
371
    /**
372
     * Detects and sets the database platform.
373
     *
374
     * Evaluates custom platform class and version in order to set the correct platform.
375
     *
376 8572
     * @throws DBALException If an invalid platform was specified for this connection.
377
     */
378 8572
    private function detectDatabasePlatform()
379
    {
380 8571
        $version = $this->getDatabasePlatformVersion();
381 7898
382
        if ($version !== null) {
383 7898
            assert($this->_driver instanceof VersionAwarePlatformDriver);
384
385 8570
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
386
        } else {
387
            $this->platform = $this->_driver->getDatabasePlatform();
388 8571
        }
389 8571
390
        $this->platform->setEventManager($this->_eventManager);
391
    }
392
393
    /**
394
     * Returns the version of the related platform if applicable.
395
     *
396
     * Returns null if either the driver is not capable to create version
397
     * specific platform instances, no explicit server version was specified
398
     * or the underlying driver connection cannot determine the platform
399
     * version without having to query it (performance reasons).
400
     *
401
     * @return string|null
402
     *
403 8572
     * @throws Exception
404
     */
405
    private function getDatabasePlatformVersion()
406 8572
    {
407 8570
        // Driver does not support version specific platforms.
408
        if (! $this->_driver instanceof VersionAwarePlatformDriver) {
409
            return null;
410
        }
411 7899
412
        // Explicit platform version requested (supersedes auto-detection).
413
        if (isset($this->params['serverVersion'])) {
414
            return $this->params['serverVersion'];
415
        }
416 7899
417
        // If not connected, we need to connect now to determine the platform version.
418 7899
        if ($this->_conn === null) {
419 7802
            try {
420 7802
                $this->connect();
421
            } catch (Throwable $originalException) {
422
                if (empty($this->params['dbname'])) {
423
                    throw $originalException;
424
                }
425
426 7802
                // The database to connect to might not yet exist.
427 7802
                // Retry detection without database name connection parameter.
428
                $databaseName           = $this->params['dbname'];
429
                $this->params['dbname'] = null;
430 7802
431 7802
                try {
432
                    $this->connect();
433
                } catch (Throwable $fallbackException) {
434
                    // Either the platform does not support database-less connections
435 7802
                    // or something else went wrong.
436
                    // Reset connection parameters and rethrow the original exception.
437 7802
                    $this->params['dbname'] = $databaseName;
438
439
                    throw $originalException;
440
                }
441 6154
442 6154
                // Reset connection parameters.
443
                $this->params['dbname'] = $databaseName;
444
                $serverVersion          = $this->getServerVersion();
445 6154
446
                // Close "temporary" connection to allow connecting to the real database again.
447 6154
                $this->close();
448
449
                return $serverVersion;
450
            }
451 7898
        }
452
453
        return $this->getServerVersion();
454
    }
455
456
    /**
457
     * Returns the database server version if the underlying driver supports it.
458
     *
459 7898
     * @return string|null
460
     */
461 7898
    private function getServerVersion()
462
    {
463
        $connection = $this->getWrappedConnection();
464 7898
465 7898
        // Automatic platform version detection.
466
        if ($connection instanceof ServerInfoAwareConnection && ! $connection->requiresQueryForServerVersion()) {
467
            return $connection->getServerVersion();
468
        }
469
470
        // Unable to detect platform version.
471
        return null;
472
    }
473
474
    /**
475
     * Returns the current auto-commit mode for this connection.
476
     *
477
     * @see    setAutoCommit
478
     *
479 8379
     * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
480
     */
481 8379
    public function isAutoCommit()
482
    {
483
        return $this->autoCommit === true;
484
    }
485
486
    /**
487
     * Sets auto-commit mode for this connection.
488
     *
489
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
490
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
491
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
492
     *
493
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
494
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
495
     *
496
     * @see   isAutoCommit
497
     *
498
     * @throws ConnectionException
499 8358
     * @throws DriverException
500
     */
501
    public function setAutoCommit(bool $autoCommit) : void
502 8358
    {
503
        // Mode not changed, no-op.
504
        if ($autoCommit === $this->autoCommit) {
505
            return;
506 8358
        }
507
508
        $this->autoCommit = $autoCommit;
509 8358
510 8357
        // Commit all currently active transactions if any when switching auto-commit mode.
511
        if ($this->isConnected !== true || $this->transactionNestingLevel === 0) {
512
            return;
513 8258
        }
514 8258
515
        $this->commitAll();
516
    }
517
518
    /**
519
     * Sets the fetch mode.
520
     *
521 3870
     * @param int $fetchMode
522
     */
523 3870
    public function setFetchMode($fetchMode) : void
524 3870
    {
525
        $this->defaultFetchMode = $fetchMode;
526
    }
527
528
    /**
529
     * Prepares and executes an SQL query and returns the first row of the result
530
     * as an associative array.
531
     *
532
     * @param string         $statement The SQL query.
533
     * @param mixed[]        $params    The query parameters.
534
     * @param int[]|string[] $types     The query parameter types.
535
     *
536
     * @return mixed[]|false False is returned if no rows are found.
537
     *
538 8179
     * @throws DBALException
539
     */
540 8179
    public function fetchAssoc($statement, array $params = [], array $types = [])
541
    {
542
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::ASSOCIATIVE);
543
    }
544
545
    /**
546
     * Prepares and executes an SQL query and returns the first row of the result
547
     * as a numerically indexed array.
548
     *
549
     * @param string         $statement The SQL query to be executed.
550
     * @param mixed[]        $params    The prepared statement params.
551
     * @param int[]|string[] $types     The query parameter types.
552
     *
553 8117
     * @return mixed[]|false False is returned if no rows are found.
554
     */
555 8117
    public function fetchArray($statement, array $params = [], array $types = [])
556
    {
557
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::NUMERIC);
558
    }
559
560
    /**
561
     * Prepares and executes an SQL query and returns the value of a single column
562
     * of the first row of the result.
563
     *
564
     * @param string         $statement The SQL query to be executed.
565
     * @param mixed[]        $params    The prepared statement params.
566
     * @param int            $column    The 0-indexed column number to retrieve.
567
     * @param int[]|string[] $types     The query parameter types.
568
     *
569
     * @return mixed|false False is returned if no rows are found.
570
     *
571 8145
     * @throws DBALException
572
     */
573 8145
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
574
    {
575
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
576
    }
577
578
    /**
579
     * Whether an actual connection to the database is established.
580
     *
581 8817
     * @return bool
582
     */
583 8817
    public function isConnected()
584
    {
585
        return $this->isConnected;
586
    }
587
588
    /**
589
     * Checks whether a transaction is currently active.
590
     *
591 9056
     * @return bool TRUE if a transaction is currently active, FALSE otherwise.
592
     */
593 9056
    public function isTransactionActive()
594
    {
595
        return $this->transactionNestingLevel > 0;
596
    }
597
598
    /**
599
     * Adds identifier condition to the query components
600
     *
601
     * @param mixed[]  $identifier Map of key columns to their values
602
     * @param string[] $columns    Column names
603
     * @param mixed[]  $values     Column values
604
     * @param string[] $conditions Key conditions
605
     *
606 8224
     * @throws DBALException
607
     */
608
    private function addIdentifierCondition(
609
        array $identifier,
610
        array &$columns,
611
        array &$values,
612 8224
        array &$conditions
613
    ) : void {
614 8224
        $platform = $this->getDatabasePlatform();
615 8224
616 8189
        foreach ($identifier as $columnName => $value) {
617 8189
            if ($value === null) {
618
                $conditions[] = $platform->getIsNullExpression($columnName);
619
                continue;
620 8222
            }
621 8222
622 8222
            $columns[]    = $columnName;
623
            $values[]     = $value;
624 8224
            $conditions[] = $columnName . ' = ?';
625
        }
626
    }
627
628
    /**
629
     * Executes an SQL DELETE statement on a table.
630
     *
631
     * Table expression and columns are not escaped and are not safe for user-input.
632
     *
633
     * @param string         $tableExpression The expression of the table on which to delete.
634
     * @param mixed[]        $identifier      The deletion criteria. An associative array containing column-value pairs.
635
     * @param int[]|string[] $types           The types of identifiers.
636
     *
637
     * @return int The number of affected rows.
638
     *
639
     * @throws DBALException
640 8168
     * @throws InvalidArgumentException
641
     */
642 8168
    public function delete($tableExpression, array $identifier, array $types = [])
643 8042
    {
644
        if (empty($identifier)) {
645
            throw InvalidArgumentException::fromEmptyCriteria();
646 8167
        }
647
648 8167
        $columns = $values = $conditions = [];
649
650 8167
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
651 8167
652 6
        return $this->executeUpdate(
653 8167
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
654
            $values,
655
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
656
        );
657
    }
658
659
    /**
660
     * Closes the connection.
661
     *
662 7728
     * @return void
663
     */
664 7728
    public function close()
665
    {
666 7728
        $this->_conn = null;
667 7728
668
        $this->isConnected = false;
669
    }
670
671
    /**
672
     * Sets the transaction isolation level.
673
     *
674
     * @param int $level The level to set.
675
     *
676
     * @return int
677
     */
678
    public function setTransactionIsolation($level)
679
    {
680
        $this->transactionIsolationLevel = $level;
681
682
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
683
    }
684
685
    /**
686
     * Gets the currently active transaction isolation level.
687
     *
688
     * @return int The current transaction isolation level.
689
     */
690
    public function getTransactionIsolation()
691
    {
692
        if ($this->transactionIsolationLevel === null) {
693
            $this->transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
694
        }
695
696
        return $this->transactionIsolationLevel;
697
    }
698
699
    /**
700
     * Executes an SQL UPDATE statement on a table.
701
     *
702
     * Table expression and columns are not escaped and are not safe for user-input.
703
     *
704
     * @param string         $tableExpression The expression of the table to update quoted or unquoted.
705
     * @param mixed[]        $data            An associative array containing column-value pairs.
706
     * @param mixed[]        $identifier      The update criteria. An associative array containing column-value pairs.
707
     * @param int[]|string[] $types           Types of the merged $data and $identifier arrays in that order.
708
     *
709
     * @return int The number of affected rows.
710
     *
711 8218
     * @throws DBALException
712
     */
713 8218
    public function update($tableExpression, array $data, array $identifier, array $types = [])
714
    {
715 8218
        $columns = $values = $conditions = $set = [];
716 8218
717 8218
        foreach ($data as $columnName => $value) {
718 8218
            $columns[] = $columnName;
719
            $values[]  = $value;
720
            $set[]     = $columnName . ' = ?';
721 8218
        }
722
723 8218
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
724 8214
725
        if (is_string(key($types))) {
726
            $types = $this->extractTypeValues($columns, $types);
727 8218
        }
728 8218
729
        $sql = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
730 8218
                . ' WHERE ' . implode(' AND ', $conditions);
731
732
        return $this->executeUpdate($sql, $values, $types);
733
    }
734
735
    /**
736
     * Inserts a table row with specified data.
737
     *
738
     * Table expression and columns are not escaped and are not safe for user-input.
739
     *
740
     * @param string         $tableExpression The expression of the table to insert data into, quoted or unquoted.
741
     * @param mixed[]        $data            An associative array containing column-value pairs.
742
     * @param int[]|string[] $types           Types of the inserted data.
743
     *
744
     * @return int The number of affected rows.
745
     *
746 8312
     * @throws DBALException
747
     */
748 8312
    public function insert($tableExpression, array $data, array $types = [])
749 8234
    {
750
        if (empty($data)) {
751
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' () VALUES ()');
752 8071
        }
753 8071
754 8071
        $columns = [];
755
        $values  = [];
756 8071
        $set     = [];
757 8071
758 8071
        foreach ($data as $columnName => $value) {
759 8071
            $columns[] = $columnName;
760
            $values[]  = $value;
761
            $set[]     = '?';
762 8071
        }
763 8071
764 8071
        return $this->executeUpdate(
765 78
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
766 8071
            ' VALUES (' . implode(', ', $set) . ')',
767
            $values,
768
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
769
        );
770
    }
771
772
    /**
773
     * Extract ordered type list from an ordered column list and type map.
774
     *
775
     * @param int[]|string[] $columnList
776
     * @param int[]|string[] $types
777
     *
778 8218
     * @return int[]|string[]
779
     */
780 8218
    private function extractTypeValues(array $columnList, array $types)
781
    {
782 8218
        $typeValues = [];
783 8218
784
        foreach ($columnList as $columnIndex => $columnName) {
785
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
786 8218
        }
787
788
        return $typeValues;
789
    }
790
791
    /**
792
     * Quotes a string so it can be safely used as a table or column name, even if
793
     * it is a reserved name.
794
     *
795
     * Delimiting style depends on the underlying database platform that is being used.
796
     *
797
     * NOTE: Just because you CAN use quoted identifiers does not mean
798
     * you SHOULD use them. In general, they end up causing way more
799
     * problems than they solve.
800
     *
801
     * @param string $str The name to be quoted.
802
     *
803 6472
     * @return string The quoted name.
804
     */
805 6472
    public function quoteIdentifier($str)
806
    {
807
        return $this->getDatabasePlatform()->quoteIdentifier($str);
808
    }
809
810
    /**
811 6698
     * {@inheritDoc}
812
     */
813 6698
    public function quote(string $input) : string
814
    {
815
        return $this->getWrappedConnection()->quote($input);
816
    }
817
818
    /**
819
     * Prepares and executes an SQL query and returns the result as an associative array.
820
     *
821
     * @param string         $sql    The SQL query.
822
     * @param mixed[]        $params The query parameters.
823
     * @param int[]|string[] $types  The query parameter types.
824
     *
825 8176
     * @return mixed[]
826
     */
827 8176
    public function fetchAll($sql, array $params = [], $types = [])
828
    {
829
        return $this->executeQuery($sql, $params, $types)->fetchAll();
830
    }
831
832
    /**
833
     * Prepares an SQL statement.
834
     *
835
     * @param string $sql The SQL statement to prepare.
836
     *
837 8465
     * @throws DBALException
838
     */
839
    public function prepare(string $sql) : DriverStatement
840 8465
    {
841 8426
        try {
842 8426
            $stmt = new Statement($sql, $this);
843
        } catch (Throwable $ex) {
844
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $sql);
845 7984
        }
846
847 7984
        $stmt->setFetchMode($this->defaultFetchMode);
848
849
        return $stmt;
850
    }
851
852
    /**
853
     * Executes an, optionally parametrized, SQL query.
854
     *
855
     * If the query is parametrized, a prepared statement is used.
856
     * If an SQLLogger is configured, the execution is logged.
857
     *
858
     * @param string                 $query  The SQL query to execute.
859
     * @param mixed[]                $params The parameters to bind to the query, if any.
860
     * @param int[]|string[]         $types  The types the previous parameters are in.
861
     * @param QueryCacheProfile|null $qcp    The query cache profile, optional.
862
     *
863
     * @return ResultStatement The executed statement.
864
     *
865 8668
     * @throws DBALException
866
     */
867 8668
    public function executeQuery(string $query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null) : ResultStatement
868 3792
    {
869
        if ($qcp !== null) {
870
            return $this->executeCacheQuery($query, $params, $types, $qcp);
871 8668
        }
872
873 8668
        $connection = $this->getWrappedConnection();
874 8668
875
        $logger = $this->_config->getSQLLogger();
876
        $logger->startQuery($query, $params, $types);
877 8668
878 6518
        try {
879
            if ($params) {
880 6518
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
881 6518
882 6415
                $stmt = $connection->prepare($query);
883 6415
                if ($types) {
884
                    $this->_bindTypedValues($stmt, $params, $types);
885 6518
                    $stmt->execute();
886
                } else {
887
                    $stmt->execute($params);
888 8664
                }
889
            } else {
890 8481
                $stmt = $connection->query($query);
891 8481
            }
892
        } catch (Throwable $ex) {
893
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
894 6993
        }
895
896 6993
        $stmt->setFetchMode($this->defaultFetchMode);
897
898 6993
        $logger->stopQuery();
899
900
        return $stmt;
901
    }
902
903
    /**
904
     * Executes a caching query.
905
     *
906
     * @param string            $query  The SQL query to execute.
907
     * @param mixed[]           $params The parameters to bind to the query, if any.
908
     * @param int[]|string[]    $types  The types the previous parameters are in.
909
     * @param QueryCacheProfile $qcp    The query cache profile.
910
     *
911 7886
     * @throws CacheException
912
     */
913 7886
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp) : ResultStatement
914
    {
915 7886
        $resultCache = $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
916
917
        if ($resultCache === null) {
918
            throw CacheException::noResultDriverConfigured();
919 7886
        }
920
921
        [$cacheKey, $realKey] = $qcp->generateCacheKeys($query, $params, $types, $this->getParams());
922 7886
923
        // fetch the row pointers entry
924 7886
        $data = $resultCache->fetch($cacheKey);
925
926 7882
        if ($data !== false) {
927 7882
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
928
            if (isset($data[$realKey])) {
929
                $stmt = new ArrayStatement($data[$realKey]);
930
            } elseif (array_key_exists($realKey, $data)) {
931
                $stmt = new ArrayStatement([]);
932
            }
933 7886
        }
934 3792
935
        if (! isset($stmt)) {
936
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
937 7886
        }
938
939 7886
        $stmt->setFetchMode($this->defaultFetchMode);
940
941
        return $stmt;
942
    }
943
944
    /**
945
     * Executes an, optionally parametrized, SQL query and returns the result,
946
     * applying a given projection/transformation function on each row of the result.
947
     *
948
     * @param string  $query    The SQL query to execute.
949
     * @param mixed[] $params   The parameters, if any.
950
     * @param Closure $function The transformation function that is applied on each row.
951
     *                           The function receives a single parameter, an array, that
952
     *                           represents a row of the result set.
953
     *
954
     * @return mixed[] The projected result of the query.
955
     */
956
    public function project($query, array $params, Closure $function)
957
    {
958
        $result = [];
959
        $stmt   = $this->executeQuery($query, $params);
960
961
        while ($row = $stmt->fetch()) {
962
            $result[] = $function($row);
963
        }
964
965
        $stmt->closeCursor();
966
967
        return $result;
968
    }
969
970
    /**
971 8513
     * {@inheritDoc}
972
     */
973 8513
    public function query(string $sql) : ResultStatement
974
    {
975 8513
        $connection = $this->getWrappedConnection();
976 8513
977
        $logger = $this->_config->getSQLLogger();
978
        $logger->startQuery($sql);
979 8513
980 8498
        try {
981 8498
            $statement = $connection->query($sql);
982
        } catch (Throwable $ex) {
983
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $sql);
984 7068
        }
985
986 7068
        $statement->setFetchMode($this->defaultFetchMode);
987
988 7068
        $logger->stopQuery();
989
990
        return $statement;
991
    }
992
993
    /**
994
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
995
     * and returns the number of affected rows.
996
     *
997
     * This method supports PDO binding types as well as DBAL mapping types.
998
     *
999
     * @param string         $query  The SQL query.
1000
     * @param mixed[]        $params The query parameters.
1001
     * @param int[]|string[] $types  The parameter types.
1002
     *
1003 8629
     * @throws DBALException
1004
     */
1005 8629
    public function executeUpdate(string $query, array $params = [], array $types = []) : int
1006
    {
1007 8629
        $connection = $this->getWrappedConnection();
1008 8629
1009
        $logger = $this->_config->getSQLLogger();
1010
        $logger->startQuery($query, $params, $types);
1011 8629
1012 8102
        try {
1013
            if ($params) {
1014 8102
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
1015
1016 8102
                $stmt = $connection->prepare($query);
1017 7090
1018 7090
                if ($types) {
1019
                    $this->_bindTypedValues($stmt, $params, $types);
1020 8089
                    $stmt->execute();
1021
                } else {
1022 8100
                    $stmt->execute($params);
1023
                }
1024 8627
                $result = $stmt->rowCount();
1025
            } else {
1026 8590
                $result = $connection->exec($query);
1027 8590
            }
1028
        } catch (Throwable $ex) {
1029
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
1030 8188
        }
1031
1032 8188
        $logger->stopQuery();
1033
1034
        return $result;
1035
    }
1036
1037
    /**
1038 8599
     * {@inheritDoc}
1039
     */
1040 8599
    public function exec(string $statement) : int
1041
    {
1042 8596
        $connection = $this->getWrappedConnection();
1043 8596
1044
        $logger = $this->_config->getSQLLogger();
1045
        $logger->startQuery($statement);
1046 8596
1047 8576
        try {
1048 8576
            $result = $connection->exec($statement);
1049
        } catch (Throwable $ex) {
1050
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
1051 5302
        }
1052
1053 5302
        $logger->stopQuery();
1054
1055
        return $result;
1056
    }
1057
1058
    /**
1059
     * Returns the current transaction nesting level.
1060
     *
1061 8272
     * @return int The nesting level. A value of 0 means there's no active transaction.
1062
     */
1063 8272
    public function getTransactionNestingLevel()
1064
    {
1065
        return $this->transactionNestingLevel;
1066
    }
1067
1068
    /**
1069
     * Returns the ID of the last inserted row, or the last value from a sequence object,
1070
     * depending on the underlying driver.
1071
     *
1072
     * Note: This method may not return a meaningful or consistent result across different drivers,
1073
     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
1074
     * columns or sequences.
1075
     *
1076
     * @param string|null $seqName Name of the sequence object from which the ID should be returned.
1077
     *
1078
     * @return string A string representation of the last inserted ID.
1079
     */
1080
    public function lastInsertId($seqName = null)
1081
    {
1082
        return $this->getWrappedConnection()->lastInsertId($seqName);
1083
    }
1084
1085
    /**
1086
     * Executes a function in a transaction.
1087
     *
1088
     * The function gets passed this Connection instance as an (optional) parameter.
1089
     *
1090
     * If an exception occurs during execution of the function or transaction commit,
1091
     * the transaction is rolled back and the exception re-thrown.
1092
     *
1093
     * @param Closure $func The function to execute transactionally.
1094
     *
1095
     * @return mixed The value returned by $func
1096 791
     *
1097
     * @throws Throwable
1098 791
     */
1099
    public function transactional(Closure $func)
1100
    {
1101
        $this->beginTransaction();
1102
        try {
1103
            $res = $func($this);
1104
            $this->commit();
1105
1106
            return $res;
1107
        } catch (Throwable $e) {
1108
            $this->rollBack();
1109
            throw $e;
1110
        }
1111
    }
1112
1113
    /**
1114
     * Sets if nested transactions should use savepoints.
1115 6769
     *
1116
     * @param bool $nestTransactionsWithSavepoints
1117 6769
     *
1118
     * @return void
1119 6769
     *
1120 6743
     * @throws ConnectionException
1121 6743
     */
1122 6767
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
1123 6767
    {
1124 6767
        if ($this->transactionNestingLevel > 0) {
1125
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
1126
        }
1127
1128
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1129
            throw ConnectionException::savepointsNotSupported();
1130
        }
1131
1132
        $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1133
    }
1134
1135
    /**
1136
     * Gets if nested transactions should use savepoints.
1137 6864
     *
1138
     * @return bool
1139 6864
     */
1140 6632
    public function getNestTransactionsWithSavepoints()
1141
    {
1142
        return $this->nestTransactionsWithSavepoints;
1143 6863
    }
1144 232
1145
    /**
1146
     * Returns the savepoint name to use for nested transactions are false if they are not supported
1147 6631
     * "savepointFormat" parameter is not set
1148 6631
     *
1149
     * @return mixed A string with the savepoint name or false.
1150
     */
1151
    protected function _getNestedTransactionSavePointName()
1152
    {
1153
        return 'DOCTRINE2_SAVEPOINT_' . $this->transactionNestingLevel;
1154
    }
1155 6631
1156
    /**
1157 6631
     * {@inheritDoc}
1158
     */
1159
    public function beginTransaction() : void
1160
    {
1161
        $connection = $this->getWrappedConnection();
1162
1163
        ++$this->transactionNestingLevel;
1164
1165
        $logger = $this->_config->getSQLLogger();
1166 6631
1167
        if ($this->transactionNestingLevel === 1) {
1168 6631
            $logger->startQuery('"START TRANSACTION"');
1169
1170
            try {
1171
                $connection->beginTransaction();
1172
            } finally {
1173
                $logger->stopQuery();
1174 8349
            }
1175
        } elseif ($this->nestTransactionsWithSavepoints) {
1176 8349
            $logger->startQuery('"SAVEPOINT"');
1177
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1178 8349
            $logger->stopQuery();
1179
        }
1180 8349
    }
1181
1182 8349
    /**
1183 8349
     * {@inheritDoc}
1184
     *
1185
     * @throws ConnectionException If the commit failed due to no active transaction or
1186 8349
     *                                            because the transaction was marked for rollback only.
1187 8349
     */
1188 8349
    public function commit() : void
1189
    {
1190 8260
        if ($this->transactionNestingLevel === 0) {
1191 6631
            throw ConnectionException::noActiveTransaction();
1192 6631
        }
1193 6631
1194
        if ($this->isRollbackOnly) {
1195 8349
            throw ConnectionException::commitFailedRollbackOnly();
1196
        }
1197
1198
        $connection = $this->getWrappedConnection();
1199
1200
        $logger = $this->_config->getSQLLogger();
1201
1202
        if ($this->transactionNestingLevel === 1) {
1203 8773
            $logger->startQuery('"COMMIT"');
1204
1205 8773
            try {
1206 8762
                $connection->commit();
1207
            } finally {
1208
                $logger->stopQuery();
1209 8316
            }
1210 6912
        } elseif ($this->nestTransactionsWithSavepoints) {
1211
            $logger->startQuery('"RELEASE SAVEPOINT"');
1212
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1213 8314
            $logger->stopQuery();
1214
        }
1215 8314
1216
        --$this->transactionNestingLevel;
1217 8314
1218 8314
        if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
1219
            return;
1220
        }
1221 8314
1222 8314
        $this->beginTransaction();
1223 8314
    }
1224
1225 8259
    /**
1226 6631
     * Commits all current nesting transactions.
1227 6631
     *
1228 6631
     * @throws ConnectionException
1229
     * @throws DriverException
1230
     */
1231 8314
    private function commitAll() : void
1232
    {
1233 8314
        while ($this->transactionNestingLevel !== 0) {
1234 8265
            if ($this->autoCommit === false && $this->transactionNestingLevel === 1) {
1235
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1236
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1237 8307
                $this->commit();
1238 8307
1239
                return;
1240
            }
1241
1242
            $this->commit();
1243
        }
1244
    }
1245
1246 8258
    /**
1247
     * {@inheritDoc}
1248 8258
     *
1249 8258
     * @throws ConnectionException If the rollback operation failed.
1250
     */
1251
    public function rollBack() : void
1252 8258
    {
1253
        if ($this->transactionNestingLevel === 0) {
1254 8258
            throw ConnectionException::noActiveTransaction();
1255
        }
1256
1257 8258
        $connection = $this->getWrappedConnection();
1258
1259 8258
        $logger = $this->_config->getSQLLogger();
1260
1261
        if ($this->transactionNestingLevel === 1) {
1262
            $logger->startQuery('"ROLLBACK"');
1263
            $this->transactionNestingLevel = 0;
1264
1265
            try {
1266 8749
                $connection->rollBack();
1267
            } finally {
1268 8749
                $this->isRollbackOnly = false;
1269 8738
                $logger->stopQuery();
1270
1271
                if ($this->autoCommit === false) {
1272 8292
                    $this->beginTransaction();
1273
                }
1274 8292
            }
1275
        } elseif ($this->nestTransactionsWithSavepoints) {
1276 8292
            $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1277 8291
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1278 8291
            --$this->transactionNestingLevel;
1279
            $logger->stopQuery();
1280
        } else {
1281 8291
            $this->isRollbackOnly = true;
1282 8291
            --$this->transactionNestingLevel;
1283 8291
        }
1284 8291
    }
1285
1286 8291
    /**
1287 8291
     * Creates a new savepoint.
1288
     *
1289
     * @param string $savepoint The name of the savepoint to create.
1290 6888
     *
1291 6631
     * @return void
1292 6631
     *
1293 6631
     * @throws ConnectionException
1294 6631
     */
1295
    public function createSavepoint($savepoint)
1296 6887
    {
1297 6887
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1298
            throw ConnectionException::savepointsNotSupported();
1299 8292
        }
1300
1301
        $this->getWrappedConnection()->exec($this->platform->createSavePoint($savepoint));
1302
    }
1303
1304
    /**
1305
     * Releases the given savepoint.
1306
     *
1307
     * @param string $savepoint The name of the savepoint to release.
1308
     *
1309
     * @return void
1310 6862
     *
1311
     * @throws ConnectionException
1312 6862
     */
1313 231
    public function releaseSavepoint($savepoint)
1314
    {
1315
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1316 6631
            throw ConnectionException::savepointsNotSupported();
1317 6631
        }
1318
1319
        if (! $this->platform->supportsReleaseSavepoints()) {
1320
            return;
1321
        }
1322
1323
        $this->getWrappedConnection()->exec($this->platform->releaseSavePoint($savepoint));
1324
    }
1325
1326
    /**
1327
     * Rolls back to the given savepoint.
1328 6861
     *
1329
     * @param string $savepoint The name of the savepoint to rollback to.
1330 6861
     *
1331 230
     * @return void
1332
     *
1333
     * @throws ConnectionException
1334 6631
     */
1335 503
    public function rollbackSavepoint($savepoint)
1336
    {
1337
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1338 6128
            throw ConnectionException::savepointsNotSupported();
1339 6128
        }
1340
1341
        $this->getWrappedConnection()->exec($this->platform->rollbackSavePoint($savepoint));
1342
    }
1343
1344
    /**
1345
     * Gets the wrapped driver connection.
1346
     *
1347
     * @return DriverConnection
1348
     */
1349
    public function getWrappedConnection()
1350 6861
    {
1351
        $this->connect();
1352 6861
        assert($this->_conn instanceof DriverConnection);
1353 230
1354
        return $this->_conn;
1355
    }
1356 6631
1357 6631
    /**
1358
     * Gets the SchemaManager that can be used to inspect or change the
1359
     * database schema through the connection.
1360
     *
1361
     * @return AbstractSchemaManager
1362
     */
1363
    public function getSchemaManager()
1364 8807
    {
1365
        if ($this->_schemaManager === null) {
1366 8807
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1367 8804
        }
1368
1369 8804
        return $this->_schemaManager;
1370
    }
1371
1372
    /**
1373
     * Marks the current transaction so that the only possible
1374
     * outcome for the transaction to be rolled back.
1375
     *
1376
     * @return void
1377
     *
1378 7904
     * @throws ConnectionException If no transaction is active.
1379
     */
1380 7904
    public function setRollbackOnly()
1381 7762
    {
1382
        if ($this->transactionNestingLevel === 0) {
1383
            throw ConnectionException::noActiveTransaction();
1384 7904
        }
1385
        $this->isRollbackOnly = true;
1386
    }
1387
1388
    /**
1389
     * Checks whether the current transaction is marked for rollback only.
1390
     *
1391
     * @return bool
1392
     *
1393
     * @throws ConnectionException If no transaction is active.
1394
     */
1395 8715
    public function isRollbackOnly()
1396
    {
1397 8715
        if ($this->transactionNestingLevel === 0) {
1398 8714
            throw ConnectionException::noActiveTransaction();
1399
        }
1400 6911
1401 6911
        return $this->isRollbackOnly;
1402
    }
1403
1404
    /**
1405
     * Converts a given value to its database representation according to the conversion
1406
     * rules of a specific DBAL mapping type.
1407
     *
1408
     * @param mixed  $value The value to convert.
1409
     * @param string $type  The name of the DBAL mapping type.
1410 8716
     *
1411
     * @return mixed The converted value.
1412 8716
     */
1413 8714
    public function convertToDatabaseValue($value, $type)
1414
    {
1415
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1416 6888
    }
1417
1418
    /**
1419
     * Converts a given value to its PHP representation according to the conversion
1420
     * rules of a specific DBAL mapping type.
1421
     *
1422
     * @param mixed  $value The value to convert.
1423
     * @param string $type  The name of the DBAL mapping type.
1424
     *
1425
     * @return mixed The converted type.
1426
     */
1427
    public function convertToPHPValue($value, $type)
1428
    {
1429
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1430
    }
1431
1432
    /**
1433
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1434
     * or DBAL mapping type, to a given statement.
1435
     *
1436
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
1437
     *           raw PDOStatement instances.
1438
     *
1439
     * @param DriverStatement $stmt   The statement to bind the values to.
1440
     * @param mixed[]         $params The map/list of named/positional parameters.
1441
     * @param int[]|string[]  $types  The parameter types.
1442
     */
1443
    private function _bindTypedValues(DriverStatement $stmt, array $params, array $types) : void
1444
    {
1445
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1446
        if (is_int(key($params))) {
1447
            // Positional parameters
1448
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1449
            $bindIndex  = 1;
1450
            foreach ($params as $value) {
1451
                $typeIndex = $bindIndex + $typeOffset;
1452
                if (isset($types[$typeIndex])) {
1453
                    $type                  = $types[$typeIndex];
1454
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1455
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1456
                } else {
1457
                    $stmt->bindValue($bindIndex, $value);
1458 7104
                }
1459
                ++$bindIndex;
1460
            }
1461 7104
        } else {
1462
            // Named parameters
1463 7104
            foreach ($params as $name => $value) {
1464 7104
                if (isset($types[$name])) {
1465 7104
                    $type                  = $types[$name];
1466 7104
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1467 7104
                    $stmt->bindValue($name, $value, $bindingType);
1468 7104
                } else {
1469 7104
                    $stmt->bindValue($name, $value);
1470 7104
                }
1471
            }
1472 6983
        }
1473
    }
1474 7104
1475
    /**
1476
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1477
     *
1478
     * @param mixed           $value The value to bind.
1479
     * @param int|string|null $type  The type to bind (PDO or DBAL).
1480
     *
1481
     * @return mixed[] [0] => the (escaped) value, [1] => the binding type.
1482
     */
1483
    private function getBindingInfo($value, $type)
1484
    {
1485
        if (is_string($type)) {
1486
            $type = Type::getType($type);
1487
        }
1488 7104
        if ($type instanceof Type) {
1489
            $value       = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1490
            $bindingType = $type->getBindingType();
1491
        } else {
1492
            $bindingType = $type;
1493
        }
1494
1495
        return [$value, $bindingType];
1496
    }
1497
1498 7104
    /**
1499
     * Resolves the parameters to a format which can be displayed.
1500 7104
     *
1501 6410
     * @internal This is a purely internal method. If you rely on this method, you are advised to
1502
     *           copy/paste the code as this method may change, or be removed without prior notice.
1503 7104
     *
1504 6410
     * @param mixed[]        $params
1505 6410
     * @param int[]|string[] $types
1506
     *
1507 7098
     * @return mixed[]
1508
     */
1509
    public function resolveParams(array $params, array $types)
1510 7104
    {
1511
        $resolvedParams = [];
1512
1513
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1514
        if (is_int(key($params))) {
1515
            // Positional parameters
1516
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1517
            $bindIndex  = 1;
1518
            foreach ($params as $value) {
1519
                $typeIndex = $bindIndex + $typeOffset;
1520
                if (isset($types[$typeIndex])) {
1521
                    $type                       = $types[$typeIndex];
1522
                    [$value]                    = $this->getBindingInfo($value, $type);
1523
                    $resolvedParams[$bindIndex] = $value;
1524 8622
                } else {
1525
                    $resolvedParams[$bindIndex] = $value;
1526 8622
                }
1527
                ++$bindIndex;
1528
            }
1529 8622
        } else {
1530
            // Named parameters
1531 5761
            foreach ($params as $name => $value) {
1532 5761
                if (isset($types[$name])) {
1533 5761
                    $type                  = $types[$name];
1534 5761
                    [$value]               = $this->getBindingInfo($value, $type);
1535 5761
                    $resolvedParams[$name] = $value;
1536
                } else {
1537
                    $resolvedParams[$name] = $value;
1538
                }
1539
            }
1540 5761
        }
1541
1542 5761
        return $resolvedParams;
1543
    }
1544
1545
    /**
1546 8614
     * Creates a new instance of a SQL query builder.
1547
     *
1548
     * @return QueryBuilder
1549
     */
1550
    public function createQueryBuilder()
1551
    {
1552
        return new Query\QueryBuilder($this);
1553
    }
1554
1555
    /**
1556
     * Ping the server
1557 8622
     *
1558
     * When the server is not available the method returns FALSE.
1559
     * It is responsibility of the developer to handle this case
1560
     * and abort the request or reconnect manually:
1561
     *
1562
     * @return bool
1563
     *
1564
     * @example
1565
     *
1566
     *   if ($conn->ping() === false) {
1567
     *      $conn->close();
1568
     *      $conn->connect();
1569
     *   }
1570
     *
1571
     * It is undefined if the underlying driver attempts to reconnect
1572
     * or disconnect when the connection is not available anymore
1573
     * as long it returns TRUE when a reconnect succeeded and
1574
     * FALSE when the connection was dropped.
1575
     */
1576
    public function ping()
1577
    {
1578
        $connection = $this->getWrappedConnection();
1579
1580
        if ($connection instanceof PingableConnection) {
1581
            return $connection->ping();
1582
        }
1583
1584
        try {
1585
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1586
1587
            return true;
1588
        } catch (DBALException $e) {
1589
            return false;
1590
        }
1591 6670
    }
1592
}
1593