Failed Conditions
Push — master ( edfbda...298c91 )
by Luís
16s
created

lib/Doctrine/DBAL/Connection.php (6 issues)

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 131
    public function __construct(array $params, Driver $driver, Configuration $config = null,
203
            EventManager $eventManager = null)
204
    {
205 131
        $this->_driver = $driver;
206 131
        $this->_params = $params;
207
208 131
        if (isset($params['pdo'])) {
209 12
            $this->_conn = $params['pdo'];
210 12
            $this->_isConnected = true;
211 12
            unset($this->_params['pdo']);
212
        }
213
214 131
        if (isset($params["platform"])) {
215 28
            if ( ! $params['platform'] instanceof Platforms\AbstractPlatform) {
216 1
                throw DBALException::invalidPlatformType($params['platform']);
217
            }
218
219 27
            $this->platform = $params["platform"];
220 27
            unset($this->_params["platform"]);
221
        }
222
223
        // Create default config and event manager if none given
224 131
        if ( ! $config) {
225 32
            $config = new Configuration();
226
        }
227
228 131
        if ( ! $eventManager) {
229 32
            $eventManager = new EventManager();
230
        }
231
232 131
        $this->_config = $config;
233 131
        $this->_eventManager = $eventManager;
234
235 131
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
236
237 131
        $this->autoCommit = $config->getAutoCommit();
238 131
    }
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 isset($this->_params['host']) ? $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 isset($this->_params['port']) ? $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 isset($this->_params['user']) ? $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 isset($this->_params['password']) ? $this->_params['password'] : null;
298
    }
299
300
    /**
301
     * Gets the DBAL driver instance.
302
     *
303
     * @return \Doctrine\DBAL\Driver
304
     */
305 52
    public function getDriver()
306
    {
307 52
        return $this->_driver;
308
    }
309
310
    /**
311
     * Gets the Configuration used by the Connection.
312
     *
313
     * @return \Doctrine\DBAL\Configuration
314
     */
315 262
    public function getConfiguration()
316
    {
317 262
        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 207
    public function getDatabasePlatform()
338
    {
339 207
        if (null === $this->platform) {
340 27
            $this->detectDatabasePlatform();
341
        }
342
343 206
        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 268
    public function connect()
363
    {
364 268
        if ($this->_isConnected) {
365 251
            return false;
366
        }
367
368 46
        $driverOptions = isset($this->_params['driverOptions']) ?
369 46
            $this->_params['driverOptions'] : [];
370 46
        $user = isset($this->_params['user']) ? $this->_params['user'] : null;
371 46
        $password = isset($this->_params['password']) ?
372 46
            $this->_params['password'] : null;
373
374 46
        $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
375 44
        $this->_isConnected = true;
376
377 44
        if (false === $this->autoCommit) {
378 3
            $this->beginTransaction();
379
        }
380
381 44 View Code Duplication
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
382 1
            $eventArgs = new Event\ConnectionEventArgs($this);
383 1
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
384
        }
385
386 44
        return true;
387
    }
388
389
    /**
390
     * Detects and sets the database platform.
391
     *
392
     * Evaluates custom platform class and version in order to set the correct platform.
393
     *
394
     * @throws DBALException if an invalid platform was specified for this connection.
395
     */
396 27
    private function detectDatabasePlatform()
397
    {
398 27
        $version = $this->getDatabasePlatformVersion();
399
400 26
        if (null !== $version) {
401 1
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
402
        } else {
403 25
            $this->platform = $this->_driver->getDatabasePlatform();
404
        }
405
406 26
        $this->platform->setEventManager($this->_eventManager);
407 26
    }
408
409
    /**
410
     * Returns the version of the related platform if applicable.
411
     *
412
     * Returns null if either the driver is not capable to create version
413
     * specific platform instances, no explicit server version was specified
414
     * or the underlying driver connection cannot determine the platform
415
     * version without having to query it (performance reasons).
416
     *
417
     * @return string|null
418
     *
419
     * @throws Exception
420
     */
421 27
    private function getDatabasePlatformVersion()
422
    {
423
        // Driver does not support version specific platforms.
424 27
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
425 24
            return null;
426
        }
427
428
        // Explicit platform version requested (supersedes auto-detection).
429 3
        if (isset($this->_params['serverVersion'])) {
430
            return $this->_params['serverVersion'];
431
        }
432
433
        // If not connected, we need to connect now to determine the platform version.
434 3
        if (null === $this->_conn) {
435
            try {
436 3
                $this->connect();
437 1
            } catch (\Exception $originalException) {
438 1
                if (empty($this->_params['dbname'])) {
439
                    throw $originalException;
440
                }
441
442
                // The database to connect to might not yet exist.
443
                // Retry detection without database name connection parameter.
444 1
                $databaseName = $this->_params['dbname'];
445 1
                $this->_params['dbname'] = null;
446
447
                try {
448 1
                    $this->connect();
449 1
                } catch (\Exception $fallbackException) {
450
                    // Either the platform does not support database-less connections
451
                    // or something else went wrong.
452
                    // Reset connection parameters and rethrow the original exception.
453 1
                    $this->_params['dbname'] = $databaseName;
454
455 1
                    throw $originalException;
456
                }
457
458
                // Reset connection parameters.
459
                $this->_params['dbname'] = $databaseName;
460
                $serverVersion = $this->getServerVersion();
461
462
                // Close "temporary" connection to allow connecting to the real database again.
463
                $this->close();
464
465
                return $serverVersion;
466
            }
467
468
        }
469
470 2
        return $this->getServerVersion();
471
    }
