Failed Conditions
Pull Request — master (#3025)
by Michael
13:37
created

Connection::commitAll()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 5
nc 3
nop 0
crap 4
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL;
21
22
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
23
use Doctrine\DBAL\Exception\InvalidArgumentException;
24
use Closure;
25
use Exception;
26
use Doctrine\DBAL\Types\Type;
27
use Doctrine\DBAL\Driver\Connection as DriverConnection;
28
use Doctrine\DBAL\Driver\Statement as DriverStatement;
29
use Doctrine\Common\EventManager;
30
use Doctrine\DBAL\Cache\ResultCacheStatement;
31
use Doctrine\DBAL\Cache\QueryCacheProfile;
32
use Doctrine\DBAL\Cache\ArrayStatement;
33
use Doctrine\DBAL\Cache\CacheException;
34
use Doctrine\DBAL\Driver\PingableConnection;
35
use Throwable;
36
37
/**
38
 * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
39
 * events, transaction isolation levels, configuration, emulated transaction nesting,
40
 * lazy connecting and more.
41
 *
42
 * @link   www.doctrine-project.org
43
 * @since  2.0
44
 * @author Guilherme Blanco <[email protected]>
45
 * @author Jonathan Wage <[email protected]>
46
 * @author Roman Borschel <[email protected]>
47
 * @author Konsta Vesterinen <[email protected]>
48
 * @author Lukas Smith <[email protected]> (MDB2 library)
49
 * @author Benjamin Eberlei <[email protected]>
50
 */
51
class Connection implements DriverConnection
52
{
53
    /**
54
     * Constant for transaction isolation level READ UNCOMMITTED.
55
     *
56
     * @deprecated Use TransactionIsolationLevel::READ_UNCOMMITTED.
57
     */
58
    public const TRANSACTION_READ_UNCOMMITTED = TransactionIsolationLevel::READ_UNCOMMITTED;
59
60
    /**
61
     * Constant for transaction isolation level READ COMMITTED.
62
     *
63
     * @deprecated Use TransactionIsolationLevel::READ_COMMITTED.
64
     */
65
    public const TRANSACTION_READ_COMMITTED = TransactionIsolationLevel::READ_COMMITTED;
66
67
    /**
68
     * Constant for transaction isolation level REPEATABLE READ.
69
     *
70
     * @deprecated Use TransactionIsolationLevel::REPEATABLE_READ.
71
     */
72
    public const TRANSACTION_REPEATABLE_READ = TransactionIsolationLevel::REPEATABLE_READ;
73
74
    /**
75
     * Constant for transaction isolation level SERIALIZABLE.
76
     *
77
     * @deprecated Use TransactionIsolationLevel::SERIALIZABLE.
78
     */
79
    public const TRANSACTION_SERIALIZABLE = TransactionIsolationLevel::SERIALIZABLE;
80
81
    /**
82
     * Represents an array of ints to be expanded by Doctrine SQL parsing.
83
     *
84
     * @var int
85
     */
86
    public const PARAM_INT_ARRAY = ParameterType::INTEGER + self::ARRAY_PARAM_OFFSET;
87
88
    /**
89
     * Represents an array of strings to be expanded by Doctrine SQL parsing.
90
     *
91
     * @var int
92
     */
93
    public const PARAM_STR_ARRAY = ParameterType::STRING + self::ARRAY_PARAM_OFFSET;
94
95
    /**
96
     * Offset by which PARAM_* constants are detected as arrays of the param type.
97
     *
98
     * @var int
99
     */
100
    const ARRAY_PARAM_OFFSET = 100;
101
102
    /**
103
     * The wrapped driver connection.
104
     *
105
     * @var \Doctrine\DBAL\Driver\Connection|null
106
     */
107
    protected $_conn;
108
109
    /**
110
     * @var \Doctrine\DBAL\Configuration
111
     */
112
    protected $_config;
113
114
    /**
115
     * @var \Doctrine\Common\EventManager
116
     */
117
    protected $_eventManager;
118
119
    /**
120
     * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder
121
     */
122
    protected $_expr;
123
124
    /**
125
     * Whether or not a connection has been established.
126
     *
127
     * @var bool
128
     */
129
    private $_isConnected = false;
130
131
    /**
132
     * The current auto-commit mode of this connection.
133
     *
134
     * @var bool
135
     */
136
    private $autoCommit = true;
137
138
    /**
139
     * The transaction nesting level.
140
     *
141
     * @var int
142
     */
143
    private $_transactionNestingLevel = 0;
144
145
    /**
146
     * The currently active transaction isolation level.
147
     *
148
     * @var int
149
     */
150
    private $_transactionIsolationLevel;
151
152
    /**
153
     * If nested transactions should use savepoints.
154
     *
155
     * @var bool
156
     */
157
    private $_nestTransactionsWithSavepoints = false;
158
159
    /**
160
     * The parameters used during creation of the Connection instance.
161
     *
162
     * @var array
163
     */
164
    private $_params = [];
165
166
    /**
167
     * The DatabasePlatform object that provides information about the
168
     * database platform used by the connection.
169
     *
170
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
171
     */
172
    private $platform;
173
174
    /**
175
     * The schema manager.
176
     *
177
     * @var \Doctrine\DBAL\Schema\AbstractSchemaManager
178
     */
179
    protected $_schemaManager;
180
181
    /**
182
     * The used DBAL driver.
183
     *
184
     * @var \Doctrine\DBAL\Driver
185
     */
186
    protected $_driver;
187
188
    /**
189
     * Flag that indicates whether the current transaction is marked for rollback only.
190
     *
191
     * @var bool
192
     */
193
    private $_isRollbackOnly = false;
194
195
    /**
196
     * @var int
197
     */
198
    protected $defaultFetchMode = FetchMode::ASSOCIATIVE;
199
200
    /**
201
     * Initializes a new instance of the Connection class.
202
     *
203
     * @param array                              $params       The connection parameters.
204
     * @param \Doctrine\DBAL\Driver              $driver       The driver to use.
205
     * @param \Doctrine\DBAL\Configuration|null  $config       The configuration, optional.
206
     * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional.
207
     *
208
     * @throws \Doctrine\DBAL\DBALException
209
     */
210 131
    public function __construct(array $params, Driver $driver, Configuration $config = null,
211
            EventManager $eventManager = null)
212
    {
213 131
        $this->_driver = $driver;
214 131
        $this->_params = $params;
215
216 131
        if (isset($params['pdo'])) {
217 12
            $this->_conn = $params['pdo'];
218 12
            $this->_isConnected = true;
219 12
            unset($this->_params['pdo']);
220
        }
221
222 131
        if (isset($params["platform"])) {
223 28
            if ( ! $params['platform'] instanceof Platforms\AbstractPlatform) {
224 1
                throw DBALException::invalidPlatformType($params['platform']);
225
            }
226
227 27
            $this->platform = $params["platform"];
228 27
            unset($this->_params["platform"]);
229
        }
230
231
        // Create default config and event manager if none given
232 131
        if ( ! $config) {
233 31
            $config = new Configuration();
234
        }
235
236 131
        if ( ! $eventManager) {
237 31
            $eventManager = new EventManager();
238
        }
239
240 131
        $this->_config = $config;
241 131
        $this->_eventManager = $eventManager;
242
243 131
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
244
245 131
        $this->autoCommit = $config->getAutoCommit();
246 131
    }
247
248
    /**
249
     * Gets the parameters used during instantiation.
250
     *
251
     * @return array
252
     */
253 98
    public function getParams()
254
    {
255 98
        return $this->_params;
256
    }
257
258
    /**
259
     * Gets the name of the database this Connection is connected to.
260
     *
261
     * @return string
262
     */
263 49
    public function getDatabase()
264
    {
265 49
        return $this->_driver->getDatabase($this);
266
    }
267
268
    /**
269
     * Gets the hostname of the currently connected database.
270
     *
271
     * @return string|null
272
     */
273 1
    public function getHost()
274
    {
275 1
        return $this->_params['host'] ?? null;
276
    }
277
278
    /**
279
     * Gets the port of the currently connected database.
280
     *
281
     * @return mixed
282
     */
283 1
    public function getPort()
284
    {
285 1
        return $this->_params['port'] ?? null;
286
    }
287
288
    /**
289
     * Gets the username used by this connection.
290
     *
291
     * @return string|null
292
     */
293 1
    public function getUsername()
294
    {
295 1
        return $this->_params['user'] ?? null;
296
    }
297
298
    /**
299
     * Gets the password used by this connection.
300
     *
301
     * @return string|null
302
     */
303 1
    public function getPassword()
304
    {
305 1
        return $this->_params['password'] ?? null;
306
    }
307
308
    /**
309
     * Gets the DBAL driver instance.
310
     *
311
     * @return \Doctrine\DBAL\Driver
312
     */
313 51
    public function getDriver()
314
    {
315 51
        return $this->_driver;
316
    }
317
318
    /**
319
     * Gets the Configuration used by the Connection.
320
     *
321
     * @return \Doctrine\DBAL\Configuration
322
     */
323 264
    public function getConfiguration()
324
    {
325 264
        return $this->_config;
326
    }
327
328
    /**
329
     * Gets the EventManager used by the Connection.
330
     *
331
     * @return \Doctrine\Common\EventManager
332
     */
333 10
    public function getEventManager()
334
    {
335 10
        return $this->_eventManager;
336
    }
337
338
    /**
339
     * Gets the DatabasePlatform for the connection.
340
     *
341
     * @return \Doctrine\DBAL\Platforms\AbstractPlatform
342
     *
343
     * @throws \Doctrine\DBAL\DBALException
344
     */
345 209
    public function getDatabasePlatform()
346
    {
347 209
        if (null === $this->platform) {
348 27
            $this->detectDatabasePlatform();
349
        }
350
351 208
        return $this->platform;
352
    }
353
354
    /**
355
     * Gets the ExpressionBuilder for the connection.
356
     *
357
     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
358
     */
359
    public function getExpressionBuilder()
360
    {
361
        return $this->_expr;
362
    }
363
364
    /**
365
     * Establishes the connection with the database.
366
     *
367
     * @return bool TRUE if the connection was successfully established, FALSE if
368
     *              the connection is already open.
369
     */
370 270
    public function connect()
371
    {
372 270
        if ($this->_isConnected) {
373 253
            return false;
374
        }
375
376 46
        $driverOptions = $this->_params['driverOptions'] ?? [];
377 46
        $user = $this->_params['user'] ?? null;
378 46
        $password = $this->_params['password'] ?? null;
379
380 46
        $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
381 44
        $this->_isConnected = true;
382
383 44
        if (false === $this->autoCommit) {
384 3
            $this->beginTransaction();
385
        }
386
387 44
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
388 1
            $eventArgs = new Event\ConnectionEventArgs($this);
389 1
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
390
        }
391
392 44
        return true;
393
    }
394
395
    /**
396
     * Detects and sets the database platform.
397
     *
398
     * Evaluates custom platform class and version in order to set the correct platform.
399
     *
400
     * @throws DBALException if an invalid platform was specified for this connection.
401
     */
402 27
    private function detectDatabasePlatform()
403
    {
404 27
        $version = $this->getDatabasePlatformVersion();
405
406 26
        if ($version !== null) {
407 1
            assert($this->_driver instanceof VersionAwarePlatformDriver);
408
409 1
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
410
        } else {
411 25
            $this->platform = $this->_driver->getDatabasePlatform();
412
        }
413
414 26
        $this->platform->setEventManager($this->_eventManager);
415 26
    }
416
417
    /**
418
     * Returns the version of the related platform if applicable.
419
     *
420
     * Returns null if either the driver is not capable to create version
421
     * specific platform instances, no explicit server version was specified
422
     * or the underlying driver connection cannot determine the platform
423
     * version without having to query it (performance reasons).
424
     *
425
     * @return string|null
426
     *
427
     * @throws Exception
428
     */
429 27
    private function getDatabasePlatformVersion()
430
    {
431
        // Driver does not support version specific platforms.
432 27
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
433 24
            return null;
434
        }
435
436
        // Explicit platform version requested (supersedes auto-detection).
437 3
        if (isset($this->_params['serverVersion'])) {
438
            return $this->_params['serverVersion'];
439
        }
440
441
        // If not connected, we need to connect now to determine the platform version.
442 3
        if (null === $this->_conn) {
443
            try {
444 3
                $this->connect();
445 1
            } catch (\Exception $originalException) {
446 1
                if (empty($this->_params['dbname'])) {
447
                    throw $originalException;
448
                }
449
450
                // The database to connect to might not yet exist.
451
                // Retry detection without database name connection parameter.
452 1
                $databaseName = $this->_params['dbname'];
453 1
                $this->_params['dbname'] = null;
454
455
                try {
456 1
                    $this->connect();
457 1
                } catch (\Exception $fallbackException) {
458
                    // Either the platform does not support database-less connections
459
                    // or something else went wrong.
460
                    // Reset connection parameters and rethrow the original exception.
461 1
                    $this->_params['dbname'] = $databaseName;
462
463 1
                    throw $originalException;
464
                }
465
466
                // Reset connection parameters.
467
                $this->_params['dbname'] = $databaseName;
468
                $serverVersion = $this->getServerVersion();
469
470
                // Close "temporary" connection to allow connecting to the real database again.
471
                $this->close();
472
473
                return $serverVersion;
474
            }
475
476
        }
477
478 2
        return $this->getServerVersion();
479
    }
