Failed Conditions
Pull Request — master (#3174)
by José Carlos
13:41
created

Connection::executeUpdate()   C

Complexity

Conditions 7
Paths 44

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 7

Importance

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

935
                /** @scrutinizer ignore-call */ 
936
                $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...
936
            }
937 127
        } catch (Exception $ex) {
938 127
            if ($logger) {
939 108
                $logger->stopQuery($ex);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Logging\SQLLogger::stopQuery() has too many arguments starting with $ex. ( Ignorable by Annotation )

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

939
                $logger->/** @scrutinizer ignore-call */ 
940
                         stopQuery($ex);

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

1039
            /** @scrutinizer ignore-call */ 
1040
            $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...
1040 19
        } catch (Exception $ex) {
1041 19
            if ($logger) {
1042
                $logger->stopQuery($ex);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Logging\SQLLogger::stopQuery() has too many arguments starting with $ex. ( Ignorable by Annotation )

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

1042
                $logger->/** @scrutinizer ignore-call */ 
1043
                         stopQuery($ex);

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...
1043
            }
1044
1045 19
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1046
        }
1047
1048 259
        $statement->setFetchMode($this->defaultFetchMode);
1049
1050 259
        if ($logger) {
1051 259
            $logger->stopQuery();
1052
        }
1053
1054 259
        return $statement;
1055
    }
1056
1057
    /**
1058
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
1059
     * and returns the number of affected rows.
1060
     *
1061
     * This method supports PDO binding types as well as DBAL mapping types.
1062
     *
1063
     * @param string $query  The SQL query.
1064
     * @param array  $params The query parameters.
1065
     * @param array  $types  The parameter types.
1066
     *
1067
     * @return int The number of affected rows.
1068
     *
1069
     * @throws \Doctrine\DBAL\DBALException
1070
     */
1071 3451
    public function executeUpdate($query, array $params = [], array $types = [])
1072
    {
1073 3451
        $this->connect();
1074
1075 3451
        $logger = $this->_config->getSQLLogger();
1076 3451
        if ($logger) {
1077 3356
            $logger->startQuery($query, $params, $types);
1078
        }
1079
1080
        try {
1081 3451
            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...
1082 1736
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
1083
1084 1736
                $stmt = $this->_conn->prepare($query);
1085 1732
                if ($types) {
1086 250
                    $this->_bindTypedValues($stmt, $params, $types);
1087 250
                    $stmt->execute();
1088
                } else {
1089 1482
                    $stmt->execute($params);
1090
                }
1091 1704
                $result = $stmt->rowCount();
1092
            } else {
1093 3419
                $result = $this->_conn->exec($query);
1094
            }
1095 2226
        } catch (Exception $ex) {
1096 2226
            if ($logger) {
1097 2207
                $logger->stopQuery($ex);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Logging\SQLLogger::stopQuery() has too many arguments starting with $ex. ( Ignorable by Annotation )

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

1097
                $logger->/** @scrutinizer ignore-call */ 
1098
                         stopQuery($ex);

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...
1098
            }
1099
1100 2226
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
1101
        }
1102
1103 3169
        if ($logger) {
1104 3086
            $logger->stopQuery();
1105
        }
1106
1107 3169
        return $result;
1108
    }
1109
1110
    /**
1111
     * Executes an SQL statement and return the number of affected rows.
1112
     *
1113
     * @param string $statement
1114
     *
1115
     * @return int The number of affected rows.
1116
     *
1117
     * @throws \Doctrine\DBAL\DBALException
1118
     */
1119 1792
    public function exec($statement)
1120
    {
1121 1792
        $this->connect();
1122
1123 1788
        $logger = $this->_config->getSQLLogger();
1124 1788
        if ($logger) {
1125 1730
            $logger->startQuery($statement);
1126
        }
1127
1128
        try {
1129 1788
            $result = $this->_conn->exec($statement);
1130 1379
        } catch (Exception $ex) {
1131 1379
            if ($logger) {
1132 1359
                $logger->stopQuery($ex);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Logging\SQLLogger::stopQuery() has too many arguments starting with $ex. ( Ignorable by Annotation )

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

1132
                $logger->/** @scrutinizer ignore-call */ 
1133
                         stopQuery($ex);

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