Completed
Pull Request — master (#3679)
by
unknown
14:34
created

Connection::commitAll()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 10
c 0
b 0
f 0
cc 4
nc 3
nop 0
crap 4
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 func_get_args;
27
use function implode;
28
use function is_int;
29
use function is_string;
30
use function key;
31
32
/**
33
 * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
34
 * events, transaction isolation levels, configuration, emulated transaction nesting,
35
 * lazy connecting and more.
36
 */
37
class Connection implements DriverConnection
38
{
39
    /**
40
     * Constant for transaction isolation level READ UNCOMMITTED.
41
     *
42
     * @deprecated Use TransactionIsolationLevel::READ_UNCOMMITTED.
43
     */
44
    public const TRANSACTION_READ_UNCOMMITTED = TransactionIsolationLevel::READ_UNCOMMITTED;
45
46
    /**
47
     * Constant for transaction isolation level READ COMMITTED.
48
     *
49
     * @deprecated Use TransactionIsolationLevel::READ_COMMITTED.
50
     */
51
    public const TRANSACTION_READ_COMMITTED = TransactionIsolationLevel::READ_COMMITTED;
52
53
    /**
54
     * Constant for transaction isolation level REPEATABLE READ.
55
     *
56
     * @deprecated Use TransactionIsolationLevel::REPEATABLE_READ.
57
     */
58
    public const TRANSACTION_REPEATABLE_READ = TransactionIsolationLevel::REPEATABLE_READ;
59
60
    /**
61
     * Constant for transaction isolation level SERIALIZABLE.
62
     *
63
     * @deprecated Use TransactionIsolationLevel::SERIALIZABLE.
64
     */
65
    public const TRANSACTION_SERIALIZABLE = TransactionIsolationLevel::SERIALIZABLE;
66
67
    /**
68
     * Represents an array of ints to be expanded by Doctrine SQL parsing.
69
     */
70
    public const PARAM_INT_ARRAY = ParameterType::INTEGER + self::ARRAY_PARAM_OFFSET;
71
72
    /**
73
     * Represents an array of strings to be expanded by Doctrine SQL parsing.
74
     */
75
    public const PARAM_STR_ARRAY = ParameterType::STRING + self::ARRAY_PARAM_OFFSET;
76
77
    /**
78
     * Offset by which PARAM_* constants are detected as arrays of the param type.
79
     */
80
    public const ARRAY_PARAM_OFFSET = 100;
81
82
    /**
83
     * The wrapped driver connection.
84
     *
85
     * @var \Doctrine\DBAL\Driver\Connection|null
86
     */
87
    protected $_conn;
88
89
    /** @var Configuration */
90
    protected $_config;
91
92
    /** @var EventManager */
93
    protected $_eventManager;
94
95
    /** @var ExpressionBuilder */
96
    protected $_expr;
97
98
    /**
99
     * Whether or not a connection has been established.
100
     *
101
     * @var bool
102
     */
103
    private $isConnected = false;
104
105
    /**
106
     * The current auto-commit mode of this connection.
107
     *
108
     * @var bool
109
     */
110
    private $autoCommit = true;
111
112
    /**
113
     * The transaction nesting level.
114
     *
115
     * @var int
116
     */
117
    private $transactionNestingLevel = 0;
118
119
    /**
120
     * The currently active transaction isolation level.
121
     *
122
     * @var int
123
     */
124
    private $transactionIsolationLevel;
125
126
    /**
127
     * If nested transactions should use savepoints.
128
     *
129
     * @var bool
130
     */
131
    private $nestTransactionsWithSavepoints = false;
132
133
    /**
134
     * The parameters used during creation of the Connection instance.
135
     *
136
     * @var mixed[]
137
     */
138
    private $params = [];
139
140
    /**
141
     * The DatabasePlatform object that provides information about the
142
     * database platform used by the connection.
143
     *
144
     * @var AbstractPlatform
145
     */
146
    private $platform;
147
148
    /**
149
     * The schema manager.
150
     *
151
     * @var AbstractSchemaManager|null
152
     */
153
    protected $_schemaManager;
154
155
    /**
156
     * The used DBAL driver.
157
     *
158
     * @var Driver
159
     */
160
    protected $_driver;
161
162
    /**
163
     * Flag that indicates whether the current transaction is marked for rollback only.
164
     *
165
     * @var bool
166
     */
167
    private $isRollbackOnly = false;
168
169
    /** @var int */
170
    protected $defaultFetchMode = FetchMode::ASSOCIATIVE;
171
172
    /**
173
     * Initializes a new instance of the Connection class.
174
     *
175
     * @param mixed[]            $params       The connection parameters.
176
     * @param Driver             $driver       The driver to use.
177
     * @param Configuration|null $config       The configuration, optional.
178
     * @param EventManager|null  $eventManager The event manager, optional.
179
     *
180
     * @throws DBALException
181
     */
182 9382
    public function __construct(
183
        array $params,
184
        Driver $driver,
185
        ?Configuration $config = null,
186
        ?EventManager $eventManager = null
187
    ) {
188 9382
        $this->_driver = $driver;
189 9382
        $this->params  = $params;
190
191 9382
        if (isset($params['pdo'])) {
192 8387
            $this->_conn       = $params['pdo'];
193 8387
            $this->isConnected = true;
194 8387
            unset($this->params['pdo']);
195
        }
196
197 9382
        if (isset($params['platform'])) {
198 8568
            if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
199 8113
                throw DBALException::invalidPlatformType($params['platform']);
200
            }
201
202 8566
            $this->platform = $params['platform'];
203
        }
204
205
        // Create default config and event manager if none given
206 9382
        if (! $config) {
207 8977
            $config = new Configuration();
208
        }
209
210 9382
        if (! $eventManager) {
211 8725
            $eventManager = new EventManager();
212
        }
213
214 9382
        $this->_config       = $config;
215 9382
        $this->_eventManager = $eventManager;
216
217 9382
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
218
219 9382
        $this->autoCommit = $config->getAutoCommit();
220 9382
    }
221
222
    /**
223
     * Gets the parameters used during instantiation.
224
     *
225
     * @return mixed[]
226
     */
227 8605
    public function getParams()
228
    {
229 8605
        return $this->params;
230
    }
231
232
    /**
233
     * Gets the name of the database this Connection is connected to.
234
     *
235
     * @return string
236
     */
237 8022
    public function getDatabase()
238
    {
239 8022
        return $this->_driver->getDatabase($this);
240
    }
241
242
    /**
243
     * Gets the hostname of the currently connected database.
244
     *
245
     * @return string|null
246
     */
247 8988
    public function getHost()
248
    {
249 8988
        return $this->params['host'] ?? null;
250
    }
251
252
    /**
253
     * Gets the port of the currently connected database.
254
     *
255
     * @return mixed
256
     */
257 2
    public function getPort()
258
    {
259 2
        return $this->params['port'] ?? null;
260
    }
261
262
    /**
263
     * Gets the username used by this connection.
264
     *
265
     * @return string|null
266
     */
267 655
    public function getUsername()
268
    {
269 655
        return $this->params['user'] ?? null;
270
    }
271
272
    /**
273
     * Gets the password used by this connection.
274
     *
275
     * @return string|null
276
     */
277 2
    public function getPassword()
278
    {
279 2
        return $this->params['password'] ?? null;
280
    }
281
282
    /**
283
     * Gets the DBAL driver instance.
284
     *
285
     * @return Driver
286
     */
287 9078
    public function getDriver()
288
    {
289 9078
        return $this->_driver;
290
    }
291
292
    /**
293
     * Gets the Configuration used by the Connection.
294
     *
295
     * @return Configuration
296
     */
297 9628
    public function getConfiguration()
298
    {
299 9628
        return $this->_config;
300
    }
301
302
    /**
303
     * Gets the EventManager used by the Connection.
304
     *
305
     * @return EventManager
306
     */
307 6989
    public function getEventManager()
308
    {
309 6989
        return $this->_eventManager;
310
    }
311
312
    /**
313
     * Gets the DatabasePlatform for the connection.
314
     *
315
     * @return AbstractPlatform
316
     *
317
     * @throws DBALException
318
     */
319 9362
    public function getDatabasePlatform()
320
    {
321 9362
        if ($this->platform === null) {
322 8975
            $this->detectDatabasePlatform();
323
        }
324
325 9360
        return $this->platform;
326
    }
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 9571
    public function connect()
345
    {
346 9571
        if ($this->isConnected) {
347 9272
            return false;
348
        }
349
350 9036
        $driverOptions = $this->params['driverOptions'] ?? [];
351 9036
        $user          = $this->params['user'] ?? null;
352 9036
        $password      = $this->params['password'] ?? null;
353
354 9036
        $this->_conn       = $this->_driver->connect($this->params, $user, $password, $driverOptions);
355 9028
        $this->isConnected = true;
356
357 9028
        $this->transactionNestingLevel = 0;
358
359 9028
        if ($this->autoCommit === false) {
360 8667
            $this->beginTransaction();
361
        }
362
363 9028
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
364 8990
            $eventArgs = new Event\ConnectionEventArgs($this);
365 8990
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
366
        }
367
368 9028
        return true;
369
    }