480
481
    /**
482
     * Returns the database server version if the underlying driver supports it.
483
     *
484
     * @return string|null
485
     */
486 2
    private function getServerVersion()
487
    {
488
        // Automatic platform version detection.
489 2
        if ($this->_conn instanceof ServerInfoAwareConnection &&
490 2
            ! $this->_conn->requiresQueryForServerVersion()
491
        ) {
492 1
            return $this->_conn->getServerVersion();
493
        }
494
495
        // Unable to detect platform version.
496 1
        return null;
497
    }
498
499
    /**
500
     * Returns the current auto-commit mode for this connection.
501
     *
502
     * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
503
     *
504
     * @see    setAutoCommit
505
     */
506 2
    public function isAutoCommit()
507
    {
508 2
        return true === $this->autoCommit;
509
    }
510
511
    /**
512
     * Sets auto-commit mode for this connection.
513
     *
514
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
515
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
516
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
517
     *
518
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
519
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
520
     *
521
     * @param bool $autoCommit True to enable auto-commit mode; false to disable it.
522
     *
523
     * @see   isAutoCommit
524
     */
525 5
    public function setAutoCommit($autoCommit)
526
    {
527 5
        $autoCommit = (boolean) $autoCommit;
528
529
        // Mode not changed, no-op.
530 5
        if ($autoCommit === $this->autoCommit) {
531 1
            return;
532
        }
533
534 5
        $this->autoCommit = $autoCommit;
535
536
        // Commit all currently active transactions if any when switching auto-commit mode.
537 5
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
538 1
            $this->commitAll();
539
        }
