Completed
Pull Request — master (#3512)
by David
64:39 queued 61:17
created

Connection::getDatabasePlatformVersion()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 49
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7.0422

Importance

Changes 0
Metric Value
eloc 22
dl 0
loc 49
ccs 19
cts 21
cp 0.9048
rs 8.6346
c 0
b 0
f 0
cc 7
nc 7
nop 0
crap 7.0422
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 9585
    public function __construct(
183
        array $params,
184
        Driver $driver,
185
        ?Configuration $config = null,
186
        ?EventManager $eventManager = null
187
    ) {
188 9585
        $this->_driver = $driver;
189 9585
        $this->params  = $params;
190
191 9585
        if (isset($params['pdo'])) {
192 8550
            $this->_conn       = $params['pdo'];
193 8550
            $this->isConnected = true;
194 8550
            unset($this->params['pdo']);
195
        }
196
197 9585
        if (isset($params['platform'])) {
198 8733
            if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
199 8278
                throw DBALException::invalidPlatformType($params['platform']);
200
            }
201
202 8731
            $this->platform = $params['platform'];
203
        }
204
205
        // Create default config and event manager if none given
206 9585
        if (! $config) {
207 9113
            $config = new Configuration();
208
        }
209
210 9585
        if (! $eventManager) {
211 8861
            $eventManager = new EventManager();
212
        }
213
214 9585
        $this->_config       = $config;
215 9585
        $this->_eventManager = $eventManager;
216
217 9585
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
218
219 9585
        $this->autoCommit = $config->getAutoCommit();
220 9585
    }
221
222
    /**
223
     * Gets the parameters used during instantiation.
224
     *
225
     * @return mixed[]
226
     */
227 8734
    public function getParams()
228
    {
229 8734
        return $this->params;
230
    }
231
232
    /**
233
     * Gets the name of the database this Connection is connected to.
234
     *
235
     * @return string
236
     */
237 8137
    public function getDatabase()
238
    {
239 8137
        return $this->_driver->getDatabase($this);
240
    }
241
242
    /**
243
     * Gets the hostname of the currently connected database.
244
     *
245
     * @return string|null
246
     */
247 9178
    public function getHost()
248
    {
249 9178
        return $this->params['host'] ?? null;
250
    }
251
252
    /**
253
     * Gets the port of the currently connected database.
254
     *
255
     * @return mixed
256
     */
257 9153
    public function getPort()
258
    {
259 9153
        return $this->params['port'] ?? null;
260
    }
261
262
    /**
263
     * Gets the username used by this connection.
264
     *
265
     * @return string|null
266
     */
267 9155
    public function getUsername()
268
    {
269 9155
        return $this->params['user'] ?? null;
270
    }
271
272
    /**
273
     * Gets the password used by this connection.
274
     *
275
     * @return string|null
276
     */
277 9128
    public function getPassword()
278
    {
279 9128
        return $this->params['password'] ?? null;
280
    }
281
282
    /**
283
     * Gets the DBAL driver instance.
284
     *
285
     * @return Driver
286
     */
287 9216
    public function getDriver()
288
    {
289 9216
        return $this->_driver;
290
    }
291
292
    /**
293
     * Gets the Configuration used by the Connection.
294
     *
295
     * @return Configuration
296
     */
297 9738
    public function getConfiguration()
298
    {
299 9738
        return $this->_config;
300
    }
301
302
    /**
303
     * Gets the EventManager used by the Connection.
304
     *
305
     * @return EventManager
306
     */
307 9123
    public function getEventManager()
308
    {
309 9123
        return $this->_eventManager;
310
    }
311
312
    /**
313
     * Gets the DatabasePlatform for the connection.
314
     *
315
     * @return AbstractPlatform
316
     *
317
     * @throws DBALException
318
     */
319 9490
    public function getDatabasePlatform()