472
473
    /**
474
     * Returns the database server version if the underlying driver supports it.
475
     *
476
     * @return string|null
477
     */
478 2
    private function getServerVersion()
479
    {
480
        // Automatic platform version detection.
481 2
        if ($this->_conn instanceof ServerInfoAwareConnection &&
482 2
            ! $this->_conn->requiresQueryForServerVersion()
483
        ) {
484 1
            return $this->_conn->getServerVersion();
485
        }
486
487
        // Unable to detect platform version.
488 1
        return null;
489
    }
490
491
    /**
492
     * Returns the current auto-commit mode for this connection.
493
     *
494
     * @return boolean True if auto-commit mode is currently enabled for this connection, false otherwise.
495
     *
496
     * @see    setAutoCommit
497
     */
498 2
    public function isAutoCommit()
499
    {
500 2
        return true === $this->autoCommit;
501
    }
502
503
    /**
504
     * Sets auto-commit mode for this connection.
505
     *
506
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
507
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
508
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
509
     *
510
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
511
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
512
     *
513
     * @param boolean $autoCommit True to enable auto-commit mode; false to disable it.
514
     *
515
     * @see   isAutoCommit
516
     */
517 5
    public function setAutoCommit($autoCommit)
518
    {
519 5
        $autoCommit = (boolean) $autoCommit;
520
521
        // Mode not changed, no-op.
522 5
        if ($autoCommit === $this->autoCommit) {
523 1
            return;
524
        }
525
526 5
        $this->autoCommit = $autoCommit;
527
528
        // Commit all currently active transactions if any when switching auto-commit mode.
529 5
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
530 1
            $this->commitAll();
531
        }
532 5
    }
533
534
    /**
535
     * Sets the fetch mode.
536
     *
537
     * @param integer $fetchMode
538
     *
539
     * @return void
540
     */
541 1
    public function setFetchMode($fetchMode)
542
    {
543 1
        $this->defaultFetchMode = $fetchMode;
544 1
    }
545
546
    /**
547
     * Prepares and executes an SQL query and returns the first row of the result
548
     * as an associative array.
549
     *
550
     * @param string $statement The SQL query.
551
     * @param array  $params    The query parameters.
552
     * @param array  $types     The query parameter types.
553
     *
554
     * @return array|bool False is returned if no rows are found.
555
     *
556
     * @throws \Doctrine\DBAL\DBALException
557
     */