540 5
    }
541
542
    /**
543
     * Sets the fetch mode.
544
     *
545
     * @param int $fetchMode
546
     *
547
     * @return void
548
     */
549 1
    public function setFetchMode($fetchMode)
550
    {
551 1
        $this->defaultFetchMode = $fetchMode;
552 1
    }
553
554
    /**
555
     * Prepares and executes an SQL query and returns the first row of the result
556
     * as an associative array.
557
     *
558
     * @param string $statement The SQL query.
559
     * @param array  $params    The query parameters.
560
     * @param array  $types     The query parameter types.
561
     *
562
     * @return array|bool False is returned if no rows are found.
563
     *
564
     * @throws \Doctrine\DBAL\DBALException
565
     */
566 42
    public function fetchAssoc($statement, array $params = [], array $types = [])
567
    {
568 42
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::ASSOCIATIVE);
569
    }
570
571
    /**
572
     * Prepares and executes an SQL query and returns the first row of the result
573
     * as a numerically indexed array.
574
     *
575
     * @param string $statement The SQL query to be executed.
576
     * @param array  $params    The prepared statement params.
577
     * @param array  $types     The query parameter types.
578
     *
579
     * @return array|bool False is returned if no rows are found.
580
     */
581 4
    public function fetchArray($statement, array $params = [], array $types = [])