370
371
    /**
372
     * Detects and sets the database platform.
373
     *
374
     * Evaluates custom platform class and version in order to set the correct platform.
375
     *
376
     * @throws DBALException If an invalid platform was specified for this connection.
377
     */
378 8975
    private function detectDatabasePlatform()
379
    {
380 8975
        $version = $this->getDatabasePlatformVersion();
381
382 8973
        if ($version !== null) {
383 8188
            assert($this->_driver instanceof VersionAwarePlatformDriver);
384
385 8188
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
386
        } else {
387 8971
            $this->platform = $this->_driver->getDatabasePlatform();
388
        }
389
390 8973
        $this->platform->setEventManager($this->_eventManager);
391 8973
    }
392
393
    /**
394
     * Returns the version of the related platform if applicable.
395
     *
396
     * Returns null if either the driver is not capable to create version
397
     * specific platform instances, no explicit server version was specified
398
     * or the underlying driver connection cannot determine the platform
399
     * version without having to query it (performance reasons).
400
     *
401
     * @return string|null
402
     *
403
     * @throws Exception
404
     */
405 8975
    private function getDatabasePlatformVersion()
406
    {
407
        // Driver does not support version specific platforms.
408 8975
        if (! $this->_driver instanceof VersionAwarePlatformDriver) {
409 8971
            return null;
410
        }
411
412
        // Explicit platform version requested (supersedes auto-detection).
413 8190
        if (isset($this->params['serverVersion'])) {
414
            return $this->params['serverVersion'];
415
        }
416
417
        // If not connected, we need to connect now to determine the platform version.
418 8190
        if ($this->_conn === null) {
419
            try {
420 8190
                $this->connect();
421 8088
            } catch (Throwable $originalException) {
422 8088
                if (empty($this->params['dbname'])) {
423
                    throw $originalException;
424
                }
425
426
                // The database to connect to might not yet exist.
427
                // Retry detection without database name connection parameter.
428 8088
                $databaseName           = $this->params['dbname'];
429 8088
                $this->params['dbname'] = null;
430
431
                try {
432 8088
                    $this->connect();
433 8088
                } catch (Throwable $fallbackException) {
434
                    // Either the platform does not support database-less connections
435
                    // or something else went wrong.
436
                    // Reset connection parameters and rethrow the original exception.
437 8088
                    $this->params['dbname'] = $databaseName;
438
439 8088
                    throw $originalException;
440
                }
441
442
                // Reset connection parameters.
443 6515
                $this->params['dbname'] = $databaseName;
444 6515
                $serverVersion          = $this->getServerVersion();
445
446
                // Close "temporary" connection to allow connecting to the real database again.
447 6515
                $this->close();
448
449 6515
                return $serverVersion;
450
            }
451
        }
452
453 8188
        return $this->getServerVersion();
454
    }
455
456
    /**
457
     * Returns the database server version if the underlying driver supports it.
458
     *
459
     * @return string|null
460
     */
461 8188
    private function getServerVersion()
462
    {
463 8188
        $connection = $this->getWrappedConnection();
464
465
        // Automatic platform version detection.
466 8188
        if ($connection instanceof ServerInfoAwareConnection && ! $connection->requiresQueryForServerVersion()) {
467 8188
            return $connection->getServerVersion();
468
        }
469
470
        // Unable to detect platform version.
471
        return null;
472
    }