320
    {
321 9490
        if ($this->platform === null) {
322 9107
            $this->detectDatabasePlatform();
323
        }
324
325 9488
        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 9627
    public function connect()
345
    {
346 9627
        if ($this->isConnected) {
347 9330
            return false;
348
        }
349
350 9166
        $driverOptions = $this->params['driverOptions'] ?? [];
351 9166
        $user          = $this->params['user'] ?? null;
352 9166
        $password      = $this->params['password'] ?? null;
353
354 9166
        $this->_conn       = $this->_driver->connect($this->params, $user, $password, $driverOptions);
355 9158
        $this->isConnected = true;
356
357 9158
        if ($this->autoCommit === false) {
358 8807
            $this->beginTransaction();
359
        }
360
361 9158
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
362 9126
            $eventArgs = new Event\ConnectionEventArgs($this);
363 9126
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
364
        }
365
366 9158
        return true;
367
    }
368
369
    /**
370
     * Detects and sets the database platform.
371
     *
372
     * Evaluates custom platform class and version in order to set the correct platform.
373
     *
374
     * @throws DBALException If an invalid platform was specified for this connection.
375
     */
376 9107
    private function detectDatabasePlatform()
377
    {
378 9107
        $version = $this->getDatabasePlatformVersion();
379
380 9105
        if ($version !== null) {
381 8353
            assert($this->_driver instanceof VersionAwarePlatformDriver);
382
383 8353
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
384
        } else {
385 9103
            $this->platform = $this->_driver->getDatabasePlatform();
386
        }
387
388 9105
        $this->platform->setEventManager($this->_eventManager);
389 9105
    }
390
391
    /**
392
     * Returns the version of the related platform if applicable.
393
     *
394
     * Returns null if either the driver is not capable to create version
395
     * specific platform instances, no explicit server version was specified
396
     * or the underlying driver connection cannot determine the platform
397
     * version without having to query it (performance reasons).
398
     *
399
     * @return string|null
400
     *
401
     * @throws Exception
402
     */
403 9107
    private function getDatabasePlatformVersion()
404
    {
405
        // Driver does not support version specific platforms.
406 9107
        if (! $this->_driver instanceof VersionAwarePlatformDriver) {
407 9103
            return null;
408
        }
409
410
        // Explicit platform version requested (supersedes auto-detection).
411 8355
        if (isset($this->params['serverVersion'])) {
412
            return $this->params['serverVersion'];
413
        }
414
415
        // If not connected, we need to connect now to determine the platform version.
416 8355
        if ($this->_conn === null) {
417
            try {
418 8355
                $this->connect();
419 8253
            } catch (Throwable $originalException) {
420 8253
                if (empty($this->params['dbname'])) {
421
                    throw $originalException;
422
                }
423
424
                // The database to connect to might not yet exist.
425
                // Retry detection without database name connection parameter.
426 8253
                $databaseName           = $this->params['dbname'];
427 8253
                $this->params['dbname'] = null;
428
429
                try {
430 8253
                    $this->connect();
431 8253
                } catch (Throwable $fallbackException) {
432
                    // Either the platform does not support database-less connections
433
                    // or something else went wrong.
434
                    // Reset connection parameters and rethrow the original exception.
435 8253
                    $this->params['dbname'] = $databaseName;
436
437 8253
                    throw $originalException;
438
                }
439
440
                // Reset connection parameters.
441 6500
                $this->params['dbname'] = $databaseName;
442 6500
                $serverVersion          = $this->getServerVersion();
443
444
                // Close "temporary" connection to allow connecting to the real database again.
445 6500
                $this->close();
446
447 6500
                return $serverVersion;
448
            }
449
        }
450
451 8353
        return $this->getServerVersion();
452
    }
453
454
    /**
455
     * Returns the database server version if the underlying driver supports it.
456
     *
457
     * @return string|null
458
     */
459 8353
    private function getServerVersion()
460
    {
461 8353
        $connection = $this->getWrappedConnection();
462
463
        // Automatic platform version detection.
464 8353
        if ($connection instanceof ServerInfoAwareConnection && ! $connection->requiresQueryForServerVersion()) {
465 8353
            return $connection->getServerVersion();
466
        }
467
468
        // Unable to detect platform version.
469
        return null;
470
    }
471
472
    /**
473
     * Returns the current auto-commit mode for this connection.
474
     *
475
     * @see    setAutoCommit
476
     *
477
     * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
478
     */
479 8855
    public function isAutoCommit()
480
    {
481 8855
        return $this->autoCommit === true;
482
    }
