Failed Conditions
Pull Request — master (#4007)
by Sergei
11:47
created

Connection::executeCacheQuery()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 30
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6.2373

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 9
nop 4
dl 0
loc 30
ccs 13
cts 16
cp 0.8125
crap 6.2373
rs 9.2222
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 2376
    public function __construct(
162
        array $params,
163
        Driver $driver,
164
        ?Configuration $config = null,
165
        ?EventManager $eventManager = null
166
    ) {
167 2376
        $this->_driver = $driver;
168 2376
        $this->params  = $params;
169
170 2376
        if (isset($params['platform'])) {
171 264
            if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
172 22
                throw InvalidPlatformType::new($params['platform']);
173
            }
174
175 242
            $this->platform = $params['platform'];
176
        }
177
178
        // Create default config and event manager if none given
179 2376
        if ($config === null) {
180 550
            $config = new Configuration();
181
        }
182
183 2376
        if ($eventManager === null) {
184 528
            $eventManager = new EventManager();
185
        }
186
187 2376
        $this->_config       = $config;
188 2376
        $this->_eventManager = $eventManager;
189
190 2376
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
191
192 2376
        $this->autoCommit = $config->getAutoCommit();
193 2376
    }
194
195
    /**
196
     * Gets the parameters used during instantiation.
197
     *
198
     * @return array<string, mixed>
199
     */
200 1648
    public function getParams() : array
201
    {
202 1648
        return $this->params;
203
    }
204
205
    /**
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 1924
    public function getDatabase() : ?string
215
    {
216 1924
        $platform = $this->getDatabasePlatform();
217 1924
        $query    = $platform->getDummySelectSQL($platform->getCurrentDatabaseExpression());
218 1924
        $database = $this->query($query)->fetchOne();
219
220 1924
        assert(is_string($database) || $database === null);
221
222 1924
        return $database;
223
    }
224
225
    /**
226
     * Gets the DBAL driver instance.
227
     */
228 1213
    public function getDriver() : Driver
229
    {
230 1213
        return $this->_driver;
231
    }
232
233
    /**
234
     * Gets the Configuration used by the Connection.
235
     */
236 8193
    public function getConfiguration() : Configuration
237
    {
238 8193
        return $this->_config;
239
    }
240
241
    /**
242
     * Gets the EventManager used by the Connection.
243
     */
244 255
    public function getEventManager() : EventManager
245
    {
246 255
        return $this->_eventManager;
247
    }
248
249
    /**
250
     * Gets the DatabasePlatform for the connection.
251
     *
252
     * @throws DBALException
253
     */
254 7013
    public function getDatabasePlatform() : AbstractPlatform
255
    {
256 7013
        if ($this->platform === null) {
257 659
            $this->detectDatabasePlatform();
258
        }
259
260 6991
        return $this->platform;
261
    }
