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

Connection::query()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.0106

Importance

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

862
        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...
863
    }
864
865
    /**
866
     * Prepares and executes an SQL query and returns the result as an associative array.
867
     *
868
     * @param string $sql    The SQL query.
869
     * @param array  $params The query parameters.
870
     * @param array  $types  The query parameter types.
871
     *
872
     * @return array
873
     */
874 1924
    public function fetchAll($sql, array $params = [], $types = [])
875
    {
876 1924
        return $this->executeQuery($sql, $params, $types)->fetchAll();
877
    }
878
879
    /**
880
     * Prepares an SQL statement.
881
     *
882
     * @param string $statement The SQL statement to prepare.
883
     *
884
     * @return DriverStatement The prepared statement.
885
     *
886
     * @throws \Doctrine\DBAL\DBALException
887
     */
888 810
    public function prepare($statement)
889
    {
890
        try {
891 810
            $stmt = new Statement($statement, $this);
892 19
        } catch (Exception $ex) {
893 19
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
894
        }
895
896 791
        $stmt->setFetchMode($this->defaultFetchMode);
897
898 791
        return $stmt;
899
    }
900
901
    /**
902
     * Executes an, optionally parametrized, SQL query.
903
     *
904
     * If the query is parametrized, a prepared statement is used.
905
     * If an SQLLogger is configured, the execution is logged.
906
     *
907
     * @param string                                      $query  The SQL query to execute.
908
     * @param array                                       $params The parameters to bind to the query, if any.
909
     * @param array                                       $types  The types the previous parameters are in.
910
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
911
     *
912
     * @return ResultStatement The executed statement.
913
     *
914
     * @throws \Doctrine\DBAL\DBALException
915
     */
916 3869
    public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null)
917
    {
918 3869
        if ($qcp !== null) {
919 190
            return $this->executeCacheQuery($query, $params, $types, $qcp);
920
        }
921
922 3869
        $this->connect();
923
924 3869
        $logger = $this->_config->getSQLLogger();
925 3869
        if ($logger) {
926 3750
            $logger->startQuery($query, $params, $types);
927
        }
928
929
        try {
930 3869
            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...
931 687
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
932
933 687
                $stmt = $this->_conn->prepare($query);
934 687
                if ($types) {
935 304
                    $this->_bindTypedValues($stmt, $params, $types);
936 304
                    $stmt->execute();
937
                } else {
938 687
                    $stmt->execute($params);
939
                }
940
            } else {
941 3809
                $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

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

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