582
    {
583 4
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::NUMERIC);
584
    }
585
586
    /**
587
     * Prepares and executes an SQL query and returns the value of a single column
588
     * of the first row of the result.
589
     *
590
     * @param string $statement The SQL query to be executed.
591
     * @param array  $params    The prepared statement params.
592
     * @param int    $column    The 0-indexed column number to retrieve.
593
     * @param array  $types     The query parameter types.
594
     *
595
     * @return mixed|bool False is returned if no rows are found.
596
     *
597
     * @throws \Doctrine\DBAL\DBALException
598
     */
599 28
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
600
    {
601 28
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
602
    }
603
604
    /**
605
     * Whether an actual connection to the database is established.
606
     *
607
     * @return bool
608
     */
609 4
    public function isConnected()
610
    {
611 4
        return $this->_isConnected;
612
    }
613
614
    /**
615
     * Checks whether a transaction is currently active.
616
     *
617
     * @return bool TRUE if a transaction is currently active, FALSE otherwise.
618
     */
619 263
    public function isTransactionActive()
620
    {
621 263
        return $this->_transactionNestingLevel > 0;
622
    }
623
624
    /**
625
     * Gathers conditions for an update or delete call.
626
     *
627
     * @param array $identifiers Input array of columns to values
628
     *
629
     * @return string[][] a triplet with:
630
     *                    - the first key being the columns
631
     *                    - the second key being the values
632
     *                    - the third key being the conditions
633
     */