262
263
    /**
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 8447
    public function connect() : void
277
    {
278 8447
        if ($this->isConnected) {
279 8051
            return;
280
        }
281
282 1076
        $driverOptions = $this->params['driverOptions'] ?? [];
283 1076
        $user          = $this->params['user'] ?? '';
284 1076
        $password      = $this->params['password'] ?? '';
285
286 1076
        $this->_conn       = $this->_driver->connect($this->params, $user, $password, $driverOptions);
287 1007
        $this->isConnected = true;
288
289 1007
        $this->transactionNestingLevel = 0;
290
291 1007
        if ($this->autoCommit === false) {
292 66
            $this->beginTransaction();
293
        }
294
295 1007
        if (! $this->_eventManager->hasListeners(Events::postConnect)) {
296 963
            return;
297
        }
298
299 44
        $eventArgs = new Event\ConnectionEventArgs($this);
300 44
        $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
301 44
    }
302
303
    /**
304
     * 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 659
    private function detectDatabasePlatform() : void
311
    {
312 659
        $version = $this->getDatabasePlatformVersion();
313
314 593
        if ($version !== null) {
315 394
            assert($this->_driver instanceof VersionAwarePlatformDriver);
316
317 394
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
318
        } else {
319 199
            $this->platform = $this->_driver->getDatabasePlatform();
320
        }
321
322 593
        $this->platform->setEventManager($this->_eventManager);
323 593
    }
324
325
    /**
326
     * 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 659
    private function getDatabasePlatformVersion() : ?string
336
    {
337
        // Driver does not support version specific platforms.
338 659
        if (! $this->_driver instanceof VersionAwarePlatformDriver) {
339 199
            return null;
340
        }
341
342
        // Explicit platform version requested (supersedes auto-detection).
343 460
        if (isset($this->params['serverVersion'])) {
344
            return $this->params['serverVersion'];
345
        }
346
347
        // If not connected, we need to connect now to determine the platform version.
348 460
        if ($this->_conn === null) {
349
            try {
350 364
                $this->connect();
351 66
            } catch (Throwable $originalException) {
352 66
                if (! isset($this->params['dbname'])) {
353
                    throw $originalException;
354
                }
355
356
                // The database to connect to might not yet exist.
357
                // Retry detection without database name connection parameter.
358 66
                $databaseName           = $this->params['dbname'];
359 66
                $this->params['dbname'] = null;
360
361
                try {
362 66
                    $this->connect();
363 66
                } catch (Throwable $fallbackException) {
364
                    // Either the platform does not support database-less connections
365
                    // or something else went wrong.
366
                    // Reset connection parameters and rethrow the original exception.
367 66
                    $this->params['dbname'] = $databaseName;
368
369 66
                    throw $originalException;
370
                }
371
372
                // 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 394
        return $this->getServerVersion();
384
    }
385
386
    /**
387
     * Returns the database server version if the underlying driver supports it.
388
     */
389 394
    private function getServerVersion() : ?string