473
474
    /**
475
     * Returns the current auto-commit mode for this connection.
476
     *
477
     * @see    setAutoCommit
478
     *
479
     * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
480
     */
481 8715
    public function isAutoCommit()
482
    {
483 8715
        return $this->autoCommit === true;
484
    }
485
486
    /**
487
     * Sets auto-commit mode for this connection.
488
     *
489
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
490
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
491
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
492
     *
493
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
494
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
495
     *
496
     * @see   isAutoCommit
497
     *
498
     * @param bool $autoCommit True to enable auto-commit mode; false to disable it.
499
     */
500 8696
    public function setAutoCommit($autoCommit)
501
    {
502 8696
        $autoCommit = (bool) $autoCommit;
503
504
        // Mode not changed, no-op.
505 8696
        if ($autoCommit === $this->autoCommit) {
506 8688
            return;
507
        }
508
509 8696
        $this->autoCommit = $autoCommit;
510
511
        // Commit all currently active transactions if any when switching auto-commit mode.
512 8696
        if ($this->isConnected !== true || $this->transactionNestingLevel === 0) {
513 8694
            return;
514
        }
515
516 8563
        $this->commitAll();
517 8563
    }
518
519
    /**
520
     * Sets the fetch mode.
521
     *
522
     * @param int $fetchMode
523
     *
524
     * @return void
525
     */
526 4328
    public function setFetchMode($fetchMode)
527
    {
528 4328
        $this->defaultFetchMode = $fetchMode;
529 4328
    }
530
531
    /**
532
     * Prepares and executes an SQL query and returns the first row of the result
533
     * as an associative array.
534
     *
535
     * @param string         $statement The SQL query.
536
     * @param mixed[]        $params    The query parameters.
537
     * @param int[]|string[] $types     The query parameter types.
538
     *
539
     * @return mixed[]|false False is returned if no rows are found.
540
     *
541
     * @throws DBALException
542
     */
543 8602
    public function fetchAssoc($statement, array $params = [], array $types = [])
544
    {
545 8602
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::ASSOCIATIVE);
546
    }
547
548
    /**
549
     * Prepares and executes an SQL query and returns the first row of the result
550
     * as a numerically indexed array.
551
     *
552
     * @param string         $statement The SQL query to be executed.
553
     * @param mixed[]        $params    The prepared statement params.
554
     * @param int[]|string[] $types     The query parameter types.
555
     *
556
     * @return mixed[]|false False is returned if no rows are found.
557
     */
558 6576
    public function fetchArray($statement, array $params = [], array $types = [])
559
    {
560 6576
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::NUMERIC);
561
    }
562
563
    /**
564
     * Prepares and executes an SQL query and returns the value of a single column
565
     * of the first row of the result.
566
     *
567
     * @param string         $statement The SQL query to be executed.
568
     * @param mixed[]        $params    The prepared statement params.
569
     * @param int            $column    The 0-indexed column number to retrieve.
570
     * @param int[]|string[] $types     The query parameter types.
571
     *
572
     * @return mixed|false False is returned if no rows are found.
573
     *
574
     * @throws DBALException
575
     */
576 8509
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
577
    {
578 8509
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
579
    }
580
581
    /**
582
     * Whether an actual connection to the database is established.
583
     *
584
     * @return bool
585
     */
586 9117
    public function isConnected()
587
    {
588 9117
        return $this->isConnected;
589
    }
590
591
    /**
592
     * Checks whether a transaction is currently active.
593
     *
594
     * @return bool TRUE if a transaction is currently active, FALSE otherwise.
595
     */
596 9701
    public function isTransactionActive()
597
    {
598 9701
        return $this->transactionNestingLevel > 0;
599
    }
600
601
    /**
602
     * Adds identifier condition to the query components
603
     *
604
     * @param mixed[]  $identifier Map of key columns to their values
605
     * @param string[] $columns    Column names
606
     * @param mixed[]  $values     Column values
607
     * @param string[] $conditions Key conditions
608
     *
609
     * @throws DBALException
610
     */
611 8540
    private function addIdentifierCondition(
612
        array $identifier,
613
        array &$columns,
614
        array &$values,
615
        array &$conditions
616
    ) : void {
617 8540
        $platform = $this->getDatabasePlatform();
618
619 8540
        foreach ($identifier as $columnName => $value) {
620 8540
            if ($value === null) {
621 8494
                $conditions[] = $platform->getIsNullExpression($columnName);
622 8494
                continue;
623
            }
624
625 8536
            $columns[]    = $columnName;
626 8536
            $values[]     = $value;
627 8536
            $conditions[] = $columnName . ' = ?';
628
        }
629 8540
    }
630
631
    /**
632
     * Executes an SQL DELETE statement on a table.
633
     *
634
     * Table expression and columns are not escaped and are not safe for user-input.
635
     *
636
     * @param string         $tableExpression The expression of the table on which to delete.
637
     * @param mixed[]        $identifier      The deletion criteria. An associative array containing column-value pairs.
638
     * @param int[]|string[] $types           The types of identifiers.
639
     *
640
     * @return int The number of affected rows.
641
     *
642
     * @throws DBALException
643
     * @throws InvalidArgumentException
644
     */
645 8475
    public function delete($tableExpression, array $identifier, array $types = [])
646
    {
647 8475
        if (empty($identifier)) {
648 8338
            throw InvalidArgumentException::fromEmptyCriteria();
649
        }
650
651 8473
        $columns = $values = $conditions = [];
652
653 8473
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
654
655 8473
        return $this->executeUpdate(
656 8473
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
657 12
            $values,
658 8473
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
659
        );
660
    }
661
662
    /**
663
     * Closes the connection.
664
     *
665
     * @return void
666
     */
667 8026
    public function close()
668
    {
669 8026
        $this->_conn = null;
670
671 8026
        $this->isConnected = false;
672 8026
    }
673
674
    /**
675
     * Sets the transaction isolation level.
676
     *
677
     * @param int $level The level to set.
678
     *
679
     * @return int
680
     */
681
    public function setTransactionIsolation($level)
682
    {
683
        $this->transactionIsolationLevel = $level;
684
685
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
686
    }
687
688
    /**
689
     * Gets the currently active transaction isolation level.
690
     *
691
     * @return int The current transaction isolation level.
692
     */
