Failed Conditions
Pull Request — develop (#3525)
by Jonathan
65:04
created

Connection   F

Complexity

Total Complexity 156

Size/Duplication

Total Lines 1550
Duplicated Lines 0 %

Test Coverage

Coverage 90%

Importance

Changes 0
Metric Value
wmc 156
eloc 414
dl 0
loc 1550
ccs 405
cts 450
cp 0.9
c 0
b 0
f 0
rs 2

66 Methods

Rating   Name   Duplication   Size   Complexity  
A fetchAll() 0 3 1
A getTransactionNestingLevel() 0 3 1
A getDriver() 0 3 1
A isTransactionActive() 0 3 1
A getEventManager() 0 3 1
B commit() 0 34 7
A getDatabase() 0 3 1
B _bindTypedValues() 0 27 7
A setNestTransactionsWithSavepoints() 0 11 3
A _getNestedTransactionSavePointName() 0 3 1
A setTransactionIsolation() 0 5 1
A executeUpdate() 0 30 4
A getServerVersion() 0 11 3
A rollbackSavepoint() 0 7 2
A getHost() 0 3 1
A __construct() 0 39 6
A isConnected() 0 3 1
A getConfiguration() 0 3 1
A beginTransaction() 0 20 3
A releaseSavepoint() 0 11 3
A getNestTransactionsWithSavepoints() 0 3 1
A getDatabasePlatform() 0 7 2
A addIdentifierCondition() 0 17 3
B resolveParams() 0 34 7
A insert() 0 21 4
A update() 0 20 3
A getUsername() 0 3 1
A rollBack() 0 32 5
A fetchColumn() 0 3 1
B getDatabasePlatformVersion() 0 49 7
A getBindingInfo() 0 13 3
A getSchemaManager() 0 7 2
A transactional() 0 11 2
B executeCacheQuery() 0 29 7
A getPort() 0 3 1
A setRollbackOnly() 0 6 2
A quoteIdentifier() 0 3 1
A isAutoCommit() 0 3 1
A lastInsertId() 0 3 1
A getPassword() 0 3 1
A createSavepoint() 0 7 2
A commitAll() 0 12 4
A fetchArray() 0 3 1
A isRollbackOnly() 0 7 2
A convertToPHPValue() 0 3 1
A exec() 0 16 2
A ping() 0 14 3
A getWrappedConnection() 0 6 1
A getTransactionIsolation() 0 7 2
A createQueryBuilder() 0 3 1
A query() 0 18 2
A setAutoCommit() 0 15 4
A convertToDatabaseValue() 0 3 1
A fetchAssoc() 0 3 1
A executeQuery() 0 34 5
A delete() 0 14 3
A project() 0 12 2
A prepare() 0 11 2
A close() 0 5 1
A quote() 0 3 1
A getExpressionBuilder() 0 3 1
A setFetchMode() 0 3 1
A extractTypeValues() 0 9 2
A detectDatabasePlatform() 0 13 2
A getParams() 0 3 1
A connect() 0 23 4

How to fix   Complexity   

Complex Class

Complex classes like Connection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Connection, and based on these observations, apply Extract Interface, too.

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