Completed
Pull Request — develop (#3348)
by Sergei
92:25 queued 30:16
created

Connection::getHost()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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