693
    public function getTransactionIsolation()
694
    {
695
        if ($this->transactionIsolationLevel === null) {
696
            $this->transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
697
        }
698
699
        return $this->transactionIsolationLevel;
700
    }
701
702
    /**
703
     * Executes an SQL UPDATE statement on a table.
704
     *
705
     * Table expression and columns are not escaped and are not safe for user-input.
706
     *
707
     * @param string         $tableExpression The expression of the table to update quoted or unquoted.
708
     * @param mixed[]        $data            An associative array containing column-value pairs.
709
     * @param mixed[]        $identifier      The update criteria. An associative array containing column-value pairs.
710
     * @param int[]|string[] $types           Types of the merged $data and $identifier arrays in that order.
711
     *
712
     * @return int The number of affected rows.
713
     *
714
     * @throws DBALException
715
     */
716 8528
    public function update($tableExpression, array $data, array $identifier, array $types = [])
717
    {
718 8528
        $columns = $values = $conditions = $set = [];
719
720 8528
        foreach ($data as $columnName => $value) {
721 8528
            $columns[] = $columnName;
722 8528
            $values[]  = $value;
723 8528
            $set[]     = $columnName . ' = ?';
724
        }
725
726 8528
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
727
728 8528
        if (is_string(key($types))) {
729 8521
            $types = $this->extractTypeValues($columns, $types);
730
        }
731
732 8528
        $sql = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
733 8528
                . ' WHERE ' . implode(' AND ', $conditions);
734
735 8528
        return $this->executeUpdate($sql, $values, $types);
736
    }
737
738
    /**
739
     * Inserts a table row with specified data.
740
     *
741
     * Table expression and columns are not escaped and are not safe for user-input.
742
     *
743
     * @param string         $tableExpression The expression of the table to insert data into, quoted or unquoted.
744
     * @param mixed[]        $data            An associative array containing column-value pairs.
745
     * @param int[]|string[] $types           Types of the inserted data.
746
     *
747
     * @return int The number of affected rows.
748
     *
749
     * @throws DBALException
750
     */
751 8693
    public function insert($tableExpression, array $data, array $types = [])
752
    {
753 8693
        if (empty($data)) {
754 8538
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' () VALUES ()');
755
        }
756
757 8441
        $columns = [];
758 8441
        $values  = [];
759 8441
        $set     = [];
760
761 8441
        foreach ($data as $columnName => $value) {
762 8441
            $columns[] = $columnName;
763 8441
            $values[]  = $value;
764 8441
            $set[]     = '?';
765
        }
766
767 8441
        return $this->executeUpdate(
768 8441
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
769 8441
            ' VALUES (' . implode(', ', $set) . ')',
770 155
            $values,
771 8441
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
772
        );
773
    }
774
775
    /**
776
     * Extract ordered type list from an ordered column list and type map.
777
     *
778
     * @param int[]|string[] $columnList
779
     * @param int[]|string[] $types
780
     *
781
     * @return int[]|string[]
782
     */
783 8529
    private function extractTypeValues(array $columnList, array $types)
784
    {
785 8529
        $typeValues = [];
786
787 8529
        foreach ($columnList as $columnIndex => $columnName) {
788 8529
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
789
        }
790
791 8529
        return $typeValues;
792
    }
793
794
    /**
795
     * Quotes a string so it can be safely used as a table or column name, even if
796
     * it is a reserved name.
797
     *
798
     * Delimiting style depends on the underlying database platform that is being used.
799
     *
800
     * NOTE: Just because you CAN use quoted identifiers does not mean
801
     * you SHOULD use them. In general, they end up causing way more
802
     * problems than they solve.
803
     *
804
     * @param string $str The name to be quoted.
805
     *
806
     * @return string The quoted name.
807
     */
808 6804
    public function quoteIdentifier($str)
809
    {
810 6804
        return $this->getDatabasePlatform()->quoteIdentifier($str);
811
    }
812
813
    /**
814
     * {@inheritDoc}
815
     */
816 7108
    public function quote($input, $type = null)
817
    {
818 7108
        $connection = $this->getWrappedConnection();
819
820 7108
        [$value, $bindingType] = $this->getBindingInfo($input, $type);
821
822 7108
        return $connection->quote($value, $bindingType);
823
    }
824
825
    /**
826
     * Prepares and executes an SQL query and returns the result as an associative array.
827
     *
828
     * @param string         $sql    The SQL query.
829
     * @param mixed[]        $params The query parameters.
830
     * @param int[]|string[] $types  The query parameter types.
831
     *
832
     * @return mixed[]
833
     */
834 8595
    public function fetchAll($sql, array $params = [], $types = [])
835
    {
836 8595
        return $this->executeQuery($sql, $params, $types)->fetchAll();
837
    }
838
839
    /**
840
     * Prepares an SQL statement.
841
     *
842
     * @param string $statement The SQL statement to prepare.
843
     *
844
     * @return DriverStatement The prepared statement.
845
     *
846
     * @throws DBALException
847
     */
848 8865
    public function prepare($statement)
849
    {
850
        try {
851 8865
            $stmt = new Statement($statement, $this);
852 8788
        } catch (Throwable $ex) {
853 8788
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
854
        }
855
856 8313
        $stmt->setFetchMode($this->defaultFetchMode);
857
858 8313
        return $stmt;
859
    }
860
861
    /**
862
     * Executes an, optionally parametrized, SQL query.
863
     *
864
     * If the query is parametrized, a prepared statement is used.
865
     * If an SQLLogger is configured, the execution is logged.
866
     *
867
     * @param string                 $query  The SQL query to execute.
868
     * @param mixed[]                $params The parameters to bind to the query, if any.
869
     * @param int[]|string[]         $types  The types the previous parameters are in.
870
     * @param QueryCacheProfile|null $qcp    The query cache profile, optional.
871
     *
872
     * @return ResultStatement The executed statement.
873
     *
874
     * @throws DBALException
875
     */
