Failed Conditions
Push — fix/SQLAnywhere16Platform-exce... ( cea9c4 )
by Michael
24:04
created

Connection::setNestTransactionsWithSavepoints()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 5
cts 6
cp 0.8333
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 1
crap 3.0416
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 2294
    public function __construct(array $params, Driver $driver, Configuration $config = null,
217
            EventManager $eventManager = null)
218
    {
219 2294
        $this->_driver = $driver;
220 2294
        $this->_params = $params;
221
222 2294
        if (isset($params['pdo'])) {
223 204
            $this->_conn = $params['pdo'];
224 204
            $this->_isConnected = true;
225 204
            unset($this->_params['pdo']);
226
        }
227
228 2294
        if (isset($params["platform"])) {
229 476
            if ( ! $params['platform'] instanceof Platforms\AbstractPlatform) {
230 17
                throw DBALException::invalidPlatformType($params['platform']);
231
            }
232
233 459
            $this->platform = $params["platform"];
234 459
            unset($this->_params["platform"]);
235
        }
236
237
        // Create default config and event manager if none given
238 2294
        if ( ! $config) {
239 527
            $config = new Configuration();
240
        }
241
242 2294
        if ( ! $eventManager) {
243 527
            $eventManager = new EventManager();
244
        }
245
246 2294
        $this->_config = $config;
247 2294
        $this->_eventManager = $eventManager;
248
249 2294
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
250
251 2294
        $this->autoCommit = $config->getAutoCommit();
252 2294
    }
253
254
    /**
255
     * Gets the parameters used during instantiation.
256
     *
257
     * @return array
258
     */
259 2066
    public function getParams()
260
    {
261 2066
        return $this->_params;
262
    }
263
264
    /**
265
     * Gets the name of the database this Connection is connected to.
266
     *
267
     * @return string
268
     */
269 1069
    public function getDatabase()
270
    {
271 1069
        return $this->_driver->getDatabase($this);
272
    }
273
274
    /**
275
     * Gets the hostname of the currently connected database.
276
     *
277
     * @return string|null
278
     */
279 17
    public function getHost()
280
    {
281 17
        return $this->_params['host'] ?? null;
282
    }
283
284
    /**
285
     * Gets the port of the currently connected database.
286
     *
287
     * @return mixed
288
     */
289 17
    public function getPort()
290
    {
291 17
        return $this->_params['port'] ?? null;
292
    }
293
294
    /**
295
     * Gets the username used by this connection.
296
     *
297
     * @return string|null
298
     */
299 17
    public function getUsername()
300
    {
301 17
        return $this->_params['user'] ?? null;
302
    }
303
304
    /**
305
     * Gets the password used by this connection.
306
     *
307
     * @return string|null
308
     */
309 17
    public function getPassword()
310
    {
311 17
        return $this->_params['password'] ?? null;
312
    }
313
314
    /**
315
     * Gets the DBAL driver instance.
316
     *
317
     * @return \Doctrine\DBAL\Driver
318
     */
319 924
    public function getDriver()
320
    {
321 924
        return $this->_driver;
322
    }
323
324
    /**
325
     * Gets the Configuration used by the Connection.
326
     *
327
     * @return \Doctrine\DBAL\Configuration
328
     */
329 5011
    public function getConfiguration()
330
    {
331 5011
        return $this->_config;
332
    }
333
334
    /**
335
     * Gets the EventManager used by the Connection.
336
     *
337
     * @return \Doctrine\Common\EventManager
338
     */
339 236
    public function getEventManager()