634 13
    private function gatherConditions(array $identifiers)
635
    {
636 13
        $columns = [];
637 13
        $values = [];
638 13
        $conditions = [];
639
640 13
        foreach ($identifiers as $columnName => $value) {
641 13
            if (null === $value) {
642 4
                $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
643 4
                continue;
644
            }
645
646 11
            $columns[] = $columnName;
647 11
            $values[] = $value;
648 11
            $conditions[] = $columnName . ' = ?';
649
        }
650
651 13
        return [$columns, $values, $conditions];
652
    }
653
654
    /**
655
     * Executes an SQL DELETE statement on a table.
656
     *
657
     * Table expression and columns are not escaped and are not safe for user-input.
658
     *
659
     * @param string $tableExpression The expression of the table on which to delete.
660
     * @param array  $identifier      The deletion criteria. An associative array containing column-value pairs.
661
     * @param array  $types           The types of identifiers.
662
     *
663
     * @return int The number of affected rows.
664
     *
665
     * @throws \Doctrine\DBAL\DBALException
666
     * @throws InvalidArgumentException
667
     */
668 6
    public function delete($tableExpression, array $identifier, array $types = [])
669
    {
670 6
        if (empty($identifier)) {
671 1
            throw InvalidArgumentException::fromEmptyCriteria();
672
        }
673
674 5
        list($columns, $values, $conditions) = $this->gatherConditions($identifier);
675
676 5
        return $this->executeUpdate(
677 5
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
678 5
            $values,
679 5
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
680
        );
681
    }
682
683
    /**
684
     * Closes the connection.
685
     *
686
     * @return void
687
     */
688 28
    public function close()
689
    {
690 28
        $this->_conn = null;
691
692 28
        $this->_isConnected = false;
693 28
    }
694
695
    /**
696
     * Sets the transaction isolation level.
697
     *
698
     * @param int $level The level to set.
699
     *
700
     * @return int
701
     */