390
    {
391 394
        $connection = $this->getWrappedConnection();
392
393
        // Automatic platform version detection.
394 394
        if ($connection instanceof ServerInfoAwareConnection) {
395 394
            return $connection->getServerVersion();
396
        }
397
398
        // 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 44
    public function isAutoCommit() : bool
410
    {
411 44
        return $this->autoCommit;
412
    }
413
414
    /**
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 110
    public function setAutoCommit(bool $autoCommit) : void
430
    {
431
        // Mode not changed, no-op.
432 110
        if ($autoCommit === $this->autoCommit) {
433
            return;
434
        }
435
436 110
        $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 88
            return;
441
        }
442
443 22
        $this->commitAll();
444 22
    }
445
446
    /**
447
     * 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
     * @param array<int, int|string>|array<string, int|string> $types  The query parameter types.
453
     *
454
     * @return array<string, mixed>|false False is returned if no rows are found.
455
     *
456
     * @throws DBALException
457
     */
458 2185
    public function fetchAssociative(string $query, array $params = [], array $types = [])
459
    {
460 2185
        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
     * @param array<int, int|string>|array<string, int|string> $types  The query parameter types.
470
     *
471
     * @return array<int, mixed>|false False is returned if no rows are found.
472
     *
473
     * @throws DBALException
474
     */
475 81
    public function fetchNumeric(string $query, array $params = [], array $types = [])
476
    {
477 81
        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
     * @param array<int, int|string>|array<string, int|string> $types  The query parameter types.
487
     *
488
     * @return mixed|false False is returned if no rows are found.
489
     *
490
     * @throws DBALException
491
     */
492 1258
    public function fetchOne(string $query, array $params = [], array $types = [])
493
    {
494 1258
        return $this->executeQuery($query, $params, $types)->fetchOne();
495
    }
496
497
    /**
498
     * Whether an actual connection to the database is established.
499
     */
500 194
    public function isConnected() : bool
501
    {
502 194
        return $this->isConnected;
503
    }
504
505
    /**
506
     * Checks whether a transaction is currently active.
507
     *
508
     * @return bool TRUE if a transaction is currently active, FALSE otherwise.
509
     */
510 8215
    public function isTransactionActive() : bool
511
    {
512 8215
        return $this->transactionNestingLevel > 0;
513
    }
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
     * @param array<int, string>   $conditions Key conditions
522
     *
523
     * @throws DBALException
524
     */
525 301
    private function addIdentifierCondition(
526
        array $identifier,
527
        array &$columns,
528
        array &$values,
529
        array &$conditions
530
    ) : void {
531 301
        $platform = $this->getDatabasePlatform();
532
533 301
        foreach ($identifier as $columnName => $value) {
534 301
            if ($value === null) {
535 88
                $conditions[] = $platform->getIsNullExpression($columnName);
536 88
                continue;
537
            }
538
539 257
            $columns[]    = $columnName;
540 257
            $values[]     = $value;
541 257
            $conditions[] = $columnName . ' = ?';
542
        }
543 301
    }
544
545
    /**
546
     * Executes an SQL DELETE statement on a table.
547
     *
548
     * Table expression and columns are not escaped and are not safe for user-input.
549
     *
550
     * @param string                        $table      The SQL expression of the table on which to delete.
551
     * @param array<string, mixed>          $identifier The deletion criteria. An associative array containing column-value pairs.
552
     * @param array<int|string, int|string> $types      The query parameter types.
553
     *
554
     * @return int The number of affected rows.
555
     *
556
     * @throws DBALException
557
     * @throws InvalidArgumentException
558
     */
559 129
    public function delete(string $table, array $identifier, array $types = []) : int
560
    {
561 129
        if (count($identifier) === 0) {
562 22
            throw EmptyCriteriaNotAllowed::new();
563
        }
564
565 107
        $columns = $values = $conditions = [];
566
567 107
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
568
569 107
        return $this->executeUpdate(
570 107
            'DELETE FROM ' . $table . ' WHERE ' . implode(' AND ', $conditions),
571
            $values,
572 107
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
573
        );
574
    }
575
576
    /**
577
     * Closes the connection.
578
     */
579 516
    public function close() : void
580
    {
581 516
        $this->_conn = null;
582
583 516
        $this->isConnected = false;
584 516
    }
585
586
    /**
587
     * Sets the transaction isolation level.
588
     *
589
     * @param int $level The level to set.
590
     */
591
    public function setTransactionIsolation(int $level) : void
592
    {
593
        $this->transactionIsolationLevel = $level;
594
595
        $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 194
    public function update(string $table, array $data, array $identifier, array $types = []) : int
627
    {
628 194
        $columns = $values = $conditions = $set = [];
629
630 194
        foreach ($data as $columnName => $value) {
631 194
            $columns[] = $columnName;
632 194
            $values[]  = $value;
633 194
            $set[]     = $columnName . ' = ?';
634
        }
635
636 194
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
637
638 194
        if (is_string(key($types))) {
639 110
            $types = $this->extractTypeValues($columns, $types);
640
        }
641
642 194
        $sql = 'UPDATE ' . $table . ' SET ' . implode(', ', $set)
643 194
                . ' WHERE ' . implode(' AND ', $conditions);
644
645 194
        return $this->executeUpdate($sql, $values, $types);
646
    }
647
648
    /**
649
     * Inserts a table row with specified data.
650
     *
651
     * Table expression and columns are not escaped and are not safe for user-input.
652
     *
653
     * @param string                                           $table The SQL expression of the table to insert data into, quoted or unquoted.
654
     * @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
     *
657
     * @return int The number of affected rows.
658
     *
659
     * @throws DBALException
660
     */
661 2011
    public function insert(string $table, array $data, array $types = []) : int
662
    {
663 2011
        if (count($data) === 0) {
664 22
            return $this->executeUpdate('INSERT INTO ' . $table . ' () VALUES ()');
665
        }
666
667 1989
        $columns = [];
668 1989
        $values  = [];
669 1989
        $set     = [];
670
671 1989
        foreach ($data as $columnName => $value) {
672 1989
            $columns[] = $columnName;
673 1989
            $values[]  = $value;
674 1989
            $set[]     = '?';
675
        }
676
677 1989
        return $this->executeUpdate(
678 1989
            'INSERT INTO ' . $table . ' (' . implode(', ', $columns) . ')' .
679 1989
            ' VALUES (' . implode(', ', $set) . ')',
680
            $values,
681 1989
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
682
        );
683
    }
684
685
    /**
686
     * Extract ordered type list from an ordered column list and type map.
687
     *
688
     * @param array<int, string>            $columnList
689
     * @param array<int|string, int|string> $types      The query parameter types.
690
     *
691
     * @return array<int, int>|array<int, string>
692
     */
693 198
    private function extractTypeValues(array $columnList, array $types)
694
    {
695 198
        $typeValues = [];
696
697 198
        foreach ($columnList as $columnName) {
698 198
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
699
        }
700
701 198
        return $typeValues;
702
    }
703
704
    /**
705
     * Quotes a string so it can be safely used as a table or column name, even if
706
     * it is a reserved name.
707
     *
708
     * Delimiting style depends on the underlying database platform that is being used.
709
     *
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
     * problems than they solve.
713
     *
714
     * @param string $identifier The identifier to be quoted.
715
     *
716
     * @return string The quoted identifier.
717
     */
718 22
    public function quoteIdentifier(string $identifier) : string
719
    {
720 22
        return $this->getDatabasePlatform()->quoteIdentifier($identifier);
721
    }
722
723 157
    public function quote(string $input) : string
724
    {
725 157
        return $this->getWrappedConnection()->quote($input);
726
    }
727
728
    /**
729
     * Prepares and executes an SQL query and returns the result as an associative array.
730
     *
731
     * @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
     *
735
     * @return array<int, mixed>
736
     */
737 2518
    public function fetchAllAssociative(string $query, array $params = [], array $types = []) : array
738
    {
739 2518
        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
     */
749 1861
    public function prepare(string $sql) : DriverStatement
750
    {
751
        try {
752 1861
            return new Statement($sql, $this);
753 22
        } catch (Throwable $ex) {
754 22
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $sql);
755
        }
756
    }
757
758
    /**
759
     * Executes an, optionally parametrized, SQL query.
760
     *
761
     * If the query is parametrized, a prepared statement is used.
762
     * If an SQLLogger is configured, the execution is logged.
763
     *
764
     * @param string                                           $query  The SQL query to execute.
765
     * @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
     *
769
     * @return ResultStatement The executed statement.
770
     *
771
     * @throws DBALException
772
     */
773 5147
    public function executeQuery(
774
        string $query,
775
        array $params = [],
776
        array $types = [],
777
        ?QueryCacheProfile $qcp = null
778
    ) : ResultStatement {
779 5147
        if ($qcp !== null) {
780 242
            return $this->executeCacheQuery($query, $params, $types, $qcp);
781
        }
782
783 5147
        $connection = $this->getWrappedConnection();
784
785 5147
        $logger = $this->_config->getSQLLogger();
786 5147
        $logger->startQuery($query, $params, $types);
787
788
        try {
789 5147
            if (count($params) > 0) {
790 770
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
791
792 770
                $stmt = $connection->prepare($query);
793 770
                if (count($types) > 0) {
794 748
                    $this->_bindTypedValues($stmt, $params, $types);
795 692
                    $stmt->execute();
796
                } else {
797 710
                    $stmt->execute($params);
798
                }
799
            } else {
800 5087
                $stmt = $connection->query($query);
801
            }
802 139
        } catch (Throwable $ex) {
803 139
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
804
        }
805
806 5008
        $logger->stopQuery();
807
808 5008
        return $stmt;
809
    }
810
811
    /**
812
     * Executes a caching query.
813
     *
814
     * @param string                                           $query  The SQL query to execute.
815
     * @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
     * @param QueryCacheProfile                                $qcp    The query cache profile.
818
     *
819
     * @throws CacheException
820
     */
821 330
    public function executeCacheQuery(string $query, array $params, array $types, QueryCacheProfile $qcp) : ResultStatement
822
    {
823 330
        $resultCache = $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
824
825 330
        if ($resultCache === null) {
826
            throw NoResultDriverConfigured::new();
827
        }
828
829 330
        $connectionParams = $this->getParams();
830 330
        unset($connectionParams['platform']);
831
832 330
        [$cacheKey, $realKey] = $qcp->generateCacheKeys($query, $params, $types, $connectionParams);
833
834
        // fetch the row pointers entry
835 330
        $data = $resultCache->fetch($cacheKey);
836
837 330
        if ($data !== false) {
838
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
839 242
            if (isset($data[$realKey])) {
840 242
                $stmt = new ArrayStatement($data[$realKey]);
841
            } elseif (array_key_exists($realKey, $data)) {
842
                $stmt = new ArrayStatement([]);
843
            }
844
        }
845
846 330
        if (! isset($stmt)) {
847 264
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
848
        }
849
850 330
        return $stmt;
851
    }
852
853 2255
    public function query(string $sql) : ResultStatement
854
    {
855 2255
        $connection = $this->getWrappedConnection();
856
857 2255
        $logger = $this->_config->getSQLLogger();
858 2255
        $logger->startQuery($sql);
859
860
        try {
861 2255
            $statement = $connection->query($sql);
862 22
        } catch (Throwable $ex) {
863 22
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $sql);
864
        }
865
866 2233
        $logger->stopQuery();
867
868 2233
        return $statement;
869
    }
870
871
    /**
872
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
873
     * and returns the number of affected rows.
874
     *
875
     * This method supports PDO binding types as well as DBAL mapping types.
876
     *
877
     * @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
     *
881
     * @throws DBALException
882
     */
883 3917
    public function executeUpdate(string $query, array $params = [], array $types = []) : int
884
    {
885 3917
        $connection = $this->getWrappedConnection();
886
887 3917
        $logger = $this->_config->getSQLLogger();
888 3917
        $logger->startQuery($query, $params, $types);
889
890
        try {
891 3917
            if (count($params) > 0) {
892 2059
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
893
894 2059
                $stmt = $connection->prepare($query);
895
896 2052
                if (count($types) > 0) {
897 2052
                    $this->_bindTypedValues($stmt, $params, $types);
898 2052
                    $stmt->execute();
899
                } else {
900
                    $stmt->execute($params);
901
                }
902
903 2021
                $result = $stmt->rowCount();
904
            } else {
905 3879
                $result = $connection->exec($query);
906
            }
907 1871
        } catch (Throwable $ex) {
908 1871
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
909
        }
910
911 3608
        $logger->stopQuery();
912
913 3608
        return $result;
914
    }
915
916 1820
    public function exec(string $statement) : int
917
    {
918 1820
        $connection = $this->getWrappedConnection();
919
920 1817
        $logger = $this->_config->getSQLLogger();
921 1817
        $logger->startQuery($statement);
922
923
        try {
924 1817
            $result = $connection->exec($statement);
925 1341
        } catch (Throwable $ex) {
926 1341
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
927
        }
928
929 568
        $logger->stopQuery();
930
931 568
        return $result;
932
    }
933
934
    /**
935
     * Returns the current transaction nesting level.
936
     *
937
     * @return int The nesting level. A value of 0 means there's no active transaction.
938
     */
939 261
    public function getTransactionNestingLevel() : int
940
    {
941 261
        return $this->transactionNestingLevel;
942
    }
943
944
    /**
945
     * Returns the ID of the last inserted row, or the last value from a sequence object,
946
     * depending on the underlying driver.
947
     *
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
     * columns or sequences.
951
     *
952
     * @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 92
    public function lastInsertId(?string $name = null) : string
957
    {
958 92
        return $this->getWrappedConnection()->lastInsertId($name);
959
    }
960
961
    /**
962
     * 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 88
    public function transactional(Closure $func)
976
    {
977 88
        $this->beginTransaction();
978
        try {
979 88
            $res = $func($this);
980 44
            $this->commit();
981
982 44
            return $res;
983 44
        } catch (Throwable $e) {
984 44
            $this->rollBack();
985
986 44
            throw $e;
987
        }
988
    }
989
990
    /**
991
     * Sets if nested transactions should use savepoints.
992
     *
993
     * @throws ConnectionException
994
     */
995 43
    public function setNestTransactionsWithSavepoints(bool $nestTransactionsWithSavepoints) : void
996
    {
997 43
        if ($this->transactionNestingLevel > 0) {
998 42
            throw MayNotAlterNestedTransactionWithSavepointsInTransaction::new();
999
        }
1000
1001 22
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1002 1
            throw SavepointsNotSupported::new();
1003
        }
1004
1005 21
        $this->nestTransactionsWithSavepoints = $nestTransactionsWithSavepoints;
1006 21
    }
1007
1008
    /**
1009
     * Gets if nested transactions should use savepoints.
1010
     */
1011 21
    public function getNestTransactionsWithSavepoints() : bool
1012
    {
1013 21
        return $this->nestTransactionsWithSavepoints;
1014
    }
1015
1016
    /**
1017
     * Returns the savepoint name to use for nested transactions are false if they are not supported
1018
     * "savepointFormat" parameter is not set
1019
     *
1020
     * @return mixed A string with the savepoint name or false.
1021
     */
1022 21
    protected function _getNestedTransactionSavePointName()
1023
    {
1024 21
        return 'DOCTRINE2_SAVEPOINT_' . $this->transactionNestingLevel;
1025
    }
1026
1027 448
    public function beginTransaction() : void
1028
    {
1029 448
        $connection = $this->getWrappedConnection();
1030
1031 448
        ++$this->transactionNestingLevel;
1032
1033 448
        $logger = $this->_config->getSQLLogger();
1034
1035 448
        if ($this->transactionNestingLevel === 1) {
1036 448
            $logger->startQuery('"START TRANSACTION"');
1037
1038
            try {
1039 448
                $connection->beginTransaction();
1040 448
            } finally {
1041 448
                $logger->stopQuery();
1042
            }
1043 87
        } elseif ($this->nestTransactionsWithSavepoints) {
1044 21
            $logger->startQuery('"SAVEPOINT"');
1045 21
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1046 21
            $logger->stopQuery();
1047
        }
1048 448
    }
1049
1050
    /**
1051
     * {@inheritDoc}
1052
     *
1053
     * @throws ConnectionException If the commit failed due to no active transaction or
1054
     *                                            because the transaction was marked for rollback only.
1055
     */
1056 252
    public function commit() : void
1057
    {
1058 252
        if ($this->transactionNestingLevel === 0) {
1059 22
            throw NoActiveTransaction::new();
1060
        }
1061
1062 230
        if ($this->isRollbackOnly) {
1063 44
            throw CommitFailedRollbackOnly::new();
1064
        }
1065
1066 186
        $connection = $this->getWrappedConnection();
1067
1068 186
        $logger = $this->_config->getSQLLogger();
1069
1070 186
        if ($this->transactionNestingLevel === 1) {
1071 186
            $logger->startQuery('"COMMIT"');
1072
1073
            try {
1074 186
                $connection->commit();
1075 186
            } finally {
1076 186
                $logger->stopQuery();
1077
            }
1078 43
        } elseif ($this->nestTransactionsWithSavepoints) {
1079 21
            $logger->startQuery('"RELEASE SAVEPOINT"');
1080 21
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1081 21
            $logger->stopQuery();
1082
        }
1083
1084 186
        --$this->transactionNestingLevel;
1085
1086 186
        if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
1087 164
            return;
1088
        }
1089
1090 44
        $this->beginTransaction();
1091 44
    }
1092
1093
    /**
1094
     * Commits all current nesting transactions.
1095
     *
1096
     * @throws ConnectionException
1097
     * @throws DriverException
1098
     */
1099 22
    private function commitAll() : void
1100
    {
1101 22
        while ($this->transactionNestingLevel !== 0) {
1102 22
            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 22
                $this->commit();
1106
1107 22
                return;
1108
            }
1109
1110 22
            $this->commit();
1111
        }
1112 22
    }
