Failed Conditions
Push — develop ( 152bc9...e39bc0 )
by Sergei
102:42 queued 37:39
created

Connection::setRollbackOnly()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

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