702
    public function setTransactionIsolation($level)
703
    {
704
        $this->_transactionIsolationLevel = $level;
705
706
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
707
    }
708
709
    /**
710
     * Gets the currently active transaction isolation level.
711
     *
712
     * @return int The current transaction isolation level.
713
     */
714
    public function getTransactionIsolation()
715
    {
716
        if (null === $this->_transactionIsolationLevel) {
0 ignored issues
show
introduced by
The condition null === $this->_transactionIsolationLevel can never be true.
Loading history...
717
            $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
718
        }
719
720
        return $this->_transactionIsolationLevel;
721
    }
722
723
    /**
724
     * Executes an SQL UPDATE statement on a table.
725
     *
726
     * Table expression and columns are not escaped and are not safe for user-input.
727
     *
728
     * @param string $tableExpression The expression of the table to update quoted or unquoted.
729
     * @param array  $data            An associative array containing column-value pairs.
730
     * @param array  $identifier      The update criteria. An associative array containing column-value pairs.
731
     * @param array  $types           Types of the merged $data and $identifier arrays in that order.
732
     *
733
     * @return int The number of affected rows.
734
     *
735
     * @throws \Doctrine\DBAL\DBALException
736
     */
737 8
    public function update($tableExpression, array $data, array $identifier, array $types = [])
738
    {
739 8
        $setColumns = [];
740 8
        $setValues = [];
741 8
        $set = [];
742
743 8
        foreach ($data as $columnName => $value) {
744 8
            $setColumns[] = $columnName;
745 8
            $setValues[] = $value;
746 8
            $set[] = $columnName . ' = ?';
747
        }
748
749 8
        list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
750 8
        $columns = array_merge($setColumns, $conditionColumns);
751 8
        $values = array_merge($setValues, $conditionValues);
752
753 8
        if (is_string(key($types))) {
754 5
            $types = $this->extractTypeValues($columns, $types);
755
        }
756
757 8
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
758 8
                . ' WHERE ' . implode(' AND ', $conditions);
759
760 8
        return $this->executeUpdate($sql, $values, $types);
761
    }
762
763
    /**
764
     * Inserts a table row with specified data.
765
     *
766
     * Table expression and columns are not escaped and are not safe for user-input.
767
     *
768
     * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
769
     * @param array  $data            An associative array containing column-value pairs.
770
     * @param array  $types           Types of the inserted data.
771
     *
772
     * @return int The number of affected rows.
773
     *
774
     * @throws \Doctrine\DBAL\DBALException
775
     */
776 78
    public function insert($tableExpression, array $data, array $types = [])
777
    {
778 78
        if (empty($data)) {
779 1
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
780
        }
781
782 77
        $columns = [];
783 77
        $values = [];
784 77
        $set = [];
785
786 77
        foreach ($data as $columnName => $value) {
787 77
            $columns[] = $columnName;
788 77
            $values[] = $value;
789 77
            $set[] = '?';
790
        }
791
792 77
        return $this->executeUpdate(
793 77
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
794 77
            ' VALUES (' . implode(', ', $set) . ')',
795 77
            $values,
796 77
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
797
        );
798
    }
799
800
    /**
801
     * Extract ordered type list from an ordered column list and type map.
802
     *
803
     * @param array $columnList
804
     * @param array $types
805
     *
806
     * @return array
807
     */
808 9
    private function extractTypeValues(array $columnList, array $types)
809
    {
810 9
        $typeValues = [];
811
812 9
        foreach ($columnList as $columnIndex => $columnName) {
813 9
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
814
        }
815
816 9
        return $typeValues;
817
    }
818
819
    /**
820
     * Quotes a string so it can be safely used as a table or column name, even if
821
     * it is a reserved name.
822
     *
823
     * Delimiting style depends on the underlying database platform that is being used.
824
     *
825
     * NOTE: Just because you CAN use quoted identifiers does not mean
826
     * you SHOULD use them. In general, they end up causing way more
827
     * problems than they solve.
828
     *
829
     * @param string $str The name to be quoted.
830
     *
831
     * @return string The quoted name.
832
     */
