Completed
Push — master ( 06d1d8...9e086f )
by Sergei
38s queued 16s
created

Connection::prepare()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Doctrine\DBAL;
4
5
use Closure;
6
use Doctrine\Common\EventManager;
7
use Doctrine\DBAL\Cache\ArrayStatement;
8
use Doctrine\DBAL\Cache\CacheException;
9
use Doctrine\DBAL\Cache\QueryCacheProfile;
10
use Doctrine\DBAL\Cache\ResultCacheStatement;
11
use Doctrine\DBAL\Driver\Connection as DriverConnection;
12
use Doctrine\DBAL\Driver\PingableConnection;
13
use Doctrine\DBAL\Driver\ResultStatement;
14
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
15
use Doctrine\DBAL\Driver\Statement as DriverStatement;
16
use Doctrine\DBAL\Exception\InvalidArgumentException;
17
use Doctrine\DBAL\Platforms\AbstractPlatform;
18
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
19
use Doctrine\DBAL\Query\QueryBuilder;
20
use Doctrine\DBAL\Schema\AbstractSchemaManager;
21
use Doctrine\DBAL\Types\Type;
22
use Exception;
23
use Throwable;
24
use function array_key_exists;
25
use function assert;
26
use function 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 10160
    public function __construct(
183
        array $params,
184
        Driver $driver,
185
        ?Configuration $config = null,
186
        ?EventManager $eventManager = null
187
    ) {
188 10160
        $this->_driver = $driver;
189 10160
        $this->params  = $params;
190
191 10160
        if (isset($params['pdo'])) {
192 9125
            $this->_conn       = $params['pdo'];
193 9125
            $this->isConnected = true;
194 9125
            unset($this->params['pdo']);
195
        }
196
197 10160
        if (isset($params['platform'])) {
198 9306
            if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
199 8851
                throw DBALException::invalidPlatformType($params['platform']);
200
            }
201
202 9304
            $this->platform = $params['platform'];
203
        }
204
205
        // Create default config and event manager if none given
206 10160
        if (! $config) {
207 9686
            $config = new Configuration();
208
        }
209
210 10160
        if (! $eventManager) {
211 9434
            $eventManager = new EventManager();
212
        }
213
214 10160
        $this->_config       = $config;
215 10160
        $this->_eventManager = $eventManager;
216
217 10160
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
218
219 10160
        $this->autoCommit = $config->getAutoCommit();
220 10160
    }
221
222
    /**
223
     * Gets the parameters used during instantiation.
224
     *
225
     * @return mixed[]
226
     */
227 9339
    public function getParams()
228
    {
229 9339
        return $this->params;
230
    }
231
232
    /**
233
     * Gets the name of the database this Connection is connected to.
234
     *
235
     * @return string
236
     */
237 8741
    public function getDatabase()
238
    {
239 8741
        return $this->_driver->getDatabase($this);
240
    }
241
242
    /**
243
     * Gets the hostname of the currently connected database.
244
     *
245
     * @return string|null
246
     */
247 9751
    public function getHost()
248
    {
249 9751
        return $this->params['host'] ?? null;
250
    }
251
252
    /**
253
     * Gets the port of the currently connected database.
254
     *
255
     * @return mixed
256
     */
257 9726
    public function getPort()
258
    {
259 9726
        return $this->params['port'] ?? null;
260
    }
261
262
    /**
263
     * Gets the username used by this connection.
264
     *
265
     * @return string|null
266
     */
267 9728
    public function getUsername()
268
    {
269 9728
        return $this->params['user'] ?? null;
270
    }
271
272
    /**
273
     * Gets the password used by this connection.
274
     *
275
     * @return string|null
276
     */
277 9701
    public function getPassword()
278
    {
279 9701
        return $this->params['password'] ?? null;
280
    }
281
282
    /**
283
     * Gets the DBAL driver instance.
284
     *
285
     * @return Driver
286
     */
287 9789
    public function getDriver()
288
    {
289 9789
        return $this->_driver;
290
    }