483
484
    /**
485
     * Sets auto-commit mode for this connection.
486
     *
487
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
488
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
489
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
490
     *
491
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
492
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
493
     *
494
     * @see   isAutoCommit
495
     *
496
     * @param bool $autoCommit True to enable auto-commit mode; false to disable it.
497
     */
498 8836
    public function setAutoCommit($autoCommit)
499
    {
500 8836
        $autoCommit = (bool) $autoCommit;
501
502
        // Mode not changed, no-op.
503 8836
        if ($autoCommit === $this->autoCommit) {
504 8828
            return;
505
        }
506
507 8836
        $this->autoCommit = $autoCommit;
508
509
        // Commit all currently active transactions if any when switching auto-commit mode.
510 8836
        if ($this->isConnected !== true || $this->transactionNestingLevel === 0) {
511 8834
            return;
512
        }
513
514 8728
        $this->commitAll();
515 8728
    }
516
517
    /**
518
     * Sets the fetch mode.
519
     *
520
     * @param int $fetchMode
521
     *
522
     * @return void
523
     */
524 4106
    public function setFetchMode($fetchMode)
525
    {
526 4106
        $this->defaultFetchMode = $fetchMode;
527 4106
    }
528
529
    /**
530
     * Prepares and executes an SQL query and returns the first row of the result
531
     * as an associative array.
532
     *
533
     * @param string         $statement The SQL query.
534
     * @param mixed[]        $params    The query parameters.
535
     * @param int[]|string[] $types     The query parameter types.
536
     *
537
     * @return mixed[]|false False is returned if no rows are found.
538
     *
539
     * @throws DBALException
540
     */
541 8733
    public function fetchAssoc($statement, array $params = [], array $types = [])
542
    {
543 8733
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::ASSOCIATIVE);
544
    }
545
546
    /**
547
     * Prepares and executes an SQL query and returns the first row of the result
548
     * as a numerically indexed array.
549
     *
550
     * @param string         $statement The SQL query to be executed.
551
     * @param mixed[]        $params    The prepared statement params.
552
     * @param int[]|string[] $types     The query parameter types.
553
     *
554
     * @return mixed[]|false False is returned if no rows are found.
555
     */
556 8584
    public function fetchArray($statement, array $params = [], array $types = [])
557
    {
558 8584
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::NUMERIC);
559
    }
560
561
    /**
562
     * Prepares and executes an SQL query and returns the value of a single column
563
     * of the first row of the result.
564
     *
565
     * @param string         $statement The SQL query to be executed.
566
     * @param mixed[]        $params    The prepared statement params.
567
     * @param int            $column    The 0-indexed column number to retrieve.
568
     * @param int[]|string[] $types     The query parameter types.
569
     *
570
     * @return mixed|false False is returned if no rows are found.
571
     *
572
     * @throws DBALException
573
     */
574 8638
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
575
    {
576 8638
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
577
    }
578
579
    /**
580
     * Whether an actual connection to the database is established.
581
     *
582
     * @return bool
583
     */
584 9332
    public function isConnected()
585
    {
586 9332
        return $this->isConnected;
587
    }
588
589
    /**
590
     * Checks whether a transaction is currently active.
591
     *
592
     * @return bool TRUE if a transaction is currently active, FALSE otherwise.
593
     */
594 9836
    public function isTransactionActive()
595
    {
596 9836
        return $this->transactionNestingLevel > 0;
597
    }
598
599
    /**
600
     * Adds identifier condition to the query components
601
     *
602
     * @param mixed[]  $identifier Map of key columns to their values
603
     * @param string[] $columns    Column names
604
     * @param mixed[]  $values     Column values
605
     * @param string[] $conditions Key conditions
606
     *
607
     * @throws DBALException
608
     */
609 8705
    private function addIdentifierCondition(
610
        array $identifier,
611
        array &$columns,
612
        array &$values,
613
        array &$conditions
614
    ) : void {
615 8705
        $platform = $this->getDatabasePlatform();
616
617 8705
        foreach ($identifier as $columnName => $value) {
618 8705
            if ($value === null) {
619 8659
                $conditions[] = $platform->getIsNullExpression($columnName);
620 8659
                continue;
621
            }
622
623 8701
            $columns[]    = $columnName;
624 8701
            $values[]     = $value;
625 8701
            $conditions[] = $columnName . ' = ?';
626
        }
627 8705
    }
