Failed Conditions
Pull Request — master (#4007)
by Sergei
62:50
created

Connection::getServerVersion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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