876 9291
    public function executeQuery($query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
877
    {
878 9291
        if ($qcp !== null) {
879 4253
            return $this->executeCacheQuery($query, $params, $types, $qcp);
880
        }
881
882 9291
        $connection = $this->getWrappedConnection();
883
884 9291
        $logger = $this->_config->getSQLLogger();
885 9291
        if ($logger) {
886 7703
            $logger->startQuery($query, $params, $types);
887
        }
888
889
        try {
890 9291
            if ($params) {
891 6828
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
892
893 6828
                $stmt = $connection->prepare($query);
894 6828
                if ($types) {
895 6758
                    $this->_bindTypedValues($stmt, $params, $types);
896 6758
                    $stmt->execute();
897
                } else {
898 6828
                    $stmt->execute($params);
899
                }
900
            } else {
901 9283
                $stmt = $connection->query($query);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Driver\Connection::query() has too many arguments starting with $query. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

901
                /** @scrutinizer ignore-call */ 
902
                $stmt = $connection->query($query);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
902
            }
903 8852
        } catch (Throwable $ex) {
904 8852
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
905
        }
906
907 7727
        $stmt->setFetchMode($this->defaultFetchMode);
908
909 7727
        if ($logger) {
910 7689
            $logger->stopQuery();
911
        }
912
913 7727
        return $stmt;
914
    }
915
916
    /**
917
     * Executes a caching query.
918
     *
919
     * @param string            $query  The SQL query to execute.
920
     * @param mixed[]           $params The parameters to bind to the query, if any.
921
     * @param int[]|string[]    $types  The types the previous parameters are in.
922
     * @param QueryCacheProfile $qcp    The query cache profile.
923
     *
924
     * @return ResultStatement
925
     *
926
     * @throws CacheException
927
     */
928 8191
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
929
    {
930 8191
        $resultCache = $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
931
932 8191
        if ($resultCache === null) {
933
            throw CacheException::noResultDriverConfigured();
934
        }
935
936 8191
        $connectionParams = $this->getParams();
937 8191
        unset($connectionParams['platform']);
938
939 8191
        [$cacheKey, $realKey] = $qcp->generateCacheKeys($query, $params, $types, $connectionParams);
940
941
        // fetch the row pointers entry
942 8191
        $data = $resultCache->fetch($cacheKey);
943
944 8191
        if ($data !== false) {
945
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
946 8183
            if (isset($data[$realKey])) {
947 8183
                $stmt = new ArrayStatement($data[$realKey]);
948
            } elseif (array_key_exists($realKey, $data)) {
949
                $stmt = new ArrayStatement([]);
950
            }
951
        }
952
953 8191
        if (! isset($stmt)) {
954 4255
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
955
        }
956
957 8191
        $stmt->setFetchMode($this->defaultFetchMode);
958
959 8191
        return $stmt;
960
    }
961
962
    /**
963
     * Executes an, optionally parametrized, SQL query and returns the result,
964
     * applying a given projection/transformation function on each row of the result.
965
     *
966
     * @param string  $query    The SQL query to execute.
967
     * @param mixed[] $params   The parameters, if any.
968
     * @param Closure $function The transformation function that is applied on each row.
969
     *                           The function receives a single parameter, an array, that
970
     *                           represents a row of the result set.
971
     *
972
     * @return mixed[] The projected result of the query.
973
     */
974
    public function project($query, array $params, Closure $function)
975
    {
976
        $result = [];
977
        $stmt   = $this->executeQuery($query, $params);
978
979
        while ($row = $stmt->fetch()) {
980
            $result[] = $function($row);
981
        }
982
983
        $stmt->closeCursor();
984
985
        return $result;
986
    }
987
988
    /**
989
     * Executes an SQL statement, returning a result set as a Statement object.
990
     *
991
     * @return \Doctrine\DBAL\Driver\Statement
992
     *
993
     * @throws DBALException
994
     */
995 8895
    public function query()
996
    {
997 8895
        $connection = $this->getWrappedConnection();
998
999 8895
        $args = func_get_args();
1000
1001 8895
        $logger = $this->_config->getSQLLogger();
1002 8895
        if ($logger) {
1003 7488
            $logger->startQuery($args[0]);
1004
        }
1005
1006
        try {
1007 8895
            $statement = $connection->query(...$args);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Driver\Connection::query() has too many arguments starting with $args. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1007
            /** @scrutinizer ignore-call */ 
1008
            $statement = $connection->query(...$args);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1008 8863
        } catch (Throwable $ex) {
1009 8863
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1010
        }
1011
1012 7490
        $statement->setFetchMode($this->defaultFetchMode);
1013
1014 7490
        if ($logger) {
1015 7488
            $logger->stopQuery();
1016
        }
1017
1018 7490
        return $statement;
1019
    }
1020
1021
    /**
1022
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
1023
     * and returns the number of affected rows.
1024
     *
1025
     * This method supports PDO binding types as well as DBAL mapping types.
1026
     *
1027
     * @param string         $query  The SQL query.
1028
     * @param mixed[]        $params The query parameters.
1029
     * @param int[]|string[] $types  The parameter types.
1030
     *
1031
     * @return int The number of affected rows.
1032
     *
1033
     * @throws DBALException
1034
     */
1035 9175
    public function executeUpdate($query, array $params = [], array $types = [])
1036
    {
1037 9175
        $connection = $this->getWrappedConnection();
1038
1039 9175
        $logger = $this->_config->getSQLLogger();
1040 9175
        if ($logger) {
1041 7837
            $logger->startQuery($query, $params, $types);
1042
        }
1043
1044
        try {
1045 9175
            if ($params) {
1046 8480
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
1047
1048 8480
                $stmt = $connection->prepare($query);
1049
1050 8480
                if ($types) {
1051 7504
                    $this->_bindTypedValues($stmt, $params, $types);
1052 7504
                    $stmt->execute();
1053
                } else {
1054 8459
                    $stmt->execute($params);
1055
                }
1056 8476
                $result = $stmt->rowCount();
1057
            } else {
1058 9171
                $result = $connection->exec($query);
1059
            }
1060 9089
        } catch (Throwable $ex) {
1061 9089
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
1062
        }
1063
1064 8657
        if ($logger) {
1065 7819
            $logger->stopQuery();
1066
        }
1067
1068 8657
        return $result;
1069
    }
1070
1071
    /**
1072
     * Executes an SQL statement and return the number of affected rows.
1073
     *
1074
     * @param string $statement
1075
     *
1076
     * @return int The number of affected rows.
1077
     *
1078
     * @throws DBALException
1079
     */
1080 9052
    public function exec($statement)
1081
    {
1082 9052
        $connection = $this->getWrappedConnection();
1083
1084 9046
        $logger = $this->_config->getSQLLogger();
1085 9046
        if ($logger) {
1086 6014
            $logger->startQuery($statement);
1087
        }
1088
1089
        try {
1090 9046
            $result = $connection->exec($statement);
1091 9000
        } catch (Throwable $ex) {
1092 9000
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
1093
        }
1094
1095 5910
        if ($logger) {
1096 5904
            $logger->stopQuery();
1097
        }
1098
1099 5910
        return $result;
1100
    }
1101
1102
    /**
1103
     * Returns the current transaction nesting level.
1104
     *
1105
     * @return int The nesting level. A value of 0 means there's no active transaction.
1106
     */
1107 8591
    public function getTransactionNestingLevel()
1108
    {
1109 8591
        return $this->transactionNestingLevel;
1110
    }
1111
1112
    /**
1113
     * Fetches the SQLSTATE associated with the last database operation.
1114
     *
1115
     * @return string|null The last error code.
1116
     */
1117
    public function errorCode()
1118
    {
1119
        return $this->getWrappedConnection()->errorCode();
1120
    }
1121
1122
    /**
1123
     * {@inheritDoc}
1124
     */
1125
    public function errorInfo()
1126
    {
1127
        return $this->getWrappedConnection()->errorInfo();
1128
    }
1129
1130
    /**
1131
     * Returns the ID of the last inserted row, or the last value from a sequence object,
1132
     * depending on the underlying driver.
1133
     *
1134
     * Note: This method may not return a meaningful or consistent result across different drivers,
1135
     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
1136
     * columns or sequences.
1137
     *
1138
     * @param string|null $seqName Name of the sequence object from which the ID should be returned.
1139
     *
1140
     * @return string A string representation of the last inserted ID.
1141
     */
1142 806
    public function lastInsertId($seqName = null)
1143
    {
1144 806
        return $this->getWrappedConnection()->lastInsertId($seqName);
1145
    }
1146
1147
    /**
1148
     * Executes a function in a transaction.
1149
     *
1150
     * The function gets passed this Connection instance as an (optional) parameter.
1151
     *
1152
     * If an exception occurs during execution of the function or transaction commit,
1153
     * the transaction is rolled back and the exception re-thrown.
1154
     *
1155
     * @param Closure $func The function to execute transactionally.
1156
     *
1157
     * @return mixed The value returned by $func
1158
     *
1159
     * @throws Exception
1160
     * @throws Throwable
1161
     */
1162 7141
    public function transactional(Closure $func)
1163
    {
1164 7141
        $this->beginTransaction();
1165
        try {
1166 7141
            $res = $func($this);
1167 7087
            $this->commit();
1168
1169 7087
            return $res;
1170 7137
        } catch (Exception $e) {
1171 7135
            $this->rollBack();
1172 7135
            throw $e;
1173 7110
        } catch (Throwable $e) {
1174 7110
            $this->rollBack();
1175 7110
            throw $e;
1176
        }
1177
    }
1178
1179
    /**
1180
     * Sets if nested transactions should use savepoints.
1181
     *
1182
     * @param bool $nestTransactionsWithSavepoints
1183
     *
1184
     * @return void
1185
     *
1186
     * @throws ConnectionException
1187
     */
1188 7237
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
1189
    {
1190 7237
        if ($this->transactionNestingLevel > 0) {
1191 7001
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
1192
        }
1193
1194 7235
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1195 236
            throw ConnectionException::savepointsNotSupported();
1196
        }
1197
1198 6999
        $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1199 6999
    }