1113
1114
    /**
1115
     * {@inheritDoc}
1116
     *
1117
     * @throws ConnectionException If the rollback operation failed.
1118
     */
1119 304
    public function rollBack() : void
1120
    {
1121 304
        if ($this->transactionNestingLevel === 0) {
1122 22
            throw NoActiveTransaction::new();
1123
        }
1124
1125 282
        $connection = $this->getWrappedConnection();
1126
1127 282
        $logger = $this->_config->getSQLLogger();
1128
1129 282
        if ($this->transactionNestingLevel === 1) {
1130 261
            $logger->startQuery('"ROLLBACK"');
1131 261
            $this->transactionNestingLevel = 0;
1132
1133
            try {
1134 261
                $connection->rollBack();
1135 261
            } finally {
1136 261
                $this->isRollbackOnly = false;
1137 261
                $logger->stopQuery();
1138
1139 261
                if ($this->autoCommit === false) {
1140 261
                    $this->beginTransaction();
1141
                }
1142
            }
1143 44
        } elseif ($this->nestTransactionsWithSavepoints) {
1144 21
            $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1145 21
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1146 21
            --$this->transactionNestingLevel;
1147 21
            $logger->stopQuery();
1148
        } else {
1149 23
            $this->isRollbackOnly = true;
1150 23
            --$this->transactionNestingLevel;
1151
        }
1152 282
    }
