Failed Conditions
Pull Request — master (#3973)
by Grégoire
03:04
created

Connection::createSavepoint()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

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