558 42
    public function fetchAssoc($statement, array $params = [], array $types = [])
559
    {
560 42
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_ASSOC);
561
    }
562
563
    /**
564
     * Prepares and executes an SQL query and returns the first row of the result
565
     * as a numerically indexed array.
566
     *
567
     * @param string $statement The SQL query to be executed.
568
     * @param array  $params    The prepared statement params.
569
     * @param array  $types     The query parameter types.
570
     *
571
     * @return array|bool False is returned if no rows are found.
572
     */
573 4
    public function fetchArray($statement, array $params = [], array $types = [])
574
    {
575 4
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_NUM);
576
    }
577
578
    /**
579
     * Prepares and executes an SQL query and returns the value of a single column
580
     * of the first row of the result.
581
     *
582
     * @param string  $statement The SQL query to be executed.
583
     * @param array   $params    The prepared statement params.
584
     * @param integer $column    The 0-indexed column number to retrieve.
585
     * @param array  $types      The query parameter types.
586
     *
587
     * @return mixed|bool False is returned if no rows are found.
588
     *
589
     * @throws \Doctrine\DBAL\DBALException
590
     */
591 28
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
592
    {
593 28
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
594
    }
595
596
    /**
597
     * Whether an actual connection to the database is established.
598
     *
599
     * @return boolean
600
     */
601 4
    public function isConnected()
602
    {
603 4
        return $this->_isConnected;
604
    }
605
606
    /**
607
     * Checks whether a transaction is currently active.
608
     *
609
     * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
610
     */
611 261
    public function isTransactionActive()
612
    {
613 261
        return $this->_transactionNestingLevel > 0;
614
    }
615
616
    /**
617
     * Gathers conditions for an update or delete call.
618
     *
619
     * @param array $identifiers Input array of columns to values
620
     *
621
     * @return string[][] a triplet with:
622
     *                    - the first key being the columns
623
     *                    - the second key being the values
624
     *                    - the third key being the conditions
625
     */
626 13
    private function gatherConditions(array $identifiers)
627
    {
628 13
        $columns = [];
629 13
        $values = [];
630 13
        $conditions = [];
631
632 13
        foreach ($identifiers as $columnName => $value) {
633 13
            if (null === $value) {
634 4
                $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
635 4
                continue;
636
            }
637
638 11
            $columns[] = $columnName;
639 11
            $values[] = $value;
640 11
            $conditions[] = $columnName . ' = ?';
641
        }
642
643 13
        return [$columns, $values, $conditions];
644
    }
645
646
    /**
647
     * Executes an SQL DELETE statement on a table.
648
     *
649
     * Table expression and columns are not escaped and are not safe for user-input.
650
     *
651
     * @param string $tableExpression  The expression of the table on which to delete.
652
     * @param array  $identifier The deletion criteria. An associative array containing column-value pairs.
653
     * @param array  $types      The types of identifiers.
654
     *
655
     * @return integer The number of affected rows.
656
     *
657
     * @throws \Doctrine\DBAL\DBALException
658
     * @throws InvalidArgumentException
659
     */
660 6
    public function delete($tableExpression, array $identifier, array $types = [])
661
    {
662 6
        if (empty($identifier)) {
663 1
            throw InvalidArgumentException::fromEmptyCriteria();
664
        }
665
666 5
        list($columns, $values, $conditions) = $this->gatherConditions($identifier);
667
668 5
        return $this->executeUpdate(
669 5
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
670 5
            $values,
671 5
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
672
        );
673
    }
674
675
    /**
676
     * Closes the connection.
677
     *
678
     * @return void
679
     */
680 28
    public function close()
681
    {
682 28
        $this->_conn = null;
683
684 28
        $this->_isConnected = false;
685 28
    }