1153
1154
    /**
1155
     * Creates a new savepoint.
1156
     *
1157
     * @param string $savepoint The name of the savepoint to create.
1158
     *
1159
     * @throws ConnectionException
1160
     */
1161 22
    public function createSavepoint(string $savepoint) : void
1162
    {
1163 22
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1164 1
            throw SavepointsNotSupported::new();
1165
        }
1166
1167 21
        $this->getWrappedConnection()->exec($this->platform->createSavePoint($savepoint));
1168 21
    }
1169
1170
    /**
1171
     * Releases the given savepoint.
1172
     *
1173
     * @param string $savepoint The name of the savepoint to release.
1174
     *
1175
     * @throws ConnectionException
1176
     */
1177 22
    public function releaseSavepoint(string $savepoint) : void
1178
    {
1179 22
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1180 1
            throw SavepointsNotSupported::new();
1181
        }
1182
1183 21
        if (! $this->platform->supportsReleaseSavepoints()) {
1184 3
            return;
1185
        }
1186
1187 18
        $this->getWrappedConnection()->exec($this->platform->releaseSavePoint($savepoint));
1188 18
    }
1189
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 22
    public function rollbackSavepoint(string $savepoint) : void
1198
    {
1199 22
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1200 1
            throw SavepointsNotSupported::new();
1201
        }
