Failed Conditions
Pull Request — master (#3260)
by Michael
61:30
created

Connection::executeQuery()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

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