686
687
    /**
688
     * Sets the transaction isolation level.
689
     *
690
     * @param integer $level The level to set.
691
     *
692
     * @return integer
693
     */
694
    public function setTransactionIsolation($level)
695
    {
696
        $this->_transactionIsolationLevel = $level;
697
698
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
699
    }
700
701
    /**
702
     * Gets the currently active transaction isolation level.
703
     *
704
     * @return integer The current transaction isolation level.
705
     */
706
    public function getTransactionIsolation()
707
    {
708
        if (null === $this->_transactionIsolationLevel) {
709
            $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
710
        }
711
712
        return $this->_transactionIsolationLevel;
713
    }
714
715
    /**
716
     * Executes an SQL UPDATE statement on a table.
717
     *
718
     * Table expression and columns are not escaped and are not safe for user-input.
719
     *
720
     * @param string $tableExpression  The expression of the table to update quoted or unquoted.
721
     * @param array  $data       An associative array containing column-value pairs.
722
     * @param array  $identifier The update criteria. An associative array containing column-value pairs.
723
     * @param array  $types      Types of the merged $data and $identifier arrays in that order.
724
     *
725
     * @return integer The number of affected rows.
726
     *
727
     * @throws \Doctrine\DBAL\DBALException
728
     */
729 8
    public function update($tableExpression, array $data, array $identifier, array $types = [])
730
    {
731 8
        $setColumns = [];
732 8
        $setValues = [];
733 8
        $set = [];
734
735 8
        foreach ($data as $columnName => $value) {
736 8
            $setColumns[] = $columnName;
737 8
            $setValues[] = $value;
738 8
            $set[] = $columnName . ' = ?';
739
        }
740
741 8
        list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
742 8
        $columns = array_merge($setColumns, $conditionColumns);
743 8
        $values = array_merge($setValues, $conditionValues);
744
745 8
        if (is_string(key($types))) {
746 5
            $types = $this->extractTypeValues($columns, $types);
747
        }
748
749 8
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
750 8
                . ' WHERE ' . implode(' AND ', $conditions);
751
752 8
        return $this->executeUpdate($sql, $values, $types);
753
    }
754
755
    /**
756
     * Inserts a table row with specified data.
757
     *
758
     * Table expression and columns are not escaped and are not safe for user-input.
759
     *
760
     * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
761
     * @param array  $data      An associative array containing column-value pairs.
762
     * @param array  $types     Types of the inserted data.
763
     *
764
     * @return integer The number of affected rows.
765
     *
766
     * @throws \Doctrine\DBAL\DBALException
767
     */
768 78
    public function insert($tableExpression, array $data, array $types = [])
769
    {
770 78
        if (empty($data)) {
771 1
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
772
        }
773
774 77
        $columns = [];
775 77
        $values = [];
776 77
        $set = [];
777
778 77
        foreach ($data as $columnName => $value) {
779 77
            $columns[] = $columnName;
780 77
            $values[] = $value;
781 77
            $set[] = '?';
782
        }
783
784 77
        return $this->executeUpdate(
785 77
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
786 77
            ' VALUES (' . implode(', ', $set) . ')',
787 77
            $values,
788 77
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
789
        );
790
    }
791
792
    /**
793
     * Extract ordered type list from an ordered column list and type map.
794
     *
795
     * @param array $columnList
796
     * @param array $types
797
     *
798
     * @return array
799
     */
800 9
    private function extractTypeValues(array $columnList, array $types)
801
    {
802 9
        $typeValues = [];
803
804 9
        foreach ($columnList as $columnIndex => $columnName) {
805 9
            $typeValues[] = isset($types[$columnName])
806 9
                ? $types[$columnName]
807 9
                : \PDO::PARAM_STR;
808
        }
809
810 9
        return $typeValues;
811
    }