1200
1201
    /**
1202
     * Gets if nested transactions should use savepoints.
1203
     *
1204
     * @return bool
1205
     */
1206 6999
    public function getNestTransactionsWithSavepoints()
1207
    {
1208 6999
        return $this->nestTransactionsWithSavepoints;
1209
    }
1210
1211
    /**
1212
     * Returns the savepoint name to use for nested transactions are false if they are not supported
1213
     * "savepointFormat" parameter is not set
1214
     *
1215
     * @return mixed A string with the savepoint name or false.
1216
     */
1217 6999
    protected function _getNestedTransactionSavePointName()
1218
    {
1219 6999
        return 'DOCTRINE2_SAVEPOINT_' . $this->transactionNestingLevel;
1220
    }
1221
1222
    /**
1223
     * {@inheritDoc}
1224
     */
1225 8707
    public function beginTransaction()
1226
    {
1227 8707
        $connection = $this->getWrappedConnection();
1228
1229 8707
        ++$this->transactionNestingLevel;
1230
1231 8707
        $logger = $this->_config->getSQLLogger();
1232
1233 8707
        if ($this->transactionNestingLevel === 1) {
1234 8707
            if ($logger) {
1235 7340
                $logger->startQuery('"START TRANSACTION"');
1236
            }
1237
1238 8707
            $connection->beginTransaction();
1239
1240 8707
            if ($logger) {
1241 8707
                $logger->stopQuery();
1242
            }
1243 8569
        } elseif ($this->nestTransactionsWithSavepoints) {
1244 6999
            if ($logger) {
1245 6999
                $logger->startQuery('"SAVEPOINT"');
1246
            }
1247 6999
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1248 6999
            if ($logger) {
1249 6999
                $logger->stopQuery();
1250
            }
1251
        }
1252
1253 8707
        return true;
1254
    }
1255
1256
    /**
1257
     * {@inheritDoc}
1258
     *
1259
     * @throws ConnectionException If the commit failed due to no active transaction or
1260
     *                                            because the transaction was marked for rollback only.
1261
     */
1262 9089
    public function commit()
1263
    {
1264 9089
        if ($this->transactionNestingLevel === 0) {
1265 9063
            throw ConnectionException::noActiveTransaction();
1266
        }
1267 8662
        if ($this->isRollbackOnly) {
1268 7312
            throw ConnectionException::commitFailedRollbackOnly();
1269
        }
1270
1271 8658
        $result = true;
1272
1273 8658
        $connection = $this->getWrappedConnection();
1274
1275 8658
        $logger = $this->_config->getSQLLogger();
1276
1277 8658
        if ($this->transactionNestingLevel === 1) {
1278 8658
            if ($logger) {
1279 7244
                $logger->startQuery('"COMMIT"');
1280
            }
1281
1282 8658
            $result = $connection->commit();
1283
1284 8658
            if ($logger) {
1285 8658
                $logger->stopQuery();
1286
            }
1287 8565
        } elseif ($this->nestTransactionsWithSavepoints) {
1288 6999
            if ($logger) {
1289 6999
                $logger->startQuery('"RELEASE SAVEPOINT"');
1290
            }
1291 6999
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1292 6999
            if ($logger) {
1293 6999
                $logger->stopQuery();
1294
            }
1295
        }
1296
1297 8658
        --$this->transactionNestingLevel;
1298
1299 8658
        if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
1300 8631
            return $result;
1301
        }