833 1
    public function quoteIdentifier($str)
834
    {
835 1
        return $this->getDatabasePlatform()->quoteIdentifier($str);
836
    }
837
838
    /**
839
     * Quotes a given input parameter.
840
     *
841
     * @param mixed    $input The parameter to be quoted.
842
     * @param int|null $type  The type of the parameter.
843
     *
844
     * @return string The quoted parameter.
845
     */
846 4
    public function quote($input, $type = null)
847
    {
848 4
        $this->connect();
849
850 4
        list($value, $bindingType) = $this->getBindingInfo($input, $type);
851
852 4
        return $this->_conn->quote($value, $bindingType);
0 ignored issues
show
Bug introduced by
The method quote() does not exist on null. ( Ignorable by Annotation )

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

852
        return $this->_conn->/** @scrutinizer ignore-call */ quote($value, $bindingType);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
853
    }
854
855
    /**
856
     * Prepares and executes an SQL query and returns the result as an associative array.
857
     *
858
     * @param string $sql    The SQL query.
859
     * @param array  $params The query parameters.
860
     * @param array  $types  The query parameter types.
861
     *
862
     * @return array
863
     */
864 87
    public function fetchAll($sql, array $params = [], $types = [])
865
    {
866 87
        return $this->executeQuery($sql, $params, $types)->fetchAll();
867
    }
868
869
    /**
870
     * Prepares an SQL statement.
871
     *
872
     * @param string $statement The SQL statement to prepare.
873
     *
874
     * @return DriverStatement The prepared statement.
875
     *
876
     * @throws \Doctrine\DBAL\DBALException
877
     */
878 41
    public function prepare($statement)
879
    {
880
        try {
881 41
            $stmt = new Statement($statement, $this);
882 1
        } catch (Exception $ex) {
883 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
884
        }
885
886 40
        $stmt->setFetchMode($this->defaultFetchMode);
887
888 40
        return $stmt;
889
    }
890
891
    /**
892
     * Executes an, optionally parametrized, SQL query.
893
     *
894
     * If the query is parametrized, a prepared statement is used.
895
     * If an SQLLogger is configured, the execution is logged.
896
     *
897
     * @param string                                      $query  The SQL query to execute.
898
     * @param array                                       $params The parameters to bind to the query, if any.
899
     * @param array                                       $types  The types the previous parameters are in.
900
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
901
     *
902
     * @return \Doctrine\DBAL\Driver\ResultStatement The executed statement.
903
     *
904
     * @throws \Doctrine\DBAL\DBALException
905
     */
906 184
    public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null)
907
    {
908 184
        if ($qcp !== null) {
909 10
            return $this->executeCacheQuery($query, $params, $types, $qcp);
910
        }
911
912 184
        $this->connect();
913
914 184
        $logger = $this->_config->getSQLLogger();
915 184
        if ($logger) {
916 179
            $logger->startQuery($query, $params, $types);
917
        }
918
919
        try {
920 184
            if ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
921 31
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
922
923 31
                $stmt = $this->_conn->prepare($query);
924 31
                if ($types) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
925 15
                    $this->_bindTypedValues($stmt, $params, $types);
926 15
                    $stmt->execute();
927
                } else {
928 31
                    $stmt->execute($params);
929
                }
930
            } else {
931 180
                $stmt = $this->_conn->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

931
                /** @scrutinizer ignore-call */ 
932
                $stmt = $this->_conn->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...
932
            }
933 8
        } catch (Exception $ex) {
934 8
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
935
        }
936
937 176
        $stmt->setFetchMode($this->defaultFetchMode);
938
939 176
        if ($logger) {
940 172
            $logger->stopQuery();
941
        }
942
943 176
        return $stmt;
944
    }