812
813
    /**
814
     * Quotes a string so it can be safely used as a table or column name, even if
815
     * it is a reserved name.
816
     *
817
     * Delimiting style depends on the underlying database platform that is being used.
818
     *
819
     * NOTE: Just because you CAN use quoted identifiers does not mean
820
     * you SHOULD use them. In general, they end up causing way more
821
     * problems than they solve.
822
     *
823
     * @param string $str The name to be quoted.
824
     *
825
     * @return string The quoted name.
826
     */
827 1
    public function quoteIdentifier($str)
828
    {
829 1
        return $this->getDatabasePlatform()->quoteIdentifier($str);
830
    }
831
832
    /**
833
     * Quotes a given input parameter.
834
     *
835
     * @param mixed       $input The parameter to be quoted.
836
     * @param int|null $type  The type of the parameter.
837
     *
838
     * @return string The quoted parameter.
839
     */
840 4
    public function quote($input, $type = null)
841
    {
842 4
        $this->connect();
843
844 4
        list($value, $bindingType) = $this->getBindingInfo($input, $type);
845
846 4
        return $this->_conn->quote($value, $bindingType);
847
    }
848
849
    /**
850
     * Prepares and executes an SQL query and returns the result as an associative array.
851
     *
852
     * @param string $sql    The SQL query.
853
     * @param array  $params The query parameters.
854
     * @param array  $types  The query parameter types.
855
     *
856
     * @return array
857
     */
858 87
    public function fetchAll($sql, array $params = [], $types = [])
859
    {
860 87
        return $this->executeQuery($sql, $params, $types)->fetchAll();
861
    }
862
863
    /**
864
     * Prepares an SQL statement.
865
     *
866
     * @param string $statement The SQL statement to prepare.
867
     *
868
     * @return \Doctrine\DBAL\Statement The prepared statement.
869
     *
870
     * @throws \Doctrine\DBAL\DBALException
871
     */
872 40
    public function prepare($statement)
873
    {
874
        try {
875 40
            $stmt = new Statement($statement, $this);
876 1
        } catch (Exception $ex) {
877 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
878
        }
879
880 39
        $stmt->setFetchMode($this->defaultFetchMode);
881
882 39
        return $stmt;
883
    }
884
885
    /**
886
     * Executes an, optionally parametrized, SQL query.
887
     *
888
     * If the query is parametrized, a prepared statement is used.
889
     * If an SQLLogger is configured, the execution is logged.
890
     *
891
     * @param string                                      $query  The SQL query to execute.
892
     * @param array                                       $params The parameters to bind to the query, if any.
893
     * @param array                                       $types  The types the previous parameters are in.
894
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
895
     *
896
     * @return \Doctrine\DBAL\Driver\Statement The executed statement.
897
     *
898
     * @throws \Doctrine\DBAL\DBALException
899
     */
900 183
    public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null)
901
    {
902 183
        if ($qcp !== null) {
903 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...
904
        }
905
906 183
        $this->connect();
907
908 183
        $logger = $this->_config->getSQLLogger();
909 183
        if ($logger) {
910 178
            $logger->startQuery($query, $params, $types);
911
        }
912
913
        try {
914 183 View Code Duplication
            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...
915 31
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
916
917 31
                $stmt = $this->_conn->prepare($query);
918 31
                if ($types) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
919 15
                    $this->_bindTypedValues($stmt, $params, $types);
920 15
                    $stmt->execute();
921
                } else {
922 31
                    $stmt->execute($params);
923
                }
924
            } else {
925 179
                $stmt = $this->_conn->query($query);
926
            }
927 8
        } catch (Exception $ex) {
928 8
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
929
        }
930
931 175
        $stmt->setFetchMode($this->defaultFetchMode);
932
933 175
        if ($logger) {
934 171
            $logger->stopQuery();
935
        }
936
937 175
        return $stmt;
938
    }