628
629
    /**
630
     * Executes an SQL DELETE statement on a table.
631
     *
632
     * Table expression and columns are not escaped and are not safe for user-input.
633
     *
634
     * @param string         $tableExpression The expression of the table on which to delete.
635
     * @param mixed[]        $identifier      The deletion criteria. An associative array containing column-value pairs.
636
     * @param int[]|string[] $types           The types of identifiers.
637
     *
638
     * @return int The number of affected rows.
639
     *
640
     * @throws DBALException
641
     * @throws InvalidArgumentException
642
     */
643 8640
    public function delete($tableExpression, array $identifier, array $types = [])
644
    {
645 8640
        if (empty($identifier)) {
646 8503
            throw InvalidArgumentException::fromEmptyCriteria();
647
        }
648
649 8638
        $columns = $values = $conditions = [];
650
651 8638
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
652
653 8638
        return $this->executeUpdate(
654 8638
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
655 12
            $values,
656 8638
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
657
        );
658
    }
659
660
    /**
661
     * Closes the connection.
662
     *
663
     * @return void
664
     */
665 8173
    public function close()
666
    {
667 8173
        $this->_conn = null;
668
669 8173
        $this->isConnected = false;
670 8173
    }
671
672
    /**
673
     * Sets the transaction isolation level.
674
     *
675
     * @param int $level The level to set.
676
     *
677
     * @return int
678
     */
679
    public function setTransactionIsolation($level)
680
    {
681
        $this->transactionIsolationLevel = $level;
682
683
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
684
    }
685
686
    /**
687
     * Gets the currently active transaction isolation level.
688
     *
689
     * @return int The current transaction isolation level.
690
     */
691
    public function getTransactionIsolation()
692
    {
693
        if ($this->transactionIsolationLevel === null) {
694
            $this->transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
695
        }
696
697
        return $this->transactionIsolationLevel;
698
    }
699
700
    /**
701
     * Executes an SQL UPDATE statement on a table.
702
     *
703
     * Table expression and columns are not escaped and are not safe for user-input.
704
     *
705
     * @param string         $tableExpression The expression of the table to update quoted or unquoted.
706
     * @param mixed[]        $data            An associative array containing column-value pairs.
707
     * @param mixed[]        $identifier      The update criteria. An associative array containing column-value pairs.
708
     * @param int[]|string[] $types           Types of the merged $data and $identifier arrays in that order.
709
     *
710
     * @return int The number of affected rows.
711
     *
712
     * @throws DBALException
713
     */
714 8693
    public function update($tableExpression, array $data, array $identifier, array $types = [])
715
    {
716 8693
        $columns = $values = $conditions = $set = [];
717
718 8693
        foreach ($data as $columnName => $value) {
719 8693
            $columns[] = $columnName;
720 8693
            $values[]  = $value;
721 8693
            $set[]     = $columnName . ' = ?';
722
        }
723
724 8693
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
725
726 8693
        if (is_string(key($types))) {
727 8686
            $types = $this->extractTypeValues($columns, $types);
728
        }
729
730 8693
        $sql = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
731 8693
                . ' WHERE ' . implode(' AND ', $conditions);
732
733 8693
        return $this->executeUpdate($sql, $values, $types);
734
    }
735
736
    /**
737
     * Inserts a table row with specified data.
738
     *
739
     * Table expression and columns are not escaped and are not safe for user-input.
740
     *
741
     * @param string         $tableExpression The expression of the table to insert data into, quoted or unquoted.
742
     * @param mixed[]        $data            An associative array containing column-value pairs.
743
     * @param int[]|string[] $types           Types of the inserted data.
744
     *
745
     * @return int The number of affected rows.
746
     *
747
     * @throws DBALException
748
     */
749 8854
    public function insert($tableExpression, array $data, array $types = [])
750
    {
751 8854
        if (empty($data)) {
752 8703
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' () VALUES ()');
753
        }
754
755 8602
        $columns = [];