1202
1203 21
        $this->getWrappedConnection()->exec($this->platform->rollbackSavePoint($savepoint));
1204 21
    }
1205
1206
    /**
1207
     * Gets the wrapped driver connection.
1208
     */
1209 8344
    public function getWrappedConnection() : DriverConnection
1210
    {
1211 8344
        $this->connect();
1212
1213 8341
        assert($this->_conn !== null);
1214
1215 8341
        return $this->_conn;
1216
    }
1217
1218
    /**
1219
     * Gets the SchemaManager that can be used to inspect or change the
1220
     * database schema through the connection.
1221
     */
1222 4326
    public function getSchemaManager() : AbstractSchemaManager
1223
    {
1224 4326
        if ($this->_schemaManager === null) {
1225 277
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1226
        }
1227
1228 4326
        return $this->_schemaManager;
1229
    }
1230
1231
    /**
1232
     * Marks the current transaction so that the only possible
1233
     * outcome for the transaction to be rolled back.
1234
     *
1235
     * @throws ConnectionException If no transaction is active.
1236
     */
1237 44
    public function setRollbackOnly() : void
1238
    {
1239 44
        if ($this->transactionNestingLevel === 0) {
1240 22
            throw NoActiveTransaction::new();
1241
        }
1242
1243 22
        $this->isRollbackOnly = true;
1244 22
    }