1302
1303 8640
        $this->beginTransaction();
1304
1305 8640
        return $result;
1306
    }
1307
1308
    /**
1309
     * Commits all current nesting transactions.
1310
     */
1311 8563
    private function commitAll()
1312
    {
1313 8563
        while ($this->transactionNestingLevel !== 0) {
1314 8563
            if ($this->autoCommit === false && $this->transactionNestingLevel === 1) {
1315
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1316
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1317 8563
                $this->commit();
1318
1319 8563
                return;
1320
            }
1321
1322 8563
            $this->commit();
1323
        }
1324 8563
    }
1325
1326
    /**
1327
     * Cancels any database changes done during the current transaction.
1328
     *
1329
     * @throws ConnectionException If the rollback operation failed.
1330
     */
1331 9062
    public function rollBack()
1332
    {
1333 9062
        if ($this->transactionNestingLevel === 0) {
1334 9038
            throw ConnectionException::noActiveTransaction();
1335
        }
1336
1337 8610
        $connection = $this->getWrappedConnection();
1338
1339 8610
        $logger = $this->_config->getSQLLogger();
1340
1341 8610
        if ($this->transactionNestingLevel === 1) {
1342 8608
            if ($logger) {
1343 7328
                $logger->startQuery('"ROLLBACK"');
1344
            }
1345 8608
            $this->transactionNestingLevel = 0;
1346 8608
            $connection->rollBack();
1347 8608
            $this->isRollbackOnly = false;
1348 8608
            if ($logger) {
1349 7328
                $logger->stopQuery();
1350
            }
1351
1352 8608
            if ($this->autoCommit === false) {
1353 8608
                $this->beginTransaction();
1354
            }
1355 7287
        } elseif ($this->nestTransactionsWithSavepoints) {
1356 6999
            if ($logger) {
1357 6999
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1358
            }
1359 6999
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1360 6999
            --$this->transactionNestingLevel;
1361 6999
            if ($logger) {
1362 6999
                $logger->stopQuery();
1363
            }
1364
        } else {
1365 7285
            $this->isRollbackOnly = true;
1366 7285
            --$this->transactionNestingLevel;
1367
        }
1368 8610
    }
1369
1370
    /**
1371
     * Creates a new savepoint.
1372
     *
1373
     * @param string $savepoint The name of the savepoint to create.
1374
     *
1375
     * @return void
1376
     *
1377
     * @throws ConnectionException
1378
     */
1379 7234
    public function createSavepoint($savepoint)
1380
    {
1381 7234
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1382 235
            throw ConnectionException::savepointsNotSupported();
1383
        }
1384
1385 6999
        $this->getWrappedConnection()->exec($this->platform->createSavePoint($savepoint));
1386 6999
    }
1387
1388
    /**
1389
     * Releases the given savepoint.
1390
     *
1391
     * @param string $savepoint The name of the savepoint to release.
1392
     *
1393
     * @return void
1394
     *
1395
     * @throws ConnectionException
1396
     */
1397 6999
    public function releaseSavepoint($savepoint)
1398
    {
1399 6999
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1400
            throw ConnectionException::savepointsNotSupported();
1401
        }
1402
1403 6999
        if (! $this->platform->supportsReleaseSavepoints()) {
1404 504
            return;
1405
        }
1406
1407 6495
        $this->getWrappedConnection()->exec($this->platform->releaseSavePoint($savepoint));
1408 6495
    }
1409
1410
    /**
1411
     * Rolls back to the given savepoint.
1412
     *
1413
     * @param string $savepoint The name of the savepoint to rollback to.
1414
     *
1415
     * @return void
1416
     *
1417
     * @throws ConnectionException
1418
     */
1419 6999
    public function rollbackSavepoint($savepoint)
1420
    {
1421 6999
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1422
            throw ConnectionException::savepointsNotSupported();
1423
        }
1424
1425 6999
        $this->getWrappedConnection()->exec($this->platform->rollbackSavePoint($savepoint));
1426 6999
    }
1427
1428
    /**
1429
     * Gets the wrapped driver connection.
1430
     *
1431
     * @return DriverConnection
1432
     */
1433 9531
    public function getWrappedConnection()
1434
    {
1435 9531
        $this->connect();
1436
1437 9525
        return $this->_conn;
1438
    }
1439
1440
    /**
1441
     * Gets the SchemaManager that can be used to inspect or change the
1442
     * database schema through the connection.
1443
     *
1444
     * @return AbstractSchemaManager
1445
     */
1446 8353
    public function getSchemaManager()
1447
    {
1448 8353
        if ($this->_schemaManager === null) {
1449 8042
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1450
        }
1451
1452 8353
        return $this->_schemaManager;
1453
    }
1454
1455
    /**
1456
     * Marks the current transaction so that the only possible
1457
     * outcome for the transaction to be rolled back.
1458
     *
1459
     * @return void
1460
     *
1461
     * @throws ConnectionException If no transaction is active.
1462
     */
1463 7312
    public function setRollbackOnly()
1464
    {
1465 7312
        if ($this->transactionNestingLevel === 0) {
1466 2
            throw ConnectionException::noActiveTransaction();
1467
        }
1468 7310
        $this->isRollbackOnly = true;
1469 7310
    }
1470
1471
    /**
1472
     * Checks whether the current transaction is marked for rollback only.
1473
     *
1474
     * @return bool
1475
     *
1476
     * @throws ConnectionException If no transaction is active.
1477
     */
1478 7289
    public function isRollbackOnly()
1479
    {
1480 7289
        if ($this->transactionNestingLevel === 0) {
1481 2
            throw ConnectionException::noActiveTransaction();
1482
        }
1483
1484 7287
        return $this->isRollbackOnly;
1485
    }
