Connection::getNestTransactionsWithSavepoints()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
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 2376
    public function __construct(
165
        array $params,
166
        Driver $driver,
167
        ?Configuration $config = null,
168
        ?EventManager $eventManager = null
169
    ) {
170 2376
        $this->_driver = $driver;
171 2376
        $this->params  = $params;
172
173 2376
        if (isset($params['platform'])) {
174 264
            if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
175 22
                throw InvalidPlatformType::new($params['platform']);
176
            }
177
178 242
            $this->platform = $params['platform'];
179
        }
180
181
        // Create default config and event manager if none given
182 2376
        if ($config === null) {
183 550
            $config = new Configuration();
184
        }
185
186 2376
        if ($eventManager === null) {
187 528
            $eventManager = new EventManager();
188
        }
189
190 2376
        $this->_config       = $config;
191 2376
        $this->_eventManager = $eventManager;
192
193 2376
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
194
195 2376
        $this->autoCommit = $config->getAutoCommit();
196 2376
    }
197
198
    /**
199
     * Gets the parameters used during instantiation.
200
     *
201
     * @return array<string, mixed>
202
     */
203 1648
    public function getParams() : array
204
    {
205 1648
        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 1924
    public function getDatabase() : ?string
218
    {
219 1924
        $platform = $this->getDatabasePlatform();
220 1924
        $query    = $platform->getDummySelectSQL($platform->getCurrentDatabaseExpression());
221 1924
        $database = $this->query($query)->fetchColumn();
222
223 1924
        assert(is_string($database) || $database === null);
224
225 1924
        return $database;
226
    }
227
228
    /**
229
     * Gets the DBAL driver instance.
230
     */
231 1213
    public function getDriver() : Driver
232
    {
233 1213
        return $this->_driver;
234
    }
235
236
    /**
237
     * Gets the Configuration used by the Connection.
238
     */
239 8281
    public function getConfiguration() : Configuration
240
    {
241 8281
        return $this->_config;
242
    }
243
244
    /**
245
     * Gets the EventManager used by the Connection.
246
     */
247 255
    public function getEventManager() : EventManager
248
    {
249 255
        return $this->_eventManager;
250
    }
251
252
    /**
253
     * Gets the DatabasePlatform for the connection.
254
     *
255
     * @throws DBALException
256
     */
257 7035
    public function getDatabasePlatform() : AbstractPlatform
258
    {
259 7035
        if ($this->platform === null) {
260 659
            $this->detectDatabasePlatform();
261
        }
262
263 7013
        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 8535
    public function connect() : void
280
    {
281 8535
        if ($this->isConnected) {
282 8139
            return;
283
        }
284
285 1076
        $driverOptions = $this->params['driverOptions'] ?? [];
286 1076
        $user          = $this->params['user'] ?? '';
287 1076
        $password      = $this->params['password'] ?? '';
288
289 1076
        $this->_conn       = $this->_driver->connect($this->params, $user, $password, $driverOptions);
290 1007
        $this->isConnected = true;
291
292 1007
        $this->transactionNestingLevel = 0;
293
294 1007
        if ($this->autoCommit === false) {
295 66
            $this->beginTransaction();
296
        }
297
298 1007
        if (! $this->_eventManager->hasListeners(Events::postConnect)) {
299 963
            return;
300
        }
301
302 44
        $eventArgs = new Event\ConnectionEventArgs($this);
303 44
        $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
304 44
    }
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 659
    private function detectDatabasePlatform() : void
314
    {
315 659
        $version = $this->getDatabasePlatformVersion();
316
317 593
        if ($version !== null) {
318 394
            assert($this->_driver instanceof VersionAwarePlatformDriver);
319
320 394
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
321
        } else {
322 199
            $this->platform = $this->_driver->getDatabasePlatform();
323
        }
324
325 593
        $this->platform->setEventManager($this->_eventManager);
326 593
    }
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 659
    private function getDatabasePlatformVersion() : ?string
339
    {
340
        // Driver does not support version specific platforms.
341 659
        if (! $this->_driver instanceof VersionAwarePlatformDriver) {
342 199
            return null;
343
        }
344
345
        // Explicit platform version requested (supersedes auto-detection).
346 460
        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 460
        if ($this->_conn === null) {
352
            try {
353 364
                $this->connect();
354 66
            } catch (Throwable $originalException) {
355 66
                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 66
                $databaseName           = $this->params['dbname'];
362 66
                $this->params['dbname'] = null;
363
364
                try {
365 66
                    $this->connect();
366 66
                } 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 66
                    $this->params['dbname'] = $databaseName;
371
372 66
                    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 394
        return $this->getServerVersion();
387
    }
388
389
    /**
390
     * Returns the database server version if the underlying driver supports it.
391
     */
392 394
    private function getServerVersion() : ?string
393
    {
394 394
        $connection = $this->getWrappedConnection();
395
396
        // Automatic platform version detection.
397 394
        if ($connection instanceof ServerInfoAwareConnection) {
398 394
            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 44
    public function isAutoCommit() : bool
413
    {
414 44
        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 110
    public function setAutoCommit(bool $autoCommit) : void
433
    {
434
        // Mode not changed, no-op.
435 110
        if ($autoCommit === $this->autoCommit) {
436
            return;
437
        }
438
439 110
        $this->autoCommit = $autoCommit;
440
441
        // Commit all currently active transactions if any when switching auto-commit mode.
442 110
        if (! $this->isConnected || $this->transactionNestingLevel === 0) {
443 88
            return;
444
        }
445
446 22
        $this->commitAll();
447 22
    }
448
449
    /**
450
     * Sets the fetch mode.
451
     */
452 22
    public function setFetchMode(int $fetchMode) : void
453
    {
454 22
        $this->defaultFetchMode = $fetchMode;
455 22
    }
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 2185
    public function fetchAssoc(string $query, array $params = [], array $types = [])
470
    {
471 2185
        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 81
    public function fetchArray(string $query, array $params = [], array $types = [])
487
    {
488 81
        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 1258
    public function fetchColumn(string $query, array $params = [], array $types = [])
504
    {
505 1258
        return $this->executeQuery($query, $params, $types)->fetchColumn();
506
    }
507
508
    /**
509
     * Whether an actual connection to the database is established.
510
     */
511 194
    public function isConnected() : bool
512
    {
513 194
        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 8303
    public function isTransactionActive() : bool
522
    {
523 8303
        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 301
    private function addIdentifierCondition(
537
        array $identifier,
538
        array &$columns,
539
        array &$values,
540
        array &$conditions
541
    ) : void {
542 301
        $platform = $this->getDatabasePlatform();
543
544 301
        foreach ($identifier as $columnName => $value) {
545 301
            if ($value === null) {
546 88
                $conditions[] = $platform->getIsNullExpression($columnName);
547 88
                continue;
548
            }
549
550 257
            $columns[]    = $columnName;
551 257
            $values[]     = $value;
552 257
            $conditions[] = $columnName . ' = ?';
553
        }
554 301
    }
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 129
    public function delete(string $table, array $identifier, array $types = []) : int
571
    {
572 129
        if (count($identifier) === 0) {
573 22
            throw EmptyCriteriaNotAllowed::new();
574
        }
575
576 107
        $columns = $values = $conditions = [];
577
578 107
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
579
580 107
        return $this->executeUpdate(
581 107
            'DELETE FROM ' . $table . ' WHERE ' . implode(' AND ', $conditions),
582
            $values,
583 107
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
584
        );
585
    }
586
587
    /**
588
     * Closes the connection.
589
     */
590 516
    public function close() : void
591
    {
592 516
        $this->_conn = null;
593
594 516
        $this->isConnected = false;
595 516
    }
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 194
    public function update(string $table, array $data, array $identifier, array $types = []) : int
638
    {
639 194
        $columns = $values = $conditions = $set = [];
640
641 194
        foreach ($data as $columnName => $value) {
642 194
            $columns[] = $columnName;
643 194
            $values[]  = $value;
644 194
            $set[]     = $columnName . ' = ?';
645
        }
646
647 194
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
648
649 194
        if (is_string(key($types))) {
650 110
            $types = $this->extractTypeValues($columns, $types);
651
        }
652
653 194
        $sql = 'UPDATE ' . $table . ' SET ' . implode(', ', $set)
654 194
                . ' WHERE ' . implode(' AND ', $conditions);
655
656 194
        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 2011
    public function insert(string $table, array $data, array $types = []) : int
673
    {
674 2011
        if (count($data) === 0) {
675 22
            return $this->executeUpdate('INSERT INTO ' . $table . ' () VALUES ()');
676
        }
677
678 1989
        $columns = [];
679 1989
        $values  = [];
680 1989
        $set     = [];
681
682 1989
        foreach ($data as $columnName => $value) {
683 1989
            $columns[] = $columnName;
684 1989
            $values[]  = $value;
685 1989
            $set[]     = '?';
686
        }
687
688 1989
        return $this->executeUpdate(
689 1989
            'INSERT INTO ' . $table . ' (' . implode(', ', $columns) . ')' .
690 1989
            ' VALUES (' . implode(', ', $set) . ')',
691
            $values,
692 1989
            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 198
    private function extractTypeValues(array $columnList, array $types)
705
    {
706 198
        $typeValues = [];
707
708 198
        foreach ($columnList as $columnName) {
709 198
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
710
        }
711
712 198
        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 22
    public function quoteIdentifier(string $identifier) : string
730
    {
731 22
        return $this->getDatabasePlatform()->quoteIdentifier($identifier);
732
    }
733
734 157
    public function quote(string $input) : string
735
    {
736 157
        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 2518
    public function fetchAll(string $query, array $params = [], array $types = []) : array
749
    {
750 2518
        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 1883
    public function prepare(string $sql) : DriverStatement
761
    {
762
        try {
763 1883
            $stmt = new Statement($sql, $this);
764 22
        } catch (Throwable $ex) {
765 22
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $sql);
766
        }
767
768 1861
        $stmt->setFetchMode($this->defaultFetchMode);
769
770 1861
        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 5191
    public function executeQuery(
789
        string $query,
790
        array $params = [],
791
        array $types = [],
792
        ?QueryCacheProfile $qcp = null
793
    ) : ResultStatement {
794 5191
        if ($qcp !== null) {
795 242
            return $this->executeCacheQuery($query, $params, $types, $qcp);
796
        }
797
798 5191
        $connection = $this->getWrappedConnection();
799
800 5191
        $logger = $this->_config->getSQLLogger();
801 5191
        $logger->startQuery($query, $params, $types);
802
803
        try {
804 5191
            if (count($params) > 0) {
805 814
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
806
807 814
                $stmt = $connection->prepare($query);
808 814
                if (count($types) > 0) {
809 792
                    $this->_bindTypedValues($stmt, $params, $types);
810 736
                    $stmt->execute();
811
                } else {
812 754
                    $stmt->execute($params);
813
                }
814
            } else {
815 5131
                $stmt = $connection->query($query);
816
            }
817 139
        } catch (Throwable $ex) {
818 139
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
819
        }
820
821 5052
        $stmt->setFetchMode($this->defaultFetchMode);
822
823 5052
        $logger->stopQuery();
824
825 5052
        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 330
    public function executeCacheQuery(string $query, array $params, array $types, QueryCacheProfile $qcp) : ResultStatement
839
    {
840 330
        $resultCache = $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
841
842 330
        if ($resultCache === null) {
843
            throw NoResultDriverConfigured::new();
844
        }
845
846 330
        $connectionParams = $this->getParams();
847 330
        unset($connectionParams['platform']);
848
849 330
        [$cacheKey, $realKey] = $qcp->generateCacheKeys($query, $params, $types, $connectionParams);
850
851
        // fetch the row pointers entry
852 330
        $data = $resultCache->fetch($cacheKey);
853
854 330
        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 242
            if (isset($data[$realKey])) {
857 242
                $stmt = new ArrayStatement($data[$realKey]);
858
            } elseif (array_key_exists($realKey, $data)) {
859
                $stmt = new ArrayStatement([]);
860
            }
861
        }
862
863 330
        if (! isset($stmt)) {
864 264
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
865
        }
866
867 330
        $stmt->setFetchMode($this->defaultFetchMode);
868
869 330
        return $stmt;
870
    }
871
872 2277
    public function query(string $sql) : ResultStatement
873
    {
874 2277
        $connection = $this->getWrappedConnection();
875
876 2277
        $logger = $this->_config->getSQLLogger();
877 2277
        $logger->startQuery($sql);
878
879
        try {
880 2277
            $statement = $connection->query($sql);
881 22
        } catch (Throwable $ex) {
882 22
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $sql);
883
        }
884
885 2255
        $statement->setFetchMode($this->defaultFetchMode);
886
887 2255
        $logger->stopQuery();
888
889 2255
        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 3917
    public function executeUpdate(string $query, array $params = [], array $types = []) : int
905
    {
906 3917
        $connection = $this->getWrappedConnection();
907
908 3917
        $logger = $this->_config->getSQLLogger();
909 3917
        $logger->startQuery($query, $params, $types);
910
911
        try {
912 3917
            if (count($params) > 0) {
913 2059
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
914
915 2059
                $stmt = $connection->prepare($query);
916
917 2052
                if (count($types) > 0) {
918 2052
                    $this->_bindTypedValues($stmt, $params, $types);
919 2052
                    $stmt->execute();
920
                } else {
921
                    $stmt->execute($params);
922
                }
923
924 2021
                $result = $stmt->rowCount();
925
            } else {
926 3879
                $result = $connection->exec($query);
927
            }
928 1871
        } catch (Throwable $ex) {
929 1871
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
930
        }
931
932 3608
        $logger->stopQuery();
933
934 3608
        return $result;
935
    }
936
937 1820
    public function exec(string $statement) : int
938
    {
939 1820
        $connection = $this->getWrappedConnection();
940
941 1817
        $logger = $this->_config->getSQLLogger();
942 1817
        $logger->startQuery($statement);
943
944
        try {
945 1817
            $result = $connection->exec($statement);
946 1341
        } catch (Throwable $ex) {
947 1341
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
948
        }
949
950 568
        $logger->stopQuery();
951
952 568
        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 261
    public function getTransactionNestingLevel() : int
961
    {
962 261
        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 92
    public function lastInsertId(?string $name = null) : string
978
    {
979 92
        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 88
    public function transactional(Closure $func)
997
    {
998 88
        $this->beginTransaction();
999
        try {
1000 88
            $res = $func($this);
1001 44
            $this->commit();
1002
1003 44
            return $res;
1004 44
        } catch (Throwable $e) {
1005 44
            $this->rollBack();
1006
1007 44
            throw $e;
1008
        }
1009
    }
1010
1011
    /**
1012
     * Sets if nested transactions should use savepoints.
1013
     *
1014
     * @throws ConnectionException
1015
     */
1016 43
    public function setNestTransactionsWithSavepoints(bool $nestTransactionsWithSavepoints) : void
1017
    {
1018 43
        if ($this->transactionNestingLevel > 0) {
1019 42
            throw MayNotAlterNestedTransactionWithSavepointsInTransaction::new();
1020
        }
1021
1022 22
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1023 1
            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 448
    public function beginTransaction() : void
1049
    {
1050 448
        $connection = $this->getWrappedConnection();
1051
1052 448
        ++$this->transactionNestingLevel;
1053
1054 448
        $logger = $this->_config->getSQLLogger();
1055
1056 448
        if ($this->transactionNestingLevel === 1) {
1057 448
            $logger->startQuery('"START TRANSACTION"');
1058
1059
            try {
1060 448
                $connection->beginTransaction();
1061 448
            } finally {
1062 448
                $logger->stopQuery();
1063
            }
1064 87
        } elseif ($this->nestTransactionsWithSavepoints) {
1065 21
            $logger->startQuery('"SAVEPOINT"');
1066 21
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1067 21
            $logger->stopQuery();
1068
        }
1069 448
    }
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 252
    public function commit() : void
1078
    {
1079 252
        if ($this->transactionNestingLevel === 0) {
1080 22
            throw NoActiveTransaction::new();
1081
        }
1082
1083 230
        if ($this->isRollbackOnly) {
1084 44
            throw CommitFailedRollbackOnly::new();
1085
        }
1086
1087 186
        $connection = $this->getWrappedConnection();
1088
1089 186
        $logger = $this->_config->getSQLLogger();
1090
1091 186
        if ($this->transactionNestingLevel === 1) {
1092 186
            $logger->startQuery('"COMMIT"');
1093
1094
            try {
1095 186
                $connection->commit();
1096 186
            } finally {
1097 186
                $logger->stopQuery();
1098
            }
1099 43
        } elseif ($this->nestTransactionsWithSavepoints) {
1100 21
            $logger->startQuery('"RELEASE SAVEPOINT"');
1101 21
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1102 21
            $logger->stopQuery();
1103
        }
1104
1105 186
        --$this->transactionNestingLevel;
1106
1107 186
        if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
1108 164
            return;
1109
        }
1110
1111 44
        $this->beginTransaction();
1112 44
    }
1113
1114
    /**
1115
     * Commits all current nesting transactions.
1116
     *
1117
     * @throws ConnectionException
1118
     * @throws DriverException
1119
     */
1120 22
    private function commitAll() : void
1121
    {
1122 22
        while ($this->transactionNestingLevel !== 0) {
1123 22
            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 22
                $this->commit();
1127
1128 22
                return;
1129
            }
1130
1131 22
            $this->commit();
1132
        }
1133 22
    }
1134
1135
    /**
1136
     * {@inheritDoc}
1137
     *
1138
     * @throws ConnectionException If the rollback operation failed.
1139
     */
1140 304
    public function rollBack() : void
1141
    {
1142 304
        if ($this->transactionNestingLevel === 0) {
1143 22
            throw NoActiveTransaction::new();
1144
        }
1145
1146 282
        $connection = $this->getWrappedConnection();
1147
1148 282
        $logger = $this->_config->getSQLLogger();
1149
1150 282
        if ($this->transactionNestingLevel === 1) {
1151 261
            $logger->startQuery('"ROLLBACK"');
1152 261
            $this->transactionNestingLevel = 0;
1153
1154
            try {
1155 261
                $connection->rollBack();
1156 261
            } finally {
1157 261
                $this->isRollbackOnly = false;
1158 261
                $logger->stopQuery();
1159
1160 261
                if ($this->autoCommit === false) {
1161 261
                    $this->beginTransaction();
1162
                }
1163
            }
1164 44
        } 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 23
            $this->isRollbackOnly = true;
1171 23
            --$this->transactionNestingLevel;
1172
        }
1173 282
    }
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 22
    public function createSavepoint(string $savepoint) : void
1183
    {
1184 22
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1185 1
            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 22
    public function releaseSavepoint(string $savepoint) : void
1199
    {
1200 22
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1201 1
            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 22
    public function rollbackSavepoint(string $savepoint) : void
1219
    {
1220 22
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1221 1
            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 8432
    public function getWrappedConnection() : DriverConnection
1231
    {
1232 8432
        $this->connect();
1233
1234 8429
        assert($this->_conn !== null);
1235
1236 8429
        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 4326
    public function getSchemaManager() : AbstractSchemaManager
1244
    {
1245 4326
        if ($this->_schemaManager === null) {
1246 277
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1247
        }
1248
1249 4326
        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 44
    public function setRollbackOnly() : void
1259
    {
1260 44
        if ($this->transactionNestingLevel === 0) {
1261 22
            throw NoActiveTransaction::new();
1262
        }
1263
1264 22
        $this->isRollbackOnly = true;
1265 22
    }
1266
1267
    /**
1268
     * Checks whether the current transaction is marked for rollback only.
1269
     *
1270
     * @throws ConnectionException If no transaction is active.
1271
     */
1272 65
    public function isRollbackOnly() : bool
1273
    {
1274 65
        if ($this->transactionNestingLevel === 0) {
1275 22
            throw NoActiveTransaction::new();
1276
        }
1277
1278 43
        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 2708
    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 2708
        if (is_int(key($params))) {
1324
            // Positional parameters
1325 2708
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1326 2708
            $bindIndex  = 1;
1327 2708
            foreach ($params as $value) {
1328 2708
                $typeIndex = $bindIndex + $typeOffset;
1329 2708
                if (isset($types[$typeIndex])) {
1330 620
                    $type                  = $types[$typeIndex];
1331 620
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1332 620
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1333
                } else {
1334 2158
                    $stmt->bindValue($bindIndex, $value);
1335
                }
1336
1337 2708
                ++$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 2652
    }
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 620
    private function getBindingInfo($value, $type) : array
1362
    {
1363 620
        if (is_string($type)) {
1364 242
            $type = Type::getType($type);
1365
        }
1366
1367 620
        if ($type instanceof Type) {
1368 242
            $value       = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1369 242
            $bindingType = $type->getBindingType();
1370
        } else {
1371 488
            $bindingType = $type;
1372
        }
1373
1374 620
        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 2022
    public function resolveParams(array $params, array $types) : array
1389
    {
1390 2022
        $resolvedParams = [];
1391
1392
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1393 2022
        if (is_int(key($params))) {
1394
            // Positional parameters
1395 206
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1396 206
            $bindIndex  = 1;
1397 206
            foreach ($params as $value) {
1398 206
                $typeIndex = $bindIndex + $typeOffset;
1399 206
                if (isset($types[$typeIndex])) {
1400
                    $type                       = $types[$typeIndex];
1401
                    [$value]                    = $this->getBindingInfo($value, $type);
1402
                    $resolvedParams[$bindIndex] = $value;
1403
                } else {
1404 206
                    $resolvedParams[$bindIndex] = $value;
1405
                }
1406
1407 206
                ++$bindIndex;
1408
            }
1409
        } else {
1410
            // Named parameters
1411 1820
            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 2022
        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 22
    public function ping() : void
1457
    {
1458 22
        $connection = $this->getWrappedConnection();
1459
1460 22
        if (! $connection instanceof PingableConnection) {
1461 16
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1462
1463 16
            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