939
940
    /**
941
     * Executes a caching query.
942
     *
943
     * @param string                                 $query  The SQL query to execute.
944
     * @param array                                  $params The parameters to bind to the query, if any.
945
     * @param array                                  $types  The types the previous parameters are in.
946
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
947
     *
948
     * @return \Doctrine\DBAL\Driver\ResultStatement
949
     *
950
     * @throws \Doctrine\DBAL\Cache\CacheException
951
     */
952 12
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
953
    {
954 12
        $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
955 12
        if ( ! $resultCache) {
956
            throw CacheException::noResultDriverConfigured();
957
        }
958
959 12
        list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types, $this->getParams());
960
961
        // fetch the row pointers entry
962 12
        if ($data = $resultCache->fetch($cacheKey)) {
963
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
964 9
            if (isset($data[$realKey])) {
965 9
                $stmt = new ArrayStatement($data[$realKey]);
966
            } elseif (array_key_exists($realKey, $data)) {
967
                $stmt = new ArrayStatement([]);
968
            }
969
        }
970
971 12
        if (!isset($stmt)) {
972 10
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
973
        }
974
975 12
        $stmt->setFetchMode($this->defaultFetchMode);
976
977 12
        return $stmt;
978
    }
979
980
    /**
981
     * Executes an, optionally parametrized, SQL query and returns the result,
982
     * applying a given projection/transformation function on each row of the result.
983
     *
984
     * @param string   $query    The SQL query to execute.
985
     * @param array    $params   The parameters, if any.
986
     * @param \Closure $function The transformation function that is applied on each row.
987
     *                           The function receives a single parameter, an array, that
988
     *                           represents a row of the result set.
989
     *
990
     * @return array The projected result of the query.
991
     */
992
    public function project($query, array $params, Closure $function)
993
    {
994
        $result = [];
995
        $stmt = $this->executeQuery($query, $params);
996
997
        while ($row = $stmt->fetch()) {
998
            $result[] = $function($row);
999
        }
1000
1001
        $stmt->closeCursor();
1002
1003
        return $result;
1004
    }
1005
1006
    /**
1007
     * Executes an SQL statement, returning a result set as a Statement object.
1008
     *
1009
     * @return \Doctrine\DBAL\Driver\Statement
1010
     *
1011
     * @throws \Doctrine\DBAL\DBALException
1012
     */
1013 8
    public function query()
1014
    {
1015 8
        $this->connect();
1016
1017 8
        $args = func_get_args();
1018
1019 8
        $logger = $this->_config->getSQLLogger();
1020 8
        if ($logger) {
1021 7
            $logger->startQuery($args[0]);
1022
        }
1023
1024
        try {
1025 8
            $statement = $this->_conn->query(...$args);
1026 1
        } catch (Exception $ex) {
1027 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1028
        }
1029
1030 7
        $statement->setFetchMode($this->defaultFetchMode);
1031
1032 7
        if ($logger) {
1033 7
            $logger->stopQuery();
1034
        }
1035
1036 7
        return $statement;
1037
    }
1038
1039
    /**
1040
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
1041
     * and returns the number of affected rows.
1042
     *
1043
     * This method supports PDO binding types as well as DBAL mapping types.
1044
     *
1045
     * @param string $query  The SQL query.
1046
     * @param array  $params The query parameters.
1047
     * @param array  $types  The parameter types.
1048
     *
1049
     * @return integer The number of affected rows.
1050
     *
1051
     * @throws \Doctrine\DBAL\DBALException
1052
     */
1053 141
    public function executeUpdate($query, array $params = [], array $types = [])
1054
    {
1055 141
        $this->connect();
1056
1057 141
        $logger = $this->_config->getSQLLogger();
1058 141
        if ($logger) {
1059 136
            $logger->startQuery($query, $params, $types);
1060
        }
1061
1062
        try {
1063 141 View Code Duplication
            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...
1064 83
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
1065
1066 83
                $stmt = $this->_conn->prepare($query);
1067 82
                if ($types) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

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