291
292
    /**
293
     * Gets the Configuration used by the Connection.
294
     *
295
     * @return Configuration
296
     */
297 10379
    public function getConfiguration()
298
    {
299 10379
        return $this->_config;
300
    }
301
302
    /**
303
     * Gets the EventManager used by the Connection.
304
     *
305
     * @return EventManager
306
     */
307 9698
    public function getEventManager()
308
    {
309 9698
        return $this->_eventManager;
310
    }
311
312
    /**
313
     * Gets the DatabasePlatform for the connection.
314
     *
315
     * @return AbstractPlatform
316
     *
317
     * @throws DBALException
318
     */
319 10063
    public function getDatabasePlatform()
320
    {
321 10063
        if ($this->platform === null) {
322 9682
            $this->detectDatabasePlatform();
323
        }
324
325 10061
        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 10268
    public function connect()
345
    {
346 10268
        if ($this->isConnected) {
347 9971
            return false;
348
        }
349
350 9739
        $driverOptions = $this->params['driverOptions'] ?? [];
351 9739
        $user          = $this->params['user'] ?? null;
352 9739
        $password      = $this->params['password'] ?? null;
353
354 9739
        $this->_conn       = $this->_driver->connect($this->params, $user, $password, $driverOptions);
355 9731
        $this->isConnected = true;
356
357 9731
        if ($this->autoCommit === false) {
358 9380
            $this->beginTransaction();
359
        }
360
361 9731
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
362 9699
            $eventArgs = new Event\ConnectionEventArgs($this);
363 9699
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
364
        }
365
366 9731
        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 9682
    private function detectDatabasePlatform()
377
    {
378 9682
        $version = $this->getDatabasePlatformVersion();
379
380 9680
        if ($version !== null) {
381 8926
            assert($this->_driver instanceof VersionAwarePlatformDriver);
382
383 8926
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
384
        } else {
385 9678
            $this->platform = $this->_driver->getDatabasePlatform();
386
        }
387
388 9680
        $this->platform->setEventManager($this->_eventManager);
389 9680
    }
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 9682
    private function getDatabasePlatformVersion()
404
    {
405
        // Driver does not support version specific platforms.
406 9682
        if (! $this->_driver instanceof VersionAwarePlatformDriver) {
407 9678
            return null;
408
        }
409
410
        // Explicit platform version requested (supersedes auto-detection).
411 8928
        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 8928
        if ($this->_conn === null) {
417
            try {
418 8928
                $this->connect();
419 8826
            } catch (Throwable $originalException) {
420 8826
                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 8826
                $databaseName           = $this->params['dbname'];
427 8826
                $this->params['dbname'] = null;
428
429
                try {
430 8826
                    $this->connect();
431 8826
                } 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 8826
                    $this->params['dbname'] = $databaseName;
436
437 8826
                    throw $originalException;
438
                }
439
440
                // Reset connection parameters.
441 7022
                $this->params['dbname'] = $databaseName;
442 7022
                $serverVersion          = $this->getServerVersion();
443
444
                // Close "temporary" connection to allow connecting to the real database again.
445 7022
                $this->close();
446
447 7022
                return $serverVersion;
448
            }
449
        }
450
451 8926
        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 8926
    private function getServerVersion()
460
    {
461 8926
        $connection = $this->getWrappedConnection();
462
463
        // Automatic platform version detection.
464 8926
        if ($connection instanceof ServerInfoAwareConnection && ! $connection->requiresQueryForServerVersion()) {
465 8926
            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 9428
    public function isAutoCommit()
480
    {
481 9428
        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 9409
    public function setAutoCommit($autoCommit)
499
    {
500 9409
        $autoCommit = (bool) $autoCommit;
501
502
        // Mode not changed, no-op.
503 9409
        if ($autoCommit === $this->autoCommit) {
504 9401
            return;
505
        }
506
507 9409
        $this->autoCommit = $autoCommit;
508
509
        // Commit all currently active transactions if any when switching auto-commit mode.
510 9409
        if ($this->isConnected !== true || $this->transactionNestingLevel === 0) {
511 9407
            return;
512
        }
513
514 9301
        $this->commitAll();
515 9301
    }
516
517
    /**
518
     * Sets the fetch mode.
519
     *
520
     * @param int $fetchMode
521
     *
522
     * @return void
523
     */
524 4670
    public function setFetchMode($fetchMode)
525
    {
526 4670
        $this->defaultFetchMode = $fetchMode;
527 4670
    }
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 9258
    public function fetchAssoc($statement, array $params = [], array $types = [])
542
    {
543 9258
        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 9157
    public function fetchArray($statement, array $params = [], array $types = [])
557
    {
558 9157
        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 9245
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
575
    {
576 9245
        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 9905
    public function isConnected()
585
    {
586 9905
        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 10477
    public function isTransactionActive()
595
    {
596 10477
        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 9278
    private function addIdentifierCondition(
610
        array $identifier,
611
        array &$columns,
612
        array &$values,
613
        array &$conditions
614
    ) : void {
615 9278
        $platform = $this->getDatabasePlatform();
616
617 9278
        foreach ($identifier as $columnName => $value) {
618 9278
            if ($value === null) {
619 9232
                $conditions[] = $platform->getIsNullExpression($columnName);
620 9232
                continue;
621
            }
622
623 9274
            $columns[]    = $columnName;
624 9274
            $values[]     = $value;
625 9274
            $conditions[] = $columnName . ' = ?';
626
        }
627 9278
    }
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 9213
    public function delete($tableExpression, array $identifier, array $types = [])
644
    {
645 9213
        if (empty($identifier)) {
646 9076
            throw InvalidArgumentException::fromEmptyCriteria();
647
        }
648
649 9211
        $columns = $values = $conditions = [];
650
651 9211
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
652
653 9211
        return $this->executeUpdate(
654 9211
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
655 12
            $values,
656 9211
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
657
        );
658
    }
659
660
    /**
661
     * Closes the connection.
662
     *
663
     * @return void
664
     */
665 8749
    public function close()
666
    {
667 8749
        $this->_conn = null;
668
669 8749
        $this->isConnected = false;
670 8749
    }
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 9266
    public function update($tableExpression, array $data, array $identifier, array $types = [])
715
    {
716 9266
        $columns = $values = $conditions = $set = [];
717
718 9266
        foreach ($data as $columnName => $value) {
719 9266
            $columns[] = $columnName;
720 9266
            $values[]  = $value;
721 9266
            $set[]     = $columnName . ' = ?';
722
        }
723
724 9266
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
725
726 9266
        if (is_string(key($types))) {
727 9259
            $types = $this->extractTypeValues($columns, $types);
728
        }
729
730 9266
        $sql = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
731 9266
                . ' WHERE ' . implode(' AND ', $conditions);
732
733 9266
        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 9429
    public function insert($tableExpression, array $data, array $types = [])
750
    {
751 9429
        if (empty($data)) {
752 9276
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' () VALUES ()');
753
        }
754
755 9177
        $columns = [];
756 9177
        $values  = [];
757 9177
        $set     = [];
758
759 9177
        foreach ($data as $columnName => $value) {
760 9177
            $columns[] = $columnName;
761 9177
            $values[]  = $value;
762 9177
            $set[]     = '?';
763
        }
764
765 9177
        return $this->executeUpdate(
766 9177
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
767 9177
            ' VALUES (' . implode(', ', $set) . ')',
768 153
            $values,
769 9177
            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 9267
    private function extractTypeValues(array $columnList, array $types)
782
    {
783 9267
        $typeValues = [];
784
785 9267
        foreach ($columnList as $columnIndex => $columnName) {
786 9267
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
787
        }
788
789 9267
        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 7368
    public function quoteIdentifier($str)
807
    {
808 7368
        return $this->getDatabasePlatform()->quoteIdentifier($str);
809
    }
810
811
    /**
812
     * {@inheritDoc}
813
     */
814 7678
    public function quote($input, $type = null)
815
    {
816 7678
        $connection = $this->getWrappedConnection();
817
818 7678
        [$value, $bindingType] = $this->getBindingInfo($input, $type);
819
820 7678
        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 9331
    public function fetchAll($sql, array $params = [], $types = [])
833
    {
834 9331
        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 9578
    public function prepare($statement)
847
    {
848
        try {
849 9578
            $stmt = new Statement($statement, $this);
850 9501
        } catch (Throwable $ex) {
851 9501
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
852
        }
853
854 9051
        $stmt->setFetchMode($this->defaultFetchMode);
855
856 9051
        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 9998
    public function executeQuery($query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
875
    {
876 9998
        if ($qcp !== null) {
877 4598
            return $this->executeCacheQuery($query, $params, $types, $qcp);
878
        }
879
880 9998
        $connection = $this->getWrappedConnection();
881
882 9998
        $logger = $this->_config->getSQLLogger();
883 9998
        if ($logger) {
884 8160
            $logger->startQuery($query, $params, $types);
885
        }
886
887
        try {
888 9998
            if ($params) {
889 7392
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
890
891 7392
                $stmt = $connection->prepare($query);
892 7392
                if ($types) {
893 7322
                    $this->_bindTypedValues($stmt, $params, $types);
894 7322
                    $stmt->execute();
895
                } else {
896 7392
                    $stmt->execute($params);
897
                }
898
            } else {
899 9990
                $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 9565
        } catch (Throwable $ex) {
902 9565
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
903
        }
904
905 8195
        $stmt->setFetchMode($this->defaultFetchMode);
906
907 8195
        if ($logger) {
908 8146
            $logger->stopQuery();
909
        }
910
911 8195
        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 8927
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
927
    {
928 8927
        $resultCache = $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
929
930 8927
        if ($resultCache === null) {
931
            throw CacheException::noResultDriverConfigured();
932
        }
933
934 8927
        $connectionParams = $this->getParams();
935 8927
        unset($connectionParams['platform']);
936
937 8927
        [$cacheKey, $realKey] = $qcp->generateCacheKeys($query, $params, $types, $connectionParams);
938
939
        // fetch the row pointers entry
940 8927
        $data = $resultCache->fetch($cacheKey);
941
942 8927
        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 8919
            if (isset($data[$realKey])) {
945 8919
                $stmt = new ArrayStatement($data[$realKey]);
946
            } elseif (array_key_exists($realKey, $data)) {
947
                $stmt = new ArrayStatement([]);
948
            }
949
        }
950
951 8927
        if (! isset($stmt)) {
952 4598
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
953
        }
954
955 8927
        $stmt->setFetchMode($this->defaultFetchMode);
956
957 8927
        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 9604
    public function query()
994
    {
995 9604
        $connection = $this->getWrappedConnection();
996
997 9604
        $args = func_get_args();
998
999 9604
        $logger = $this->_config->getSQLLogger();
1000 9604
        if ($logger) {
1001 8047
            $logger->startQuery($args[0]);
1002
        }
1003
1004
        try {
1005 9604
            $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 9576
        } catch (Throwable $ex) {
1007 9576
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1008
        }
1009
1010 8049
        $statement->setFetchMode($this->defaultFetchMode);
1011
1012 8049
        if ($logger) {
1013 8047
            $logger->stopQuery();
1014
        }
1015
1016 8049
        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 9878
    public function executeUpdate($query, array $params = [], array $types = [])
1034
    {
1035 9878
        $connection = $this->getWrappedConnection();
1036
1037 9878
        $logger = $this->_config->getSQLLogger();
1038 9878
        if ($logger) {
1039 8390
            $logger->startQuery($query, $params, $types);
1040
        }
1041
1042
        try {
1043 9878
            if ($params) {
1044 9216
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
1045
1046 9216
                $stmt = $connection->prepare($query);
1047
1048 9216
                if ($types) {
1049 8067
                    $this->_bindTypedValues($stmt, $params, $types);
1050 8067
                    $stmt->execute();
1051
                } else {
1052 9195
                    $stmt->execute($params);
1053
                }
1054 9212
                $result = $stmt->rowCount();
1055
            } else {
1056 9874
                $result = $connection->exec($query);
1057
            }
1058 9794
        } catch (Throwable $ex) {
1059 9794
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
1060
        }
1061
1062 9385
        if ($logger) {
1063 8372
            $logger->stopQuery();
1064
        }
1065
1066 9385
        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 9757
    public function exec($statement)
1079
    {
1080 9757
        $connection = $this->getWrappedConnection();
1081
1082 9751
        $logger = $this->_config->getSQLLogger();
1083 9751
        if ($logger) {
1084 6271
            $logger->startQuery($statement);
1085
        }
1086
1087
        try {
1088 9751
            $result = $connection->exec($statement);
1089 9709
        } catch (Throwable $ex) {
1090 9709
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
1091
        }
1092
1093 6169
        if ($logger) {
1094 6163
            $logger->stopQuery();
1095
        }
1096
1097 6169
        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 9329
    public function getTransactionNestingLevel()
1106
    {
1107 9329
        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 831
    public function lastInsertId($seqName = null)
1141
    {
1142 831
        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 7729
    public function transactional(Closure $func)
1161
    {
1162 7729
        $this->beginTransaction();
1163
        try {
1164 7729
            $res = $func($this);
1165 7675
            $this->commit();
1166
1167 7675
            return $res;
1168 7725
        } catch (Exception $e) {
1169 7723
            $this->rollBack();
1170 7723
            throw $e;
1171 7698
        } catch (Throwable $e) {
1172 7698
            $this->rollBack();
1173 7698
            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 7826
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
1187
    {
1188 7826
        if ($this->transactionNestingLevel > 0) {
1189 7566
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
1190
        }
1191
1192 7824
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1193 260
            throw ConnectionException::savepointsNotSupported();
1194
        }
1195
1196 7564
        $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1197 7564
    }
1198
1199
    /**
1200
     * Gets if nested transactions should use savepoints.
1201
     *
1202
     * @return bool
1203
     */
1204 7564
    public function getNestTransactionsWithSavepoints()
1205
    {
1206 7564
        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 7564
    protected function _getNestedTransactionSavePointName()
1216
    {
1217 7564
        return 'DOCTRINE2_SAVEPOINT_' . $this->transactionNestingLevel;
1218
    }
1219
1220
    /**
1221
     * {@inheritDoc}
1222
     */
1223 9414
    public function beginTransaction()
1224
    {
1225 9414
        $connection = $this->getWrappedConnection();
1226
1227 9414
        ++$this->transactionNestingLevel;
1228
1229 9414
        $logger = $this->_config->getSQLLogger();
1230
1231 9414
        if ($this->transactionNestingLevel === 1) {
1232 9414
            if ($logger) {
1233 7902
                $logger->startQuery('"START TRANSACTION"');
1234
            }
1235
1236 9414
            $connection->beginTransaction();
1237
1238 9414
            if ($logger) {
1239 9414
                $logger->stopQuery();
1240
            }
1241 9305
        } elseif ($this->nestTransactionsWithSavepoints) {
1242 7564
            if ($logger) {
1243 7564
                $logger->startQuery('"SAVEPOINT"');
1244
            }
1245 7564
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1246 7564
            if ($logger) {
1247 7564
                $logger->stopQuery();
1248
            }
1249
        }
1250
1251 9414
        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 9873
    public function commit()
1261
    {
1262 9873
        if ($this->transactionNestingLevel === 0) {
1263 9851
            throw ConnectionException::noActiveTransaction();
1264
        }
1265 9371
        if ($this->isRollbackOnly) {
1266 7876
            throw ConnectionException::commitFailedRollbackOnly();
1267
        }
1268
1269 9367
        $connection = $this->getWrappedConnection();
1270
1271 9367
        $logger = $this->_config->getSQLLogger();
1272
1273 9367
        if ($this->transactionNestingLevel === 1) {
1274 9367
            if ($logger) {
1275 7832
                $logger->startQuery('"COMMIT"');
1276
            }
1277
1278 9367
            $connection->commit();
1279
1280 9367
            if ($logger) {
1281 9367
                $logger->stopQuery();
1282
            }
1283 9303
        } elseif ($this->nestTransactionsWithSavepoints) {
1284 7564
            if ($logger) {
1285 7564
                $logger->startQuery('"RELEASE SAVEPOINT"');
1286
            }
1287 7564
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1288 7564
            if ($logger) {
1289 7564
                $logger->stopQuery();
1290
            }
1291
        }
1292
1293 9367
        --$this->transactionNestingLevel;
1294
1295 9367
        if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
1296 9315
            return true;
1297
        }
1298
1299 9353
        $this->beginTransaction();
1300
1301 9353
        return true;
1302
    }
1303
1304
    /**
1305
     * Commits all current nesting transactions.
1306
     */
1307 9301
    private function commitAll()
1308
    {
1309 9301
        while ($this->transactionNestingLevel !== 0) {
1310 9301
            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 9301
                $this->commit();
1314
1315 9301
                return;
1316
            }
1317
1318 9301
            $this->commit();
1319
        }
1320 9301
    }
1321
1322
    /**
1323
     * Cancels any database changes done during the current transaction.
1324
     *
1325
     * @throws ConnectionException If the rollback operation failed.
1326
     */
1327 9848
    public function rollBack()
1328
    {
1329 9848
        if ($this->transactionNestingLevel === 0) {
1330 9826
            throw ConnectionException::noActiveTransaction();
1331
        }
1332
1333 9346
        $connection = $this->getWrappedConnection();
1334
1335 9346
        $logger = $this->_config->getSQLLogger();
1336
1337 9346
        if ($this->transactionNestingLevel === 1) {
1338 9344
            if ($logger) {
1339 7890
                $logger->startQuery('"ROLLBACK"');
1340
            }
1341 9344
            $this->transactionNestingLevel = 0;
1342 9344
            $connection->rollBack();
1343 9344
            $this->isRollbackOnly = false;
1344 9344
            if ($logger) {
1345 7890
                $logger->stopQuery();
1346
            }
1347
1348 9344
            if ($this->autoCommit === false) {
1349 9344
                $this->beginTransaction();
1350
            }
1351 7851
        } elseif ($this->nestTransactionsWithSavepoints) {
1352 7564
            if ($logger) {
1353 7564
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1354
            }
1355 7564
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1356 7564
            --$this->transactionNestingLevel;
1357 7564
            if ($logger) {
1358 7564
                $logger->stopQuery();
1359
            }
1360
        } else {
1361 7849
            $this->isRollbackOnly = true;
1362 7849
            --$this->transactionNestingLevel;
1363
        }
1364 9346
    }
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 7823
    public function createSavepoint($savepoint)
1376
    {
1377 7823
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1378 259
            throw ConnectionException::savepointsNotSupported();
1379
        }
1380
1381 7564
        $this->getWrappedConnection()->exec($this->platform->createSavePoint($savepoint));
1382 7564
    }
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 7822
    public function releaseSavepoint($savepoint)
1394
    {
1395 7822
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1396 258
            throw ConnectionException::savepointsNotSupported();
1397
        }
1398
1399 7564
        if (! $this->platform->supportsReleaseSavepoints()) {
1400 563
            return;
1401
        }
1402
1403 7001
        $this->getWrappedConnection()->exec($this->platform->releaseSavePoint($savepoint));
1404 7001
    }
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 7822
    public function rollbackSavepoint($savepoint)
1416
    {
1417 7822
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1418 258
            throw ConnectionException::savepointsNotSupported();
1419
        }
1420
1421 7564
        $this->getWrappedConnection()->exec($this->platform->rollbackSavePoint($savepoint));
1422 7564
    }
1423
1424
    /**
1425
     * Gets the wrapped driver connection.
1426
     *
1427
     * @return DriverConnection
1428
     */
1429 10228
    public function getWrappedConnection()
1430
    {
1431 10228
        $this->connect();
1432
1433 10222
        return $this->_conn;
1434
    }
1435
1436
    /**
1437
     * Gets the SchemaManager that can be used to inspect or change the
1438
     * database schema through the connection.
1439
     *
1440
     * @return AbstractSchemaManager
1441
     */
1442 9076
    public function getSchemaManager()
1443
    {
1444 9076
        if ($this->_schemaManager === null) {
1445 8771
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1446
        }
1447
1448 9076
        return $this->_schemaManager;
1449
    }
1450
1451
    /**
1452
     * Marks the current transaction so that the only possible
1453
     * outcome for the transaction to be rolled back.
1454
     *
1455
     * @return void
1456
     *
1457
     * @throws ConnectionException If no transaction is active.
1458
     */
1459 9803
    public function setRollbackOnly()
1460
    {
1461 9803
        if ($this->transactionNestingLevel === 0) {
1462 9801
            throw ConnectionException::noActiveTransaction();
1463
        }
1464 7874
        $this->isRollbackOnly = true;
1465 7874
    }
1466
1467
    /**
1468
     * Checks whether the current transaction is marked for rollback only.
1469
     *
1470
     * @return bool
1471
     *
1472
     * @throws ConnectionException If no transaction is active.
1473
     */
1474 9805
    public function isRollbackOnly()
1475
    {
1476 9805
        if ($this->transactionNestingLevel === 0) {
1477 9801
            throw ConnectionException::noActiveTransaction();
1478
        }
1479
1480 7851
        return $this->isRollbackOnly;
1481
    }
1482
1483
    /**
1484
     * Converts a given value to its database representation according to the conversion
1485
     * rules of a specific DBAL mapping type.
1486
     *
1487
     * @param mixed  $value The value to convert.
1488
     * @param string $type  The name of the DBAL mapping type.
1489
     *
1490
     * @return mixed The converted value.
1491
     */
1492
    public function convertToDatabaseValue($value, $type)
1493
    {
1494
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1495
    }
1496
1497
    /**
1498
     * Converts a given value to its PHP representation according to the conversion
1499
     * rules of a specific DBAL mapping type.
1500
     *
1501
     * @param mixed  $value The value to convert.
1502
     * @param string $type  The name of the DBAL mapping type.
1503
     *
1504
     * @return mixed The converted type.
1505
     */
1506
    public function convertToPHPValue($value, $type)
1507
    {
1508
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1509
    }
1510
1511
    /**
1512
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1513
     * or DBAL mapping type, to a given statement.
1514
     *
1515
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
1516
     *           raw PDOStatement instances.
1517
     *
1518
     * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
1519
     * @param mixed[]                         $params The map/list of named/positional parameters.
1520
     * @param int[]|string[]                  $types  The parameter types (PDO binding types or DBAL mapping types).
1521
     *
1522
     * @return void
1523
     */
1524 8095
    private function _bindTypedValues($stmt, array $params, array $types)
1525
    {
1526
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1527 8095
        if (is_int(key($params))) {
1528
            // Positional parameters
1529 8095
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1530 8095
            $bindIndex  = 1;
1531 8095
            foreach ($params as $value) {
1532 8095
                $typeIndex = $bindIndex + $typeOffset;
1533 8095
                if (isset($types[$typeIndex])) {
1534 8095
                    $type                  = $types[$typeIndex];
1535 8095
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1536 8095
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1537
                } else {
1538 7949
                    $stmt->bindValue($bindIndex, $value);
1539
                }
1540 8095
                ++$bindIndex;
1541
            }
1542
        } else {
1543
            // Named parameters
1544
            foreach ($params as $name => $value) {
1545
                if (isset($types[$name])) {
1546
                    $type                  = $types[$name];
1547
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1548
                    $stmt->bindValue($name, $value, $bindingType);
1549
                } else {
1550
                    $stmt->bindValue($name, $value);
1551
                }
1552
            }
1553
        }
1554 8095
    }
1555
1556
    /**
1557
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1558
     *
1559
     * @param mixed           $value The value to bind.
1560
     * @param int|string|null $type  The type to bind (PDO or DBAL).
1561
     *
1562
     * @return mixed[] [0] => the (escaped) value, [1] => the binding type.
1563
     */
1564 8135
    private function getBindingInfo($value, $type)
1565
    {
1566 8135
        if (is_string($type)) {
1567 7645
            $type = Type::getType($type);
1568
        }
1569 8135
        if ($type instanceof Type) {
1570 7645
            $value       = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1571 7645
            $bindingType = $type->getBindingType();
1572
        } else {
1573 8123
            $bindingType = $type;
1574
        }
1575
1576 8135
        return [$value, $bindingType];
1577
    }
1578
1579
    /**
1580
     * Resolves the parameters to a format which can be displayed.
1581
     *
1582
     * @internal This is a purely internal method. If you rely on this method, you are advised to
1583
     *           copy/paste the code as this method may change, or be removed without prior notice.
1584
     *
1585
     * @param mixed[]        $params
1586
     * @param int[]|string[] $types
1587
     *
1588
     * @return mixed[]
1589
     */
1590 9835
    public function resolveParams(array $params, array $types)
1591
    {
1592 9835
        $resolvedParams = [];
1593
1594
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1595 9835
        if (is_int(key($params))) {
1596
            // Positional parameters
1597 6635
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1598 6635
            $bindIndex  = 1;
1599 6635
            foreach ($params as $value) {
1600 6635
                $typeIndex = $bindIndex + $typeOffset;
1601 6635
                if (isset($types[$typeIndex])) {
1602
                    $type                       = $types[$typeIndex];
1603
                    [$value]                    = $this->getBindingInfo($value, $type);
1604
                    $resolvedParams[$bindIndex] = $value;
1605
                } else {
1606 6635
                    $resolvedParams[$bindIndex] = $value;
1607
                }
1608 6635
                ++$bindIndex;
1609
            }
1610
        } else {
1611
            // Named parameters
1612 9819
            foreach ($params as $name => $value) {
1613
                if (isset($types[$name])) {
1614
                    $type                  = $types[$name];
1615
                    [$value]               = $this->getBindingInfo($value, $type);
1616
                    $resolvedParams[$name] = $value;
1617
                } else {
1618
                    $resolvedParams[$name] = $value;
1619
                }
1620
            }
1621
        }
1622
1623 9835
        return $resolvedParams;
1624
    }
1625
1626
    /**
1627
     * Creates a new instance of a SQL query builder.
1628
     *
1629
     * @return QueryBuilder
1630
     */
1631
    public function createQueryBuilder()
1632
    {
1633
        return new Query\QueryBuilder($this);
1634
    }
1635
1636
    /**
1637
     * Ping the server
1638
     *
1639
     * When the server is not available the method returns FALSE.
1640
     * It is responsibility of the developer to handle this case
1641
     * and abort the request or reconnect manually:
1642
     *
1643
     * @return bool
1644
     *
1645
     * @example
1646
     *
1647
     *   if ($conn->ping() === false) {
1648
     *      $conn->close();
1649
     *      $conn->connect();
1650
     *   }
1651
     *
1652
     * It is undefined if the underlying driver attempts to reconnect
1653
     * or disconnect when the connection is not available anymore
1654
     * as long it returns TRUE when a reconnect succeeded and
1655
     * FALSE when the connection was dropped.
1656
     */
1657 7600
    public function ping()
1658
    {
1659 7600
        $connection = $this->getWrappedConnection();
1660
1661 7600
        if ($connection instanceof PingableConnection) {
1662 2123
            return $connection->ping();
1663
        }
1664
1665
        try {
1666 7579
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1667
1668 7579
            return true;
1669
        } catch (DBALException $e) {
1670
            return false;
1671
        }
1672
    }
1673
}
1674