340
    {
341 236
        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 4208
    public function getDatabasePlatform()
352
    {
353 4208
        if (null === $this->platform) {
354 634
            $this->detectDatabasePlatform();
355
        }
356
357 4191
        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 5117
    public function connect()
377
    {
378 5117
        if ($this->_isConnected) {
379 4777
            return false;
380
        }
381
382 798
        $driverOptions = $this->_params['driverOptions'] ?? [];
383 798
        $user = $this->_params['user'] ?? null;
384 798
        $password = $this->_params['password'] ?? null;
385
386 798
        $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
387 744
        $this->_isConnected = true;
388
389 744
        if (false === $this->autoCommit) {
390 51
            $this->beginTransaction();
391
        }
392
393 744
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
394 17
            $eventArgs = new Event\ConnectionEventArgs($this);
395 17
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
396
        }
397
398 744
        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 634
    private function detectDatabasePlatform()
409
    {
410 634
        $version = $this->getDatabasePlatformVersion();
411
412 581
        if (null !== $version) {
413 445
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
0 ignored issues
show
Bug introduced by
The method createDatabasePlatformForVersion() does not exist on Doctrine\DBAL\Driver. It seems like you code against a sub-type of Doctrine\DBAL\Driver such as Doctrine\DBAL\Driver\AbstractSQLAnywhereDriver or Doctrine\DBAL\Driver\AbstractPostgreSQLDriver or Doctrine\Tests\Mocks\Ver...AwarePlatformDriverMock or Doctrine\DBAL\Driver\AbstractMySQLDriver or Doctrine\DBAL\Driver\AbstractSQLServerDriver. ( Ignorable by Annotation )

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

413
            /** @scrutinizer ignore-call */ 
414
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
Loading history...
414
        } else {
415 136
            $this->platform = $this->_driver->getDatabasePlatform();
416
        }
417
418 581
        $this->platform->setEventManager($this->_eventManager);
419 581
    }
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 634
    private function getDatabasePlatformVersion()
434
    {
435
        // Driver does not support version specific platforms.
436 634
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
437 119
            return null;
438
        }
439
440
        // Explicit platform version requested (supersedes auto-detection).
441 515
        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 515
        if (null === $this->_conn) {
447
            try {
448 483
                $this->connect();
449 69
            } catch (\Exception $originalException) {
450 69
                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 69
                $databaseName = $this->_params['dbname'];
457 69
                $this->_params['dbname'] = null;
458
459
                try {
460 69
                    $this->connect();
461 53
                } 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 53
                    $this->_params['dbname'] = $databaseName;
466
467 53
                    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 462
        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 462
    private function getServerVersion()
491
    {
492
        // Automatic platform version detection.
493 462
        if ($this->_conn instanceof ServerInfoAwareConnection &&
494 462
            ! $this->_conn->requiresQueryForServerVersion()
495
        ) {
496 445
            return $this->_conn->getServerVersion();
497
        }
498
499
        // Unable to detect platform version.
500 17
        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 34
    public function isAutoCommit()
511
    {
512 34
        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 85
    public function setAutoCommit($autoCommit)
530
    {
531 85
        $autoCommit = (boolean) $autoCommit;
532
533
        // Mode not changed, no-op.
534 85
        if ($autoCommit === $this->autoCommit) {
535 17
            return;
536
        }
537
538 85
        $this->autoCommit = $autoCommit;
539
540
        // Commit all currently active transactions if any when switching auto-commit mode.
541 85
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
542 17
            $this->commitAll();
543
        }
544 85
    }
545
546
    /**
547
     * Sets the fetch mode.
548
     *
549
     * @param int $fetchMode
550
     *
551
     * @return void
552
     */
553 17
    public function setFetchMode($fetchMode)
554
    {
555 17
        $this->defaultFetchMode = $fetchMode;
556 17
    }
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 802
    public function fetchAssoc($statement, array $params = [], array $types = [])
571
    {
572 802
        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 64
    public function fetchArray($statement, array $params = [], array $types = [])
586
    {
587 64
        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 598
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
604
    {
605 598
        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 68
    public function isConnected()
614
    {
615 68
        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 4994
    public function isTransactionActive()
624
    {
625 4994
        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 248
    private function gatherConditions(array $identifiers)
639
    {
640 248
        $columns = [];
641 248
        $values = [];
642 248
        $conditions = [];
643
644 248
        foreach ($identifiers as $columnName => $value) {
645 248
            if (null === $value) {
646 68
                $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
647 68
                continue;
648
            }
649
650 214
            $columns[] = $columnName;
651 214
            $values[] = $value;
652 214
            $conditions[] = $columnName . ' = ?';
653
        }
654
655 248
        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 116
    public function delete($tableExpression, array $identifier, array $types = [])
673
    {
674 116
        if (empty($identifier)) {
675 17
            throw InvalidArgumentException::fromEmptyCriteria();
676
        }
677
678 99
        list($columns, $values, $conditions) = $this->gatherConditions($identifier);
679
680 99
        return $this->executeUpdate(
681 99
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
682 99
            $values,
683 99
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
684
        );
685
    }
686
687
    /**
688
     * Closes the connection.
689
     *
690
     * @return void
691
     */
692 425
    public function close()
693
    {
694 425
        $this->_conn = null;
695
696 425
        $this->_isConnected = false;
697 425
    }
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 149
    public function update($tableExpression, array $data, array $identifier, array $types = [])
742
    {
743 149
        $setColumns = [];
744 149
        $setValues = [];
745 149
        $set = [];
746
747 149
        foreach ($data as $columnName => $value) {
748 149
            $setColumns[] = $columnName;
749 149
            $setValues[] = $value;
750 149
            $set[] = $columnName . ' = ?';
751
        }
752
753 149
        list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
754 149
        $columns = array_merge($setColumns, $conditionColumns);
755 149
        $values = array_merge($setValues, $conditionValues);
756
757 149
        if (is_string(key($types))) {
758 85
            $types = $this->extractTypeValues($columns, $types);
759
        }
760
761 149
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
762 149
                . ' WHERE ' . implode(' AND ', $conditions);
763
764 149
        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 1463
    public function insert($tableExpression, array $data, array $types = [])
781
    {
782 1463
        if (empty($data)) {
783 17
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
784
        }
785
786 1446
        $columns = [];
787 1446
        $values = [];
788 1446
        $set = [];
789
790 1446
        foreach ($data as $columnName => $value) {
791 1446
            $columns[] = $columnName;
792 1446
            $values[] = $value;
793 1446
            $set[] = '?';
794
        }
795
796 1446
        return $this->executeUpdate(
797 1446
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
798 1446
            ' VALUES (' . implode(', ', $set) . ')',
799 1446
            $values,
800 1446
            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 153
    private function extractTypeValues(array $columnList, array $types)
813
    {
814 153
        $typeValues = [];
815
816 153
        foreach ($columnList as $columnIndex => $columnName) {
817 153
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
818
        }
819
820 153
        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 17
    public function quoteIdentifier($str)
838
    {
839 17
        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 76
    public function quote($input, $type = null)
851
    {
852 76
        $this->connect();
853
854 76
        list($value, $bindingType) = $this->getBindingInfo($input, $type);
855
856 76
        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 1759
    public function fetchAll($sql, array $params = [], $types = [])
869
    {
870 1759
        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 728
    public function prepare($statement)
883
    {
884
        try {
885 728
            $stmt = new Statement($statement, $this);
886 17
        } catch (Exception $ex) {
887 17
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
888
        }
889
890 711
        $stmt->setFetchMode($this->defaultFetchMode);
891
892 711
        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 3500
    public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null)
911
    {
912 3500
        if ($qcp !== null) {
913 170
            return $this->executeCacheQuery($query, $params, $types, $qcp);
914
        }
915
916 3500
        $this->connect();
917
918 3500
        $logger = $this->_config->getSQLLogger();
919 3500
        if ($logger) {
920 3391
            $logger->startQuery($query, $params, $types);
921
        }
922
923
        try {
924 3500
            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 613
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
926
927 613
                $stmt = $this->_conn->prepare($query);
928 613
                if ($types) {
929 272
                    $this->_bindTypedValues($stmt, $params, $types);
930 272
                    $stmt->execute();
931
                } else {
932 613
                    $stmt->execute($params);
933
                }
934
            } else {
935 3448
                $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 114
        } catch (Exception $ex) {
938 114
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
939
        }
940
941 3386
        $stmt->setFetchMode($this->defaultFetchMode);
942
943 3386
        if ($logger) {
944 3294
            $logger->stopQuery();
945
        }
946
947 3386
        return $stmt;
948
    }
949
950
    /**
951
     * Executes a caching query.
952
     *
953
     * @param string                                 $query  The SQL query to execute.
954
     * @param array                                  $params The parameters to bind to the query, if any.
955
     * @param array                                  $types  The types the previous parameters are in.
956
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
957
     *
958
     * @return \Doctrine\DBAL\Driver\ResultStatement
959
     *
960
     * @throws \Doctrine\DBAL\Cache\CacheException
961
     */
962 204
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
963
    {
964 204
        $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
965 204
        if ( ! $resultCache) {
966
            throw CacheException::noResultDriverConfigured();
967
        }
968
969 204
        list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types, $this->getParams());
970
971
        // fetch the row pointers entry
972 204
        if ($data = $resultCache->fetch($cacheKey)) {
973
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
974 153
            if (isset($data[$realKey])) {
975 153
                $stmt = new ArrayStatement($data[$realKey]);
976
            } elseif (array_key_exists($realKey, $data)) {
977
                $stmt = new ArrayStatement([]);
978
            }
979
        }
980
981 204
        if (!isset($stmt)) {
982 170
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
983
        }
984
985 204
        $stmt->setFetchMode($this->defaultFetchMode);
986
987 204
        return $stmt;
988
    }
989
990
    /**
991
     * Executes an, optionally parametrized, SQL query and returns the result,
992
     * applying a given projection/transformation function on each row of the result.
993
     *
994
     * @param string   $query    The SQL query to execute.
995
     * @param array    $params   The parameters, if any.
996
     * @param \Closure $function The transformation function that is applied on each row.
997
     *                           The function receives a single parameter, an array, that
998
     *                           represents a row of the result set.
999
     *
1000
     * @return array The projected result of the query.
1001
     */
1002
    public function project($query, array $params, Closure $function)
1003
    {
1004
        $result = [];
1005
        $stmt = $this->executeQuery($query, $params);
1006
1007
        while ($row = $stmt->fetch()) {
1008
            $result[] = $function($row);
1009
        }
1010
1011
        $stmt->closeCursor();
1012
1013
        return $result;
1014
    }
1015
1016
    /**
1017
     * Executes an SQL statement, returning a result set as a Statement object.
1018
     *
1019
     * @return \Doctrine\DBAL\Driver\Statement
1020
     *
1021
     * @throws \Doctrine\DBAL\DBALException
1022
     */
1023 255
    public function query()
1024
    {
1025 255
        $this->connect();
1026
1027 255
        $args = func_get_args();
1028
1029 255
        $logger = $this->_config->getSQLLogger();
1030 255
        if ($logger) {
1031 238
            $logger->startQuery($args[0]);
1032
        }
1033
1034
        try {
1035 255
            $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

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