945
946
    /**
947
     * Executes a caching query.
948
     *
949
     * @param string                                 $query  The SQL query to execute.
950
     * @param array                                  $params The parameters to bind to the query, if any.
951
     * @param array                                  $types  The types the previous parameters are in.
952
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
953
     *
954
     * @return \Doctrine\DBAL\Driver\ResultStatement
955
     *
956
     * @throws \Doctrine\DBAL\Cache\CacheException
957
     */
958 12
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
959
    {
960 12
        $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
961 12
        if ( ! $resultCache) {
962
            throw CacheException::noResultDriverConfigured();
963
        }
964
965 12
        list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types, $this->getParams());
966
967
        // fetch the row pointers entry
968 12
        if ($data = $resultCache->fetch($cacheKey)) {
969
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
970 9
            if (isset($data[$realKey])) {
971 9
                $stmt = new ArrayStatement($data[$realKey]);
972
            } elseif (array_key_exists($realKey, $data)) {
973
                $stmt = new ArrayStatement([]);
974
            }
975
        }
976
977 12
        if (!isset($stmt)) {
978 10
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
979
        }
980
981 12
        $stmt->setFetchMode($this->defaultFetchMode);
982
983 12
        return $stmt;
984
    }
985
986
    /**
987
     * Executes an, optionally parametrized, SQL query and returns the result,
988
     * applying a given projection/transformation function on each row of the result.
989
     *
990
     * @param string   $query    The SQL query to execute.
991
     * @param array    $params   The parameters, if any.
992
     * @param \Closure $function The transformation function that is applied on each row.
993
     *                           The function receives a single parameter, an array, that
994
     *                           represents a row of the result set.
995
     *
996
     * @return array The projected result of the query.
997
     */
998
    public function project($query, array $params, Closure $function)
999
    {
1000
        $result = [];
1001
        $stmt = $this->executeQuery($query, $params);
1002
1003
        while ($row = $stmt->fetch()) {
1004
            $result[] = $function($row);
1005
        }
1006
1007
        $stmt->closeCursor();
1008
1009
        return $result;
1010
    }
1011
1012
    /**
1013
     * Executes an SQL statement, returning a result set as a Statement object.
1014
     *
1015
     * @return \Doctrine\DBAL\Driver\Statement
1016
     *
1017
     * @throws \Doctrine\DBAL\DBALException
1018
     */
1019 8
    public function query()
1020
    {
1021 8
        $this->connect();
1022
1023 8
        $args = func_get_args();
1024
1025 8
        $logger = $this->_config->getSQLLogger();
1026 8
        if ($logger) {
1027 7
            $logger->startQuery($args[0]);
1028
        }
1029
1030
        try {
1031 8
            $statement = $this->_conn->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

1031
            /** @scrutinizer ignore-call */ 
1032
            $statement = $this->_conn->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...
1032 1
        } catch (Exception $ex) {
1033 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1034
        }
1035
1036 7
        $statement->setFetchMode($this->defaultFetchMode);
1037
1038 7
        if ($logger) {
1039 7
            $logger->stopQuery();
1040
        }
1041
1042 7
        return $statement;
1043
    }
1044
1045
    /**
1046
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
1047
     * and returns the number of affected rows.
1048
     *
1049
     * This method supports PDO binding types as well as DBAL mapping types.
1050
     *
1051
     * @param string $query  The SQL query.
1052
     * @param array  $params The query parameters.
1053
     * @param array  $types  The parameter types.
1054
     *
1055
     * @return int The number of affected rows.
1056
     *
1057
     * @throws \Doctrine\DBAL\DBALException
1058
     */
1059 142
    public function executeUpdate($query, array $params = [], array $types = [])
1060
    {
1061 142
        $this->connect();
1062
1063 142
        $logger = $this->_config->getSQLLogger();
1064 142
        if ($logger) {
1065 137
            $logger->startQuery($query, $params, $types);
1066
        }
1067
1068
        try {
1069 142
            if ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1070 83
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
1071
1072 83
                $stmt = $this->_conn->prepare($query);
1073 82
                if ($types) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

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