756 8602
        $values  = [];
757 8602
        $set     = [];
758
759 8602
        foreach ($data as $columnName => $value) {
760 8602
            $columns[] = $columnName;
761 8602
            $values[]  = $value;
762 8602
            $set[]     = '?';
763
        }
764
765 8602
        return $this->executeUpdate(
766 8602
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
767 8602
            ' VALUES (' . implode(', ', $set) . ')',
768 151
            $values,
769 8602
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
770
        );
771
    }
772
773
    /**
774
     * Extract ordered type list from an ordered column list and type map.
775
     *
776
     * @param int[]|string[] $columnList
777
     * @param int[]|string[] $types
778
     *
779
     * @return int[]|string[]
780
     */
781 8694
    private function extractTypeValues(array $columnList, array $types)
782
    {
783 8694
        $typeValues = [];
784
785 8694
        foreach ($columnList as $columnIndex => $columnName) {
786 8694
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
787
        }
788
789 8694
        return $typeValues;
790
    }
791
792
    /**
793
     * Quotes a string so it can be safely used as a table or column name, even if
794
     * it is a reserved name.
795
     *
796
     * Delimiting style depends on the underlying database platform that is being used.
797
     *
798
     * NOTE: Just because you CAN use quoted identifiers does not mean
799
     * you SHOULD use them. In general, they end up causing way more
800
     * problems than they solve.
801
     *
802
     * @param string $str The name to be quoted.
803
     *
804
     * @return string The quoted name.
805
     */
806 6819
    public function quoteIdentifier($str)
807
    {
808 6819
        return $this->getDatabasePlatform()->quoteIdentifier($str);
809
    }
810
811
    /**
812
     * {@inheritDoc}
813
     */
814 7105
    public function quote($input, $type = null)
815
    {
816 7105
        $connection = $this->getWrappedConnection();
817
818 7105
        [$value, $bindingType] = $this->getBindingInfo($input, $type);
819
820 7105
        return $connection->quote($value, $bindingType);
821
    }
822
823
    /**
824
     * Prepares and executes an SQL query and returns the result as an associative array.
825
     *
826
     * @param string         $sql    The SQL query.
827
     * @param mixed[]        $params The query parameters.
828
     * @param int[]|string[] $types  The query parameter types.
829
     *
830
     * @return mixed[]
831
     */
832 8726
    public function fetchAll($sql, array $params = [], $types = [])
833
    {
834 8726
        return $this->executeQuery($sql, $params, $types)->fetchAll();
835
    }
836
837
    /**
838
     * Prepares an SQL statement.
839
     *
840
     * @param string $statement The SQL statement to prepare.
841
     *
842
     * @return DriverStatement The prepared statement.
843
     *
844
     * @throws DBALException
845
     */
846 9005
    public function prepare($statement)
847
    {
848
        try {
849 9005
            $stmt = new Statement($statement, $this);
850 8928
        } catch (Throwable $ex) {
851 8928
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
852
        }
853
854 8478
        $stmt->setFetchMode($this->defaultFetchMode);
855
856 8478
        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
     * @param int[]|string[]         $types  The types the previous parameters are in.
868
     * @param QueryCacheProfile|null $qcp    The query cache profile, optional.
869
     *
870
     * @return ResultStatement The executed statement.
871
     *
872
     * @throws DBALException
873
     */
874 9359
    public function executeQuery($query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
875
    {
876 9359
        if ($qcp !== null) {
877 4034
            return $this->executeCacheQuery($query, $params, $types, $qcp);
878
        }
879
880 9359
        $connection = $this->getWrappedConnection();
881
882 9359
        $logger = $this->_config->getSQLLogger();
883 9359
        if ($logger) {
884 7520
            $logger->startQuery($query, $params, $types);
885
        }
886
887
        try {
888 9359
            if ($params) {
889 6843
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
890
891 6843
                $stmt = $connection->prepare($query);
892 6843
                if ($types) {
893 6773
                    $this->_bindTypedValues($stmt, $params, $types);
894 6773
                    $stmt->execute();
895
                } else {
896 6843
                    $stmt->execute($params);
897
                }
898
            } else {
899 9351
                $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

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

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