1486
1487
    /**
1488
     * Converts a given value to its database representation according to the conversion
1489
     * rules of a specific DBAL mapping type.
1490
     *
1491
     * @param mixed  $value The value to convert.
1492
     * @param string $type  The name of the DBAL mapping type.
1493
     *
1494
     * @return mixed The converted value.
1495
     */
1496
    public function convertToDatabaseValue($value, $type)
1497
    {
1498
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1499
    }
1500
1501
    /**
1502
     * Converts a given value to its PHP representation according to the conversion
1503
     * rules of a specific DBAL mapping type.
1504
     *
1505
     * @param mixed  $value The value to convert.
1506
     * @param string $type  The name of the DBAL mapping type.
1507
     *
1508
     * @return mixed The converted type.
1509
     */
1510
    public function convertToPHPValue($value, $type)
1511
    {
1512
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1513
    }
1514
1515
    /**
1516
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1517
     * or DBAL mapping type, to a given statement.
1518
     *
1519
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
1520
     *           raw PDOStatement instances.
1521
     *
1522
     * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
1523
     * @param mixed[]                         $params The map/list of named/positional parameters.
1524
     * @param int[]|string[]                  $types  The parameter types (PDO binding types or DBAL mapping types).
1525
     *
1526
     * @return void
1527
     */
1528 7532
    private function _bindTypedValues($stmt, array $params, array $types)
1529
    {
1530
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1531 7532
        if (is_int(key($params))) {
1532
            // Positional parameters
1533 7532
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1534 7532
            $bindIndex  = 1;
1535 7532
            foreach ($params as $value) {
1536 7532
                $typeIndex = $bindIndex + $typeOffset;
1537 7532
                if (isset($types[$typeIndex])) {
1538 7532
                    $type                  = $types[$typeIndex];
1539 7532
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1540 7532
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1541
                } else {
1542 7385
                    $stmt->bindValue($bindIndex, $value);
1543
                }
1544 7532
                ++$bindIndex;
1545
            }
1546
        } else {
1547
            // Named parameters
1548
            foreach ($params as $name => $value) {
1549
                if (isset($types[$name])) {
1550
                    $type                  = $types[$name];
1551
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1552
                    $stmt->bindValue($name, $value, $bindingType);
1553
                } else {
1554
                    $stmt->bindValue($name, $value);
1555
                }
1556
            }
1557
        }
1558 7532
    }
1559
1560
    /**
1561
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1562
     *
1563
     * @param mixed           $value The value to bind.
1564
     * @param int|string|null $type  The type to bind (PDO or DBAL).
1565
     *
1566
     * @return mixed[] [0] => the (escaped) value, [1] => the binding type.
1567
     */
1568 7565
    private function getBindingInfo($value, $type)
1569
    {
1570 7565
        if (is_string($type)) {
1571 7082
            $type = Type::getType($type);
1572
        }
1573 7565
        if ($type instanceof Type) {
1574 7082
            $value       = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1575 7082
            $bindingType = $type->getBindingType();
1576
        } else {
1577 7553
            $bindingType = $type;
1578
        }
1579
1580 7565
        return [$value, $bindingType];
1581
    }
1582
1583
    /**
1584
     * Resolves the parameters to a format which can be displayed.
1585
     *
1586
     * @internal This is a purely internal method. If you rely on this method, you are advised to
1587
     *           copy/paste the code as this method may change, or be removed without prior notice.
1588
     *
1589
     * @param mixed[]        $params
1590
     * @param int[]|string[] $types
1591
     *
1592
     * @return mixed[]
1593
     */
1594 9130
    public function resolveParams(array $params, array $types)
1595
    {
1596 9130
        $resolvedParams = [];
1597
1598
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1599 9130
        if (is_int(key($params))) {
1600
            // Positional parameters
1601 6180
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1602 6180
            $bindIndex  = 1;
1603 6180
            foreach ($params as $value) {
1604 6180
                $typeIndex = $bindIndex + $typeOffset;
1605 6180
                if (isset($types[$typeIndex])) {
1606
                    $type                       = $types[$typeIndex];
1607
                    [$value]                    = $this->getBindingInfo($value, $type);
1608
                    $resolvedParams[$bindIndex] = $value;
1609
                } else {
1610 6180
                    $resolvedParams[$bindIndex] = $value;
1611
                }
1612 6180
                ++$bindIndex;
1613
            }
1614
        } else {
1615
            // Named parameters
1616 9114
            foreach ($params as $name => $value) {
1617
                if (isset($types[$name])) {
1618
                    $type                  = $types[$name];
1619
                    [$value]               = $this->getBindingInfo($value, $type);
1620
                    $resolvedParams[$name] = $value;
1621
                } else {
1622
                    $resolvedParams[$name] = $value;
1623
                }
1624
            }
1625
        }
1626
1627 9130
        return $resolvedParams;
1628
    }
1629
1630
    /**
1631
     * Creates a new instance of a SQL query builder.
1632
     *
1633
     * @return QueryBuilder
1634
     */
1635
    public function createQueryBuilder()
1636
    {
1637
        return new Query\QueryBuilder($this);
1638
    }
1639
1640
    /**
1641
     * Ping the server
1642
     *
1643
     * When the server is not available the method returns FALSE.
1644
     * It is responsibility of the developer to handle this case
1645
     * and abort the request or reconnect manually:
1646
     *
1647
     * @return bool
1648
     *
1649
     * @example
1650
     *
1651
     *   if ($conn->ping() === false) {
1652
     *      $conn->close();
1653
     *      $conn->connect();
1654
     *   }
1655
     *
1656
     * It is undefined if the underlying driver attempts to reconnect
1657
     * or disconnect when the connection is not available anymore
1658
     * as long it returns TRUE when a reconnect succeeded and
1659
     * FALSE when the connection was dropped.
1660
     */
1661 7037
    public function ping()
1662
    {
1663 7037
        $connection = $this->getWrappedConnection();
1664
1665 7037
        if ($connection instanceof PingableConnection) {
1666 1980
            return $connection->ping();
1667
        }
1668
1669
        try {
1670 7016
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1671
1672 7016
            return true;
1673
        } catch (DBALException $e) {
1674
            return false;
1675
        }
1676
    }
1677
}
1678