1245
1246
    /**
1247
     * Checks whether the current transaction is marked for rollback only.
1248
     *
1249
     * @throws ConnectionException If no transaction is active.
1250
     */
1251 65
    public function isRollbackOnly() : bool
1252
    {
1253 65
        if ($this->transactionNestingLevel === 0) {
1254 22
            throw NoActiveTransaction::new();
1255
        }
1256
1257 43
        return $this->isRollbackOnly;
1258
    }
1259
1260
    /**
1261
     * Converts a given value to its database representation according to the conversion
1262
     * rules of a specific DBAL mapping type.
1263
     *
1264
     * @param mixed  $value The value to convert.
1265
     * @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
    }
1273
1274
    /**
1275
     * Converts a given value to its PHP representation according to the conversion
1276
     * rules of a specific DBAL mapping type.
1277
     *
1278
     * @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 2664
    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 2664
        if (is_int(key($params))) {
1303
            // Positional parameters
1304 2664
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1305 2664
            $bindIndex  = 1;
1306 2664
            foreach ($params as $value) {
1307 2664
                $typeIndex = $bindIndex + $typeOffset;
1308 2664
                if (isset($types[$typeIndex])) {
1309 620
                    $type                  = $types[$typeIndex];
1310 620
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1311 620
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1312
                } else {
1313 2114
                    $stmt->bindValue($bindIndex, $value);
1314
                }
1315
1316 2664
                ++$bindIndex;
1317
            }
1318
        } else {
1319
            // Named parameters
1320
            foreach ($params as $name => $value) {
1321
                if (isset($types[$name])) {
1322
                    $type                  = $types[$name];
1323
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1324
                    $stmt->bindValue($name, $value, $bindingType);
1325
                } else {
1326
                    $stmt->bindValue($name, $value);
1327
                }
1328
            }
1329
        }
1330 2608
    }
1331
1332
    /**
1333
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1334
     *
1335
     * @param mixed                $value The value to bind.
1336
     * @param int|string|Type|null $type  The type to bind (PDO or DBAL).
1337
     *
1338
     * @return array<int, mixed> [0] => the (escaped) value, [1] => the binding type.
1339
     */
