Completed
Push — master ( 8575c2...a53269 )
by Marco
02:31 queued 01:15
created

Connection::gatherConditions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

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

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

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

Loading history...
911 31
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
912
913 31
                $stmt = $this->_conn->prepare($query);
914 31
                if ($types) {
915 15
                    $this->_bindTypedValues($stmt, $params, $types);
916 15
                    $stmt->execute();
917
                } else {
918 31
                    $stmt->execute($params);
919
                }
920
            } else {
921 180
                $stmt = $this->_conn->query($query);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Driver\Connection::query() has too many arguments starting with $query. ( Ignorable by Annotation )

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

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

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

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

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

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

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