1340 620
    private function getBindingInfo($value, $type) : array
1341
    {
1342 620
        if (is_string($type)) {
1343 242
            $type = Type::getType($type);
1344
        }
1345
1346 620
        if ($type instanceof Type) {
1347 242
            $value       = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1348 242
            $bindingType = $type->getBindingType();
1349
        } else {
1350 488
            $bindingType = $type;
1351
        }
1352
1353 620
        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
     *
1362
     * @param array<int, mixed>|array<string, mixed>                     $params
1363
     * @param array<int, int|string|Type>|array<string, int|string|Type> $types  The query parameter types.
1364
     *
1365
     * @return array<int, mixed>|array<string, mixed>
1366
     */
1367 2022
    public function resolveParams(array $params, array $types) : array
1368
    {
1369 2022
        $resolvedParams = [];
1370
1371
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1372 2022
        if (is_int(key($params))) {
1373
            // Positional parameters
1374 206
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1375 206
            $bindIndex  = 1;
1376 206
            foreach ($params as $value) {
1377 206
                $typeIndex = $bindIndex + $typeOffset;
1378 206
                if (isset($types[$typeIndex])) {
1379
                    $type                       = $types[$typeIndex];
1380
                    [$value]                    = $this->getBindingInfo($value, $type);
1381
                    $resolvedParams[$bindIndex] = $value;
1382
                } else {
1383 206
                    $resolvedParams[$bindIndex] = $value;
1384
                }
1385
1386 206
                ++$bindIndex;
1387
            }
1388
        } else {
1389
            // Named parameters
1390 1820
            foreach ($params as $name => $value) {
1391
                if (isset($types[$name])) {
1392
                    $type                  = $types[$name];
1393
                    [$value]               = $this->getBindingInfo($value, $type);
1394
                    $resolvedParams[$name] = $value;
1395
                } else {
1396
                    $resolvedParams[$name] = $value;
1397
                }
1398
            }
1399
        }
1400
1401 2022
        return $resolvedParams;
1402
    }
1403
1404
    /**
1405
     * Creates a new instance of a SQL query builder.
1406
     */
1407
    public function createQueryBuilder() : QueryBuilder
1408
    {
1409
        return new Query\QueryBuilder($this);
1410
    }
1411
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
     * @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 22
    public function ping() : void
1436
    {
1437 22
        $connection = $this->getWrappedConnection();
1438
1439 22
        if (! $connection instanceof PingableConnection) {
1440 16
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1441
1442 16
            return;
1443
        }
1444
1445
        try {
1446 6
            $connection->ping();
1447
        } catch (DriverException $e) {
1448
            throw DBALException::driverException($this->_driver, $e);
1449
        }
1450 6
    }
1451
}
1452