Connection.php 48 KB
Newer Older
1
<?php
romanb's avatar
romanb committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
Benjamin Eberlei's avatar
Benjamin Eberlei committed
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
romanb's avatar
romanb committed
18 19
 */

20
namespace Doctrine\DBAL;
romanb's avatar
romanb committed
21

22
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
Benjamin Morel's avatar
Benjamin Morel committed
23 24 25 26 27 28 29 30 31 32
use PDO;
use Closure;
use Exception;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Cache\ResultCacheStatement;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Cache\ArrayStatement;
use Doctrine\DBAL\Cache\CacheException;
33
use Doctrine\DBAL\Driver\PingableConnection;
34 35

/**
36
 * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
37 38
 * events, transaction isolation levels, configuration, emulated transaction nesting,
 * lazy connecting and more.
39
 *
Benjamin Morel's avatar
Benjamin Morel committed
40 41 42 43 44 45 46 47
 * @link   www.doctrine-project.org
 * @since  2.0
 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
 * @author Lukas Smith <smith@pooteeweet.org> (MDB2 library)
 * @author Benjamin Eberlei <kontakt@beberlei.de>
48
 */
49
class Connection implements DriverConnection
50
{
51 52 53 54
    /**
     * Constant for transaction isolation level READ UNCOMMITTED.
     */
    const TRANSACTION_READ_UNCOMMITTED = 1;
55

56 57 58 59
    /**
     * Constant for transaction isolation level READ COMMITTED.
     */
    const TRANSACTION_READ_COMMITTED = 2;
60

61 62 63 64
    /**
     * Constant for transaction isolation level REPEATABLE READ.
     */
    const TRANSACTION_REPEATABLE_READ = 3;
65

66 67 68 69
    /**
     * Constant for transaction isolation level SERIALIZABLE.
     */
    const TRANSACTION_SERIALIZABLE = 4;
70

71 72
    /**
     * Represents an array of ints to be expanded by Doctrine SQL parsing.
73
     *
Benjamin Morel's avatar
Benjamin Morel committed
74
     * @var integer
75 76
     */
    const PARAM_INT_ARRAY = 101;
77

78 79
    /**
     * Represents an array of strings to be expanded by Doctrine SQL parsing.
80
     *
Benjamin Morel's avatar
Benjamin Morel committed
81
     * @var integer
82 83
     */
    const PARAM_STR_ARRAY = 102;
84

85 86
    /**
     * Offset by which PARAM_* constants are detected as arrays of the param type.
87
     *
Benjamin Morel's avatar
Benjamin Morel committed
88
     * @var integer
89 90
     */
    const ARRAY_PARAM_OFFSET = 100;
91

romanb's avatar
romanb committed
92 93 94
    /**
     * The wrapped driver connection.
     *
95
     * @var \Doctrine\DBAL\Driver\Connection
romanb's avatar
romanb committed
96 97
     */
    protected $_conn;
98

romanb's avatar
romanb committed
99
    /**
100
     * @var \Doctrine\DBAL\Configuration
romanb's avatar
romanb committed
101 102
     */
    protected $_config;
103

romanb's avatar
romanb committed
104
    /**
105
     * @var \Doctrine\Common\EventManager
romanb's avatar
romanb committed
106 107
     */
    protected $_eventManager;
108

109
    /**
110
     * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder
111 112
     */
    protected $_expr;
113

romanb's avatar
romanb committed
114 115 116 117 118
    /**
     * Whether or not a connection has been established.
     *
     * @var boolean
     */
119
    private $_isConnected = false;
120

121
    /**
122
     * The current auto-commit mode of this connection.
123 124 125 126 127
     *
     * @var boolean
     */
    private $autoCommit = true;

128 129 130 131 132 133 134 135 136 137 138 139 140 141
    /**
     * The transaction nesting level.
     *
     * @var integer
     */
    private $_transactionNestingLevel = 0;

    /**
     * The currently active transaction isolation level.
     *
     * @var integer
     */
    private $_transactionIsolationLevel;

142
    /**
Benjamin Morel's avatar
Benjamin Morel committed
143
     * If nested transactions should use savepoints.
144
     *
145
     * @var boolean
146
     */
147
    private $_nestTransactionsWithSavepoints = false;
Lukas Kahwe Smith's avatar
Lukas Kahwe Smith committed
148

romanb's avatar
romanb committed
149 150 151 152 153
    /**
     * The parameters used during creation of the Connection instance.
     *
     * @var array
     */
154
    private $_params = array();
155

romanb's avatar
romanb committed
156 157 158 159
    /**
     * The DatabasePlatform object that provides information about the
     * database platform used by the connection.
     *
160
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
romanb's avatar
romanb committed
161
     */
162
    private $platform;
163

romanb's avatar
romanb committed
164 165 166
    /**
     * The schema manager.
     *
167
     * @var \Doctrine\DBAL\Schema\AbstractSchemaManager
romanb's avatar
romanb committed
168 169
     */
    protected $_schemaManager;
170

romanb's avatar
romanb committed
171
    /**
romanb's avatar
romanb committed
172 173
     * The used DBAL driver.
     *
174
     * @var \Doctrine\DBAL\Driver
romanb's avatar
romanb committed
175 176
     */
    protected $_driver;
177

178
    /**
179
     * Flag that indicates whether the current transaction is marked for rollback only.
180
     *
181
     * @var boolean
182
     */
183 184
    private $_isRollbackOnly = false;

Benjamin Morel's avatar
Benjamin Morel committed
185 186 187
    /**
     * @var integer
     */
188
    protected $defaultFetchMode = PDO::FETCH_ASSOC;
189

romanb's avatar
romanb committed
190 191 192
    /**
     * Initializes a new instance of the Connection class.
     *
Benjamin Morel's avatar
Benjamin Morel committed
193 194 195 196 197 198
     * @param array                              $params       The connection parameters.
     * @param \Doctrine\DBAL\Driver              $driver       The driver to use.
     * @param \Doctrine\DBAL\Configuration|null  $config       The configuration, optional.
     * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional.
     *
     * @throws \Doctrine\DBAL\DBALException
romanb's avatar
romanb committed
199 200 201 202 203 204
     */
    public function __construct(array $params, Driver $driver, Configuration $config = null,
            EventManager $eventManager = null)
    {
        $this->_driver = $driver;
        $this->_params = $params;
205

romanb's avatar
romanb committed
206 207 208
        if (isset($params['pdo'])) {
            $this->_conn = $params['pdo'];
            $this->_isConnected = true;
209
            unset($this->_params['pdo']);
romanb's avatar
romanb committed
210
        }
211

romanb's avatar
romanb committed
212 213 214
        // Create default config and event manager if none given
        if ( ! $config) {
            $config = new Configuration();
romanb's avatar
romanb committed
215
        }
216

romanb's avatar
romanb committed
217
        if ( ! $eventManager) {
romanb's avatar
romanb committed
218
            $eventManager = new EventManager();
romanb's avatar
romanb committed
219
        }
220

romanb's avatar
romanb committed
221
        $this->_config = $config;
romanb's avatar
romanb committed
222
        $this->_eventManager = $eventManager;
223

224
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
225

226
        $this->autoCommit = $config->getAutoCommit();
romanb's avatar
romanb committed
227
    }
romanb's avatar
romanb committed
228

229
    /**
romanb's avatar
romanb committed
230
     * Gets the parameters used during instantiation.
231
     *
Benjamin Morel's avatar
Benjamin Morel committed
232
     * @return array
233 234 235 236 237 238
     */
    public function getParams()
    {
        return $this->_params;
    }

romanb's avatar
romanb committed
239
    /**
romanb's avatar
romanb committed
240
     * Gets the name of the database this Connection is connected to.
romanb's avatar
romanb committed
241
     *
Benjamin Morel's avatar
Benjamin Morel committed
242
     * @return string
romanb's avatar
romanb committed
243 244 245 246 247
     */
    public function getDatabase()
    {
        return $this->_driver->getDatabase($this);
    }
248

249 250
    /**
     * Gets the hostname of the currently connected database.
251
     *
Benjamin Morel's avatar
Benjamin Morel committed
252
     * @return string|null
253 254 255
     */
    public function getHost()
    {
jwage's avatar
jwage committed
256
        return isset($this->_params['host']) ? $this->_params['host'] : null;
257
    }
258

259 260
    /**
     * Gets the port of the currently connected database.
261
     *
262 263 264 265
     * @return mixed
     */
    public function getPort()
    {
jwage's avatar
jwage committed
266
        return isset($this->_params['port']) ? $this->_params['port'] : null;
267
    }
268

269 270
    /**
     * Gets the username used by this connection.
271
     *
Benjamin Morel's avatar
Benjamin Morel committed
272
     * @return string|null
273 274 275
     */
    public function getUsername()
    {
jwage's avatar
jwage committed
276
        return isset($this->_params['user']) ? $this->_params['user'] : null;
277
    }
278

279 280
    /**
     * Gets the password used by this connection.
281
     *
Benjamin Morel's avatar
Benjamin Morel committed
282
     * @return string|null
283 284 285
     */
    public function getPassword()
    {
jwage's avatar
jwage committed
286
        return isset($this->_params['password']) ? $this->_params['password'] : null;
287
    }
romanb's avatar
romanb committed
288 289 290 291

    /**
     * Gets the DBAL driver instance.
     *
292
     * @return \Doctrine\DBAL\Driver
romanb's avatar
romanb committed
293 294 295 296 297 298 299 300 301
     */
    public function getDriver()
    {
        return $this->_driver;
    }

    /**
     * Gets the Configuration used by the Connection.
     *
302
     * @return \Doctrine\DBAL\Configuration
romanb's avatar
romanb committed
303 304 305 306 307 308 309 310 311
     */
    public function getConfiguration()
    {
        return $this->_config;
    }

    /**
     * Gets the EventManager used by the Connection.
     *
312
     * @return \Doctrine\Common\EventManager
romanb's avatar
romanb committed
313 314 315 316 317 318 319 320 321
     */
    public function getEventManager()
    {
        return $this->_eventManager;
    }

    /**
     * Gets the DatabasePlatform for the connection.
     *
322
     * @return \Doctrine\DBAL\Platforms\AbstractPlatform
romanb's avatar
romanb committed
323 324 325
     */
    public function getDatabasePlatform()
    {
326 327
        if (null == $this->platform) {
            $this->detectDatabasePlatform();
328 329 330
        }

        return $this->platform;
romanb's avatar
romanb committed
331
    }
332

333 334 335
    /**
     * Gets the ExpressionBuilder for the connection.
     *
336
     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
337 338 339 340 341
     */
    public function getExpressionBuilder()
    {
        return $this->_expr;
    }
342

romanb's avatar
romanb committed
343 344 345
    /**
     * Establishes the connection with the database.
     *
346 347
     * @return boolean TRUE if the connection was successfully established, FALSE if
     *                 the connection is already open.
romanb's avatar
romanb committed
348 349 350 351 352 353
     */
    public function connect()
    {
        if ($this->_isConnected) return false;

        $driverOptions = isset($this->_params['driverOptions']) ?
romanb's avatar
romanb committed
354 355
                $this->_params['driverOptions'] : array();
        $user = isset($this->_params['user']) ? $this->_params['user'] : null;
romanb's avatar
romanb committed
356
        $password = isset($this->_params['password']) ?
romanb's avatar
romanb committed
357 358 359
                $this->_params['password'] : null;

        $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
romanb's avatar
romanb committed
360 361
        $this->_isConnected = true;

362 363 364 365
        if (null === $this->platform) {
            $this->detectDatabasePlatform();
        }

366 367 368 369
        if (false === $this->autoCommit) {
            $this->beginTransaction();
        }

370
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
371
            $eventArgs = new Event\ConnectionEventArgs($this);
372 373 374
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
        }

romanb's avatar
romanb committed
375 376 377
        return true;
    }

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
    /**
     * Detects and sets the database platform.
     *
     * Evaluates custom platform class and version in order to set the correct platform.
     *
     * @throws DBALException if an invalid platform was specified for this connection.
     */
    private function detectDatabasePlatform()
    {
        if ( ! isset($this->_params['platform'])) {
            $version = $this->getDatabasePlatformVersion();

            if (null !== $version) {
                $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
            } else {
                $this->platform = $this->_driver->getDatabasePlatform();
            }
        } elseif ($this->_params['platform'] instanceof Platforms\AbstractPlatform) {
            $this->platform = $this->_params['platform'];
        } else {
            throw DBALException::invalidPlatformSpecified();
        }

        $this->platform->setEventManager($this->_eventManager);
    }

    /**
     * Returns the version of the related platform if applicable.
     *
     * Returns null if either the driver is not capable to create version
     * specific platform instances, no explicit server version was specified
     * or the underlying driver connection cannot determine the platform
     * version without having to query it (performance reasons).
     *
     * @return string|null
     */
    private function getDatabasePlatformVersion()
    {
        // Driver does not support version specific platforms.
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
            return null;
        }

        // Explicit platform version requested (supersedes auto-detection).
        if (isset($this->_params['serverVersion'])) {
            return $this->_params['serverVersion'];
        }

        // If not connected, we need to connect now to determine the platform version.
        if (null === $this->_conn) {
            $this->connect();
        }

        // Automatic platform version detection.
        if ($this->_conn instanceof ServerInfoAwareConnection &&
            ! $this->_conn->requiresQueryForServerVersion()
        ) {
            return $this->_conn->getServerVersion();
        }

        // Unable to detect platform version.
        return null;
    }

442 443 444 445 446 447 448
    /**
     * Returns the current auto-commit mode for this connection.
     *
     * @return boolean True if auto-commit mode is currently enabled for this connection, false otherwise.
     *
     * @see    setAutoCommit
     */
449
    public function isAutoCommit()
450
    {
451
        return true === $this->autoCommit;
452 453 454 455 456 457 458 459 460 461 462 463 464 465
    }

    /**
     * Sets auto-commit mode for this connection.
     *
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
     *
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
     *
     * @param boolean $autoCommit True to enable auto-commit mode; false to disable it.
     *
466
     * @see   isAutoCommit
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
     */
    public function setAutoCommit($autoCommit)
    {
        $autoCommit = (boolean) $autoCommit;

        // Mode not changed, no-op.
        if ($autoCommit === $this->autoCommit) {
            return;
        }

        $this->autoCommit = $autoCommit;

        // Commit all currently active transactions if any when switching auto-commit mode.
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
            $this->commitAll();
        }
    }

485
    /**
Benjamin Morel's avatar
Benjamin Morel committed
486
     * Sets the fetch mode.
487
     *
488
     * @param integer $fetchMode
Benjamin Morel's avatar
Benjamin Morel committed
489 490
     *
     * @return void
491
     */
492
    public function setFetchMode($fetchMode)
493
    {
494
        $this->defaultFetchMode = $fetchMode;
495 496
    }

romanb's avatar
romanb committed
497
    /**
498 499
     * Prepares and executes an SQL query and returns the first row of the result
     * as an associative array.
500
     *
romanb's avatar
romanb committed
501
     * @param string $statement The SQL query.
Benjamin Morel's avatar
Benjamin Morel committed
502
     * @param array  $params    The query parameters.
503
     * @param array  $types     The query parameter types.
Benjamin Morel's avatar
Benjamin Morel committed
504
     *
romanb's avatar
romanb committed
505 506
     * @return array
     */
507
    public function fetchAssoc($statement, array $params = array(), array $types = array())
romanb's avatar
romanb committed
508
    {
509
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_ASSOC);
romanb's avatar
romanb committed
510 511 512
    }

    /**
513 514
     * Prepares and executes an SQL query and returns the first row of the result
     * as a numerically indexed array.
romanb's avatar
romanb committed
515
     *
Benjamin Morel's avatar
Benjamin Morel committed
516 517
     * @param string $statement The SQL query to be executed.
     * @param array  $params    The prepared statement params.
518
     * @param array  $types     The query parameter types.
Benjamin Morel's avatar
Benjamin Morel committed
519
     *
romanb's avatar
romanb committed
520 521
     * @return array
     */
522
    public function fetchArray($statement, array $params = array(), array $types = array())
romanb's avatar
romanb committed
523
    {
524
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_NUM);
romanb's avatar
romanb committed
525 526 527
    }

    /**
528 529
     * Prepares and executes an SQL query and returns the value of a single column
     * of the first row of the result.
530
     *
Benjamin Morel's avatar
Benjamin Morel committed
531 532
     * @param string  $statement The SQL query to be executed.
     * @param array   $params    The prepared statement params.
Bilge's avatar
Bilge committed
533
     * @param integer $column    The 0-indexed column number to retrieve.
534
     * @param array  $types      The query parameter types.
Benjamin Morel's avatar
Benjamin Morel committed
535
     *
536
     * @return mixed
romanb's avatar
romanb committed
537
     */
538
    public function fetchColumn($statement, array $params = array(), $column = 0, array $types = array())
romanb's avatar
romanb committed
539
    {
540
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
romanb's avatar
romanb committed
541 542 543 544 545 546 547 548 549 550 551 552
    }

    /**
     * Whether an actual connection to the database is established.
     *
     * @return boolean
     */
    public function isConnected()
    {
        return $this->_isConnected;
    }

553 554
    /**
     * Checks whether a transaction is currently active.
555
     *
556 557 558 559 560 561 562
     * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
     */
    public function isTransactionActive()
    {
        return $this->_transactionNestingLevel > 0;
    }

563 564
    /**
     * Executes an SQL DELETE statement on a table.
romanb's avatar
romanb committed
565
     *
566 567 568
     * Table expression and columns are not escaped and are not safe for user-input.
     *
     * @param string $tableExpression  The expression of the table on which to delete.
Benjamin Morel's avatar
Benjamin Morel committed
569 570 571
     * @param array  $identifier The deletion criteria. An associative array containing column-value pairs.
     * @param array  $types      The types of identifiers.
     *
572
     * @return integer The number of affected rows.
romanb's avatar
romanb committed
573
     */
574
    public function delete($tableExpression, array $identifier, array $types = array())
romanb's avatar
romanb committed
575 576
    {
        $this->connect();
577

romanb's avatar
romanb committed
578
        $criteria = array();
579 580 581

        foreach (array_keys($identifier) as $columnName) {
            $criteria[] = $columnName . ' = ?';
romanb's avatar
romanb committed
582 583
        }

584
        if (is_string(key($types))) {
585 586 587
            $types = $this->extractTypeValues($identifier, $types);
        }

588
        $query = 'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $criteria);
romanb's avatar
romanb committed
589

590
        return $this->executeUpdate($query, array_values($identifier), $types);
romanb's avatar
romanb committed
591 592 593 594 595 596 597 598 599 600
    }

    /**
     * Closes the connection.
     *
     * @return void
     */
    public function close()
    {
        unset($this->_conn);
601

romanb's avatar
romanb committed
602 603 604
        $this->_isConnected = false;
    }

605 606 607
    /**
     * Sets the transaction isolation level.
     *
608
     * @param integer $level The level to set.
Benjamin Morel's avatar
Benjamin Morel committed
609
     *
610
     * @return integer
611 612 613 614
     */
    public function setTransactionIsolation($level)
    {
        $this->_transactionIsolationLevel = $level;
615

616
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
617 618 619 620 621
    }

    /**
     * Gets the currently active transaction isolation level.
     *
622
     * @return integer The current transaction isolation level.
623 624 625
     */
    public function getTransactionIsolation()
    {
626 627 628 629
        if (null === $this->_transactionIsolationLevel) {
            $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
        }

630 631 632
        return $this->_transactionIsolationLevel;
    }

romanb's avatar
romanb committed
633
    /**
634
     * Executes an SQL UPDATE statement on a table.
romanb's avatar
romanb committed
635
     *
636 637 638
     * Table expression and columns are not escaped and are not safe for user-input.
     *
     * @param string $tableExpression  The expression of the table to update quoted or unquoted.
Benjamin Morel's avatar
Benjamin Morel committed
639 640 641 642
     * @param array  $data       An associative array containing column-value pairs.
     * @param array  $identifier The update criteria. An associative array containing column-value pairs.
     * @param array  $types      Types of the merged $data and $identifier arrays in that order.
     *
643
     * @return integer The number of affected rows.
romanb's avatar
romanb committed
644
     */
645
    public function update($tableExpression, array $data, array $identifier, array $types = array())
romanb's avatar
romanb committed
646 647 648
    {
        $this->connect();
        $set = array();
649

romanb's avatar
romanb committed
650
        foreach ($data as $columnName => $value) {
651
            $set[] = $columnName . ' = ?';
romanb's avatar
romanb committed
652 653
        }

654
        if (is_string(key($types))) {
655 656 657
            $types = $this->extractTypeValues(array_merge($data, $identifier), $types);
        }

romanb's avatar
romanb committed
658 659
        $params = array_merge(array_values($data), array_values($identifier));

660
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
661 662
                . ' WHERE ' . implode(' = ? AND ', array_keys($identifier))
                . ' = ?';
romanb's avatar
romanb committed
663

664
        return $this->executeUpdate($sql, $params, $types);
romanb's avatar
romanb committed
665 666 667 668 669
    }

    /**
     * Inserts a table row with specified data.
     *
670 671 672
     * Table expression and columns are not escaped and are not safe for user-input.
     *
     * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
Benjamin Morel's avatar
Benjamin Morel committed
673 674 675
     * @param array  $data      An associative array containing column-value pairs.
     * @param array  $types     Types of the inserted data.
     *
676
     * @return integer The number of affected rows.
romanb's avatar
romanb committed
677
     */
678
    public function insert($tableExpression, array $data, array $types = array())
romanb's avatar
romanb committed
679 680 681
    {
        $this->connect();

682
        if (empty($data)) {
683
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
684 685
        }

686
        return $this->executeUpdate(
687
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', array_keys($data)) . ')' .
688 689
            ' VALUES (' . implode(', ', array_fill(0, count($data), '?')) . ')',
            array_values($data),
690
            is_string(key($types)) ? $this->extractTypeValues($data, $types) : $types
691
        );
romanb's avatar
romanb committed
692 693
    }

694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
    /**
     * Extract ordered type list from two associate key lists of data and types.
     *
     * @param array $data
     * @param array $types
     *
     * @return array
     */
    private function extractTypeValues(array $data, array $types)
    {
        $typeValues = array();

        foreach ($data as $k => $_) {
            $typeValues[] = isset($types[$k])
                ? $types[$k]
                : \PDO::PARAM_STR;
        }

        return $typeValues;
    }

romanb's avatar
romanb committed
715
    /**
Benjamin Morel's avatar
Benjamin Morel committed
716
     * Quotes a string so it can be safely used as a table or column name, even if
romanb's avatar
romanb committed
717 718 719 720
     * it is a reserved name.
     *
     * Delimiting style depends on the underlying database platform that is being used.
     *
721 722
     * NOTE: Just because you CAN use quoted identifiers does not mean
     * you SHOULD use them. In general, they end up causing way more
romanb's avatar
romanb committed
723 724
     * problems than they solve.
     *
725
     * @param string $str The name to be quoted.
Benjamin Morel's avatar
Benjamin Morel committed
726
     *
727
     * @return string The quoted name.
romanb's avatar
romanb committed
728 729 730
     */
    public function quoteIdentifier($str)
    {
731
        return $this->getDatabasePlatform()->quoteIdentifier($str);
romanb's avatar
romanb committed
732 733 734 735 736
    }

    /**
     * Quotes a given input parameter.
     *
Benjamin Morel's avatar
Benjamin Morel committed
737 738 739
     * @param mixed       $input The parameter to be quoted.
     * @param string|null $type  The type of the parameter.
     *
740
     * @return string The quoted parameter.
romanb's avatar
romanb committed
741 742 743
     */
    public function quote($input, $type = null)
    {
744
        $this->connect();
745 746

        list($value, $bindingType) = $this->getBindingInfo($input, $type);
747
        return $this->_conn->quote($value, $bindingType);
romanb's avatar
romanb committed
748 749 750
    }

    /**
751
     * Prepares and executes an SQL query and returns the result as an associative array.
romanb's avatar
romanb committed
752
     *
Benjamin Morel's avatar
Benjamin Morel committed
753 754 755 756
     * @param string $sql    The SQL query.
     * @param array  $params The query parameters.
     * @param array  $types  The query parameter types.
     *
romanb's avatar
romanb committed
757 758
     * @return array
     */
root's avatar
root committed
759
    public function fetchAll($sql, array $params = array(), $types = array())
romanb's avatar
romanb committed
760
    {
root's avatar
root committed
761
        return $this->executeQuery($sql, $params, $types)->fetchAll();
romanb's avatar
romanb committed
762 763 764 765 766
    }

    /**
     * Prepares an SQL statement.
     *
767
     * @param string $statement The SQL statement to prepare.
Benjamin Morel's avatar
Benjamin Morel committed
768
     *
769
     * @return \Doctrine\DBAL\Driver\Statement The prepared statement.
Benjamin Morel's avatar
Benjamin Morel committed
770 771
     *
     * @throws \Doctrine\DBAL\DBALException
romanb's avatar
romanb committed
772 773 774 775
     */
    public function prepare($statement)
    {
        $this->connect();
776

777 778 779
        try {
            $stmt = new Statement($statement, $this);
        } catch (\Exception $ex) {
780
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
781 782
        }

783
        $stmt->setFetchMode($this->defaultFetchMode);
784 785

        return $stmt;
romanb's avatar
romanb committed
786 787 788
    }

    /**
Pascal Borreli's avatar
Pascal Borreli committed
789
     * Executes an, optionally parametrized, SQL query.
romanb's avatar
romanb committed
790
     *
Pascal Borreli's avatar
Pascal Borreli committed
791
     * If the query is parametrized, a prepared statement is used.
792 793
     * If an SQLLogger is configured, the execution is logged.
     *
Benjamin Morel's avatar
Benjamin Morel committed
794 795 796 797 798
     * @param string                                      $query  The SQL query to execute.
     * @param array                                       $params The parameters to bind to the query, if any.
     * @param array                                       $types  The types the previous parameters are in.
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
     *
799
     * @return \Doctrine\DBAL\Driver\Statement The executed statement.
Benjamin Morel's avatar
Benjamin Morel committed
800 801 802
     *
     * @throws \Doctrine\DBAL\DBALException
     *
803
     * @internal PERF: Directly prepares a driver statement, not a wrapper.
romanb's avatar
romanb committed
804
     */
805
    public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null)
romanb's avatar
romanb committed
806
    {
807
        if ($qcp !== null) {
808
            return $this->executeCacheQuery($query, $params, $types, $qcp);
809 810
        }

romanb's avatar
romanb committed
811 812
        $this->connect();

813 814 815
        $logger = $this->_config->getSQLLogger();
        if ($logger) {
            $logger->startQuery($query, $params, $types);
romanb's avatar
romanb committed
816
        }
817

818 819 820
        try {
            if ($params) {
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
821

822 823 824 825 826 827 828
                $stmt = $this->_conn->prepare($query);
                if ($types) {
                    $this->_bindTypedValues($stmt, $params, $types);
                    $stmt->execute();
                } else {
                    $stmt->execute($params);
                }
829
            } else {
830
                $stmt = $this->_conn->query($query);
831
            }
832
        } catch (\Exception $ex) {
833
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
romanb's avatar
romanb committed
834
        }
835

836
        $stmt->setFetchMode($this->defaultFetchMode);
837

838 839
        if ($logger) {
            $logger->stopQuery();
840 841
        }

romanb's avatar
romanb committed
842
        return $stmt;
romanb's avatar
romanb committed
843
    }
844

845
    /**
Benjamin Morel's avatar
Benjamin Morel committed
846 847 848 849 850 851
     * Executes a caching query.
     *
     * @param string                                 $query  The SQL query to execute.
     * @param array                                  $params The parameters to bind to the query, if any.
     * @param array                                  $types  The types the previous parameters are in.
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
852 853
     *
     * @return \Doctrine\DBAL\Driver\ResultStatement
Benjamin Morel's avatar
Benjamin Morel committed
854 855
     *
     * @throws \Doctrine\DBAL\Cache\CacheException
856 857 858 859
     */
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
    {
        $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
860
        if ( ! $resultCache) {
861 862 863 864 865 866 867 868 869
            throw CacheException::noResultDriverConfigured();
        }

        list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types);

        // fetch the row pointers entry
        if ($data = $resultCache->fetch($cacheKey)) {
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
            if (isset($data[$realKey])) {
870
                $stmt = new ArrayStatement($data[$realKey]);
Steve Müller's avatar
Steve Müller committed
871
            } elseif (array_key_exists($realKey, $data)) {
872
                $stmt = new ArrayStatement(array());
873 874
            }
        }
875 876 877 878 879

        if (!isset($stmt)) {
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
        }

880
        $stmt->setFetchMode($this->defaultFetchMode);
881 882

        return $stmt;
883 884
    }

885
    /**
Pascal Borreli's avatar
Pascal Borreli committed
886
     * Executes an, optionally parametrized, SQL query and returns the result,
887
     * applying a given projection/transformation function on each row of the result.
888
     *
Benjamin Morel's avatar
Benjamin Morel committed
889 890 891 892 893 894
     * @param string   $query    The SQL query to execute.
     * @param array    $params   The parameters, if any.
     * @param \Closure $function The transformation function that is applied on each row.
     *                           The function receives a single parameter, an array, that
     *                           represents a row of the result set.
     *
895
     * @return mixed The projected result of the query.
896
     */
897
    public function project($query, array $params, Closure $function)
898 899
    {
        $result = array();
900
        $stmt = $this->executeQuery($query, $params ?: array());
901

902
        while ($row = $stmt->fetch()) {
903
            $result[] = $function($row);
904
        }
905

906
        $stmt->closeCursor();
907

908 909
        return $result;
    }
romanb's avatar
romanb committed
910 911

    /**
912
     * Executes an SQL statement, returning a result set as a Statement object.
913
     *
914
     * @return \Doctrine\DBAL\Driver\Statement
Benjamin Morel's avatar
Benjamin Morel committed
915 916
     *
     * @throws \Doctrine\DBAL\DBALException
917 918 919
     */
    public function query()
    {
920 921
        $this->connect();

922 923
        $args = func_get_args();

924
        $logger = $this->_config->getSQLLogger();
925 926 927 928
        if ($logger) {
            $logger->startQuery($args[0]);
        }

929
        try {
930 931 932 933 934 935 936 937 938 939 940
            switch (func_num_args()) {
                case 1:
                    $statement = $this->_conn->query($args[0]);
                    break;
                case 2:
                    $statement = $this->_conn->query($args[0], $args[1]);
                    break;
                default:
                    $statement = call_user_func_array(array($this->_conn, 'query'), $args);
                    break;
            }
941
        } catch (\Exception $ex) {
942
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
943 944
        }

945
        $statement->setFetchMode($this->defaultFetchMode);
946 947 948 949 950 951

        if ($logger) {
            $logger->stopQuery();
        }

        return $statement;
952 953 954 955 956
    }

    /**
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
     * and returns the number of affected rows.
957
     *
958
     * This method supports PDO binding types as well as DBAL mapping types.
romanb's avatar
romanb committed
959
     *
Benjamin Morel's avatar
Benjamin Morel committed
960 961 962 963
     * @param string $query  The SQL query.
     * @param array  $params The query parameters.
     * @param array  $types  The parameter types.
     *
964
     * @return integer The number of affected rows.
Benjamin Morel's avatar
Benjamin Morel committed
965 966 967
     *
     * @throws \Doctrine\DBAL\DBALException
     *
968
     * @internal PERF: Directly prepares a driver statement, not a wrapper.
romanb's avatar
romanb committed
969
     */
970
    public function executeUpdate($query, array $params = array(), array $types = array())
romanb's avatar
romanb committed
971 972 973
    {
        $this->connect();

974 975 976
        $logger = $this->_config->getSQLLogger();
        if ($logger) {
            $logger->startQuery($query, $params, $types);
romanb's avatar
romanb committed
977 978
        }

979 980 981
        try {
            if ($params) {
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
982

983 984 985 986 987 988 989 990
                $stmt = $this->_conn->prepare($query);
                if ($types) {
                    $this->_bindTypedValues($stmt, $params, $types);
                    $stmt->execute();
                } else {
                    $stmt->execute($params);
                }
                $result = $stmt->rowCount();
991
            } else {
992
                $result = $this->_conn->exec($query);
993
            }
994
        } catch (\Exception $ex) {
995
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
romanb's avatar
romanb committed
996
        }
997

998 999
        if ($logger) {
            $logger->stopQuery();
1000 1001
        }

romanb's avatar
romanb committed
1002
        return $result;
romanb's avatar
romanb committed
1003 1004
    }

1005
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1006
     * Executes an SQL statement and return the number of affected rows.
1007
     *
1008
     * @param string $statement
Benjamin Morel's avatar
Benjamin Morel committed
1009
     *
1010
     * @return integer The number of affected rows.
Benjamin Morel's avatar
Benjamin Morel committed
1011 1012
     *
     * @throws \Doctrine\DBAL\DBALException
1013 1014 1015 1016
     */
    public function exec($statement)
    {
        $this->connect();
1017 1018 1019 1020 1021 1022

        $logger = $this->_config->getSQLLogger();
        if ($logger) {
            $logger->startQuery($statement);
        }

1023 1024 1025
        try {
            $result = $this->_conn->exec($statement);
        } catch (\Exception $ex) {
1026
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
1027
        }
1028 1029 1030 1031 1032 1033

        if ($logger) {
            $logger->stopQuery();
        }

        return $result;
1034 1035
    }

1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
    /**
     * Returns the current transaction nesting level.
     *
     * @return integer The nesting level. A value of 0 means there's no active transaction.
     */
    public function getTransactionNestingLevel()
    {
        return $this->_transactionNestingLevel;
    }

romanb's avatar
romanb committed
1046
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1047
     * Fetches the SQLSTATE associated with the last database operation.
romanb's avatar
romanb committed
1048
     *
1049
     * @return integer The last error code.
romanb's avatar
romanb committed
1050 1051 1052 1053
     */
    public function errorCode()
    {
        $this->connect();
Benjamin Morel's avatar
Benjamin Morel committed
1054

romanb's avatar
romanb committed
1055 1056 1057 1058
        return $this->_conn->errorCode();
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1059
     * Fetches extended error information associated with the last database operation.
romanb's avatar
romanb committed
1060
     *
1061
     * @return array The last error information.
romanb's avatar
romanb committed
1062 1063 1064 1065
     */
    public function errorInfo()
    {
        $this->connect();
Benjamin Morel's avatar
Benjamin Morel committed
1066

romanb's avatar
romanb committed
1067 1068 1069 1070 1071 1072 1073 1074
        return $this->_conn->errorInfo();
    }

    /**
     * Returns the ID of the last inserted row, or the last value from a sequence object,
     * depending on the underlying driver.
     *
     * Note: This method may not return a meaningful or consistent result across different drivers,
1075 1076
     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
     * columns or sequences.
romanb's avatar
romanb committed
1077
     *
Benjamin Morel's avatar
Benjamin Morel committed
1078 1079
     * @param string|null $seqName Name of the sequence object from which the ID should be returned.
     *
1080
     * @return string A string representation of the last inserted ID.
romanb's avatar
romanb committed
1081
     */
romanb's avatar
romanb committed
1082 1083 1084
    public function lastInsertId($seqName = null)
    {
        $this->connect();
Benjamin Morel's avatar
Benjamin Morel committed
1085

romanb's avatar
romanb committed
1086 1087
        return $this->_conn->lastInsertId($seqName);
    }
1088

1089 1090 1091 1092 1093 1094 1095 1096
    /**
     * Executes a function in a transaction.
     *
     * The function gets passed this Connection instance as an (optional) parameter.
     *
     * If an exception occurs during execution of the function or transaction commit,
     * the transaction is rolled back and the exception re-thrown.
     *
Benjamin Morel's avatar
Benjamin Morel committed
1097 1098 1099 1100 1101
     * @param \Closure $func The function to execute transactionally.
     *
     * @return void
     *
     * @throws \Exception
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
     */
    public function transactional(Closure $func)
    {
        $this->beginTransaction();
        try {
            $func($this);
            $this->commit();
        } catch (Exception $e) {
            $this->rollback();
            throw $e;
        }
    }

1115
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1116
     * Sets if nested transactions should use savepoints.
1117
     *
1118
     * @param boolean $nestTransactionsWithSavepoints
Benjamin Morel's avatar
Benjamin Morel committed
1119
     *
1120
     * @return void
Benjamin Morel's avatar
Benjamin Morel committed
1121 1122
     *
     * @throws \Doctrine\DBAL\ConnectionException
1123 1124 1125
     */
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
    {
1126 1127 1128 1129
        if ($this->_transactionNestingLevel > 0) {
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
        }

1130
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1131
            throw ConnectionException::savepointsNotSupported();
1132 1133
        }

1134
        $this->_nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1135 1136 1137
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1138
     * Gets if nested transactions should use savepoints.
1139 1140 1141 1142 1143 1144 1145 1146
     *
     * @return boolean
     */
    public function getNestTransactionsWithSavepoints()
    {
        return $this->_nestTransactionsWithSavepoints;
    }

1147 1148 1149 1150
    /**
     * Returns the savepoint name to use for nested transactions are false if they are not supported
     * "savepointFormat" parameter is not set
     *
Benjamin Morel's avatar
Benjamin Morel committed
1151
     * @return mixed A string with the savepoint name or false.
1152
     */
1153 1154 1155
    protected function _getNestedTransactionSavePointName()
    {
        return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
1156 1157
    }

1158 1159 1160 1161 1162 1163 1164 1165 1166
    /**
     * Starts a transaction by suspending auto-commit mode.
     *
     * @return void
     */
    public function beginTransaction()
    {
        $this->connect();

1167 1168
        ++$this->_transactionNestingLevel;

1169 1170
        $logger = $this->_config->getSQLLogger();

1171
        if ($this->_transactionNestingLevel == 1) {
1172 1173 1174
            if ($logger) {
                $logger->startQuery('"START TRANSACTION"');
            }
1175
            $this->_conn->beginTransaction();
1176 1177 1178
            if ($logger) {
                $logger->stopQuery();
            }
Steve Müller's avatar
Steve Müller committed
1179
        } elseif ($this->_nestTransactionsWithSavepoints) {
1180 1181 1182
            if ($logger) {
                $logger->startQuery('"SAVEPOINT"');
            }
1183
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1184 1185 1186
            if ($logger) {
                $logger->stopQuery();
            }
1187 1188 1189 1190 1191 1192 1193
        }
    }

    /**
     * Commits the current transaction.
     *
     * @return void
Benjamin Morel's avatar
Benjamin Morel committed
1194 1195 1196
     *
     * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or
     *                                            because the transaction was marked for rollback only.
1197 1198 1199 1200
     */
    public function commit()
    {
        if ($this->_transactionNestingLevel == 0) {
1201
            throw ConnectionException::noActiveTransaction();
1202 1203 1204 1205 1206 1207 1208
        }
        if ($this->_isRollbackOnly) {
            throw ConnectionException::commitFailedRollbackOnly();
        }

        $this->connect();

1209 1210
        $logger = $this->_config->getSQLLogger();

1211
        if ($this->_transactionNestingLevel == 1) {
1212 1213 1214
            if ($logger) {
                $logger->startQuery('"COMMIT"');
            }
1215
            $this->_conn->commit();
1216 1217 1218
            if ($logger) {
                $logger->stopQuery();
            }
Steve Müller's avatar
Steve Müller committed
1219
        } elseif ($this->_nestTransactionsWithSavepoints) {
1220 1221 1222
            if ($logger) {
                $logger->startQuery('"RELEASE SAVEPOINT"');
            }
1223
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1224 1225 1226
            if ($logger) {
                $logger->stopQuery();
            }
1227 1228 1229
        }

        --$this->_transactionNestingLevel;
1230 1231 1232 1233 1234 1235 1236 1237 1238

        if (false === $this->autoCommit && 0 === $this->_transactionNestingLevel) {
            $this->beginTransaction();
        }
    }

    /**
     * Commits all current nesting transactions.
     */
1239
    private function commitAll()
1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251
    {
        while (0 !== $this->_transactionNestingLevel) {
            if (false === $this->autoCommit && 1 === $this->_transactionNestingLevel) {
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
                $this->commit();

                return;
            }

            $this->commit();
        }
1252 1253 1254
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1255
     * Cancels any database changes done during the current transaction.
1256
     *
Benjamin Morel's avatar
Benjamin Morel committed
1257 1258
     * This method can be listened with onPreTransactionRollback and onTransactionRollback
     * eventlistener methods.
1259
     *
Benjamin Morel's avatar
Benjamin Morel committed
1260
     * @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed.
1261
     */
1262
    public function rollBack()
1263 1264
    {
        if ($this->_transactionNestingLevel == 0) {
1265
            throw ConnectionException::noActiveTransaction();
1266 1267 1268 1269
        }

        $this->connect();

1270 1271
        $logger = $this->_config->getSQLLogger();

1272
        if ($this->_transactionNestingLevel == 1) {
1273 1274 1275
            if ($logger) {
                $logger->startQuery('"ROLLBACK"');
            }
1276 1277 1278
            $this->_transactionNestingLevel = 0;
            $this->_conn->rollback();
            $this->_isRollbackOnly = false;
1279 1280 1281
            if ($logger) {
                $logger->stopQuery();
            }
1282 1283 1284 1285

            if (false === $this->autoCommit) {
                $this->beginTransaction();
            }
Steve Müller's avatar
Steve Müller committed
1286
        } elseif ($this->_nestTransactionsWithSavepoints) {
1287 1288 1289
            if ($logger) {
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
            }
1290 1291
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
            --$this->_transactionNestingLevel;
1292 1293 1294
            if ($logger) {
                $logger->stopQuery();
            }
1295
        } else {
1296
            $this->_isRollbackOnly = true;
1297 1298 1299 1300
            --$this->_transactionNestingLevel;
        }
    }

1301
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1302 1303 1304
     * Creates a new savepoint.
     *
     * @param string $savepoint The name of the savepoint to create.
1305 1306
     *
     * @return void
Benjamin Morel's avatar
Benjamin Morel committed
1307 1308
     *
     * @throws \Doctrine\DBAL\ConnectionException
1309
     */
1310
    public function createSavepoint($savepoint)
1311
    {
1312
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1313
            throw ConnectionException::savepointsNotSupported();
1314 1315
        }

1316
        $this->_conn->exec($this->platform->createSavePoint($savepoint));
1317 1318 1319
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1320 1321 1322
     * Releases the given savepoint.
     *
     * @param string $savepoint The name of the savepoint to release.
1323 1324
     *
     * @return void
Benjamin Morel's avatar
Benjamin Morel committed
1325 1326
     *
     * @throws \Doctrine\DBAL\ConnectionException
1327
     */
1328
    public function releaseSavepoint($savepoint)
1329
    {
1330
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1331
            throw ConnectionException::savepointsNotSupported();
1332 1333
        }

1334 1335
        if ($this->platform->supportsReleaseSavepoints()) {
            $this->_conn->exec($this->platform->releaseSavePoint($savepoint));
1336
        }
1337 1338 1339
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1340 1341 1342
     * Rolls back to the given savepoint.
     *
     * @param string $savepoint The name of the savepoint to rollback to.
1343 1344
     *
     * @return void
Benjamin Morel's avatar
Benjamin Morel committed
1345 1346
     *
     * @throws \Doctrine\DBAL\ConnectionException
1347
     */
1348
    public function rollbackSavepoint($savepoint)
1349
    {
1350
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1351
            throw ConnectionException::savepointsNotSupported();
1352 1353
        }

1354
        $this->_conn->exec($this->platform->rollbackSavePoint($savepoint));
1355 1356
    }

romanb's avatar
romanb committed
1357 1358 1359
    /**
     * Gets the wrapped driver connection.
     *
1360
     * @return \Doctrine\DBAL\Driver\Connection
romanb's avatar
romanb committed
1361 1362 1363 1364
     */
    public function getWrappedConnection()
    {
        $this->connect();
1365

romanb's avatar
romanb committed
1366 1367
        return $this->_conn;
    }
1368

romanb's avatar
romanb committed
1369 1370 1371 1372
    /**
     * Gets the SchemaManager that can be used to inspect or change the
     * database schema through the connection.
     *
1373
     * @return \Doctrine\DBAL\Schema\AbstractSchemaManager
romanb's avatar
romanb committed
1374 1375 1376 1377 1378 1379
     */
    public function getSchemaManager()
    {
        if ( ! $this->_schemaManager) {
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
        }
1380

romanb's avatar
romanb committed
1381 1382
        return $this->_schemaManager;
    }
1383

1384 1385 1386
    /**
     * Marks the current transaction so that the only possible
     * outcome for the transaction to be rolled back.
1387
     *
Benjamin Morel's avatar
Benjamin Morel committed
1388 1389 1390
     * @return void
     *
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1391 1392 1393 1394 1395 1396 1397 1398 1399 1400
     */
    public function setRollbackOnly()
    {
        if ($this->_transactionNestingLevel == 0) {
            throw ConnectionException::noActiveTransaction();
        }
        $this->_isRollbackOnly = true;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1401
     * Checks whether the current transaction is marked for rollback only.
1402
     *
1403
     * @return boolean
Benjamin Morel's avatar
Benjamin Morel committed
1404 1405
     *
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1406
     */
1407
    public function isRollbackOnly()
1408 1409 1410 1411
    {
        if ($this->_transactionNestingLevel == 0) {
            throw ConnectionException::noActiveTransaction();
        }
Benjamin Morel's avatar
Benjamin Morel committed
1412

1413 1414 1415
        return $this->_isRollbackOnly;
    }

1416 1417 1418
    /**
     * Converts a given value to its database representation according to the conversion
     * rules of a specific DBAL mapping type.
1419
     *
Benjamin Morel's avatar
Benjamin Morel committed
1420 1421 1422
     * @param mixed  $value The value to convert.
     * @param string $type  The name of the DBAL mapping type.
     *
1423 1424 1425 1426
     * @return mixed The converted value.
     */
    public function convertToDatabaseValue($value, $type)
    {
1427
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1428 1429 1430 1431 1432
    }

    /**
     * Converts a given value to its PHP representation according to the conversion
     * rules of a specific DBAL mapping type.
1433
     *
Benjamin Morel's avatar
Benjamin Morel committed
1434 1435 1436
     * @param mixed  $value The value to convert.
     * @param string $type  The name of the DBAL mapping type.
     *
1437 1438 1439 1440
     * @return mixed The converted type.
     */
    public function convertToPHPValue($value, $type)
    {
1441
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1442 1443 1444 1445 1446
    }

    /**
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
     * or DBAL mapping type, to a given statement.
1447
     *
Benjamin Morel's avatar
Benjamin Morel committed
1448 1449 1450 1451 1452 1453
     * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
     * @param array                           $params The map/list of named/positional parameters.
     * @param array                           $types  The parameter types (PDO binding types or DBAL mapping types).
     *
     * @return void
     *
1454 1455
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
     *           raw PDOStatement instances.
1456
     */
1457
    private function _bindTypedValues($stmt, array $params, array $types)
1458 1459 1460 1461
    {
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
        if (is_int(key($params))) {
            // Positional parameters
1462
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1463
            $bindIndex = 1;
1464
            foreach ($params as $value) {
1465 1466 1467
                $typeIndex = $bindIndex + $typeOffset;
                if (isset($types[$typeIndex])) {
                    $type = $types[$typeIndex];
1468
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479
                    $stmt->bindValue($bindIndex, $value, $bindingType);
                } else {
                    $stmt->bindValue($bindIndex, $value);
                }
                ++$bindIndex;
            }
        } else {
            // Named parameters
            foreach ($params as $name => $value) {
                if (isset($types[$name])) {
                    $type = $types[$name];
1480
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1481 1482 1483 1484 1485 1486 1487
                    $stmt->bindValue($name, $value, $bindingType);
                } else {
                    $stmt->bindValue($name, $value);
                }
            }
        }
    }
1488 1489 1490 1491

    /**
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
     *
Benjamin Morel's avatar
Benjamin Morel committed
1492 1493 1494 1495
     * @param mixed $value The value to bind.
     * @param mixed $type  The type to bind (PDO or DBAL).
     *
     * @return array [0] => the (escaped) value, [1] => the binding type.
1496 1497 1498 1499 1500 1501 1502
     */
    private function getBindingInfo($value, $type)
    {
        if (is_string($type)) {
            $type = Type::getType($type);
        }
        if ($type instanceof Type) {
1503
            $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1504 1505 1506 1507
            $bindingType = $type->getBindingType();
        } else {
            $bindingType = $type; // PDO::PARAM_* constants
        }
Benjamin Morel's avatar
Benjamin Morel committed
1508

1509 1510 1511
        return array($value, $bindingType);
    }

1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558
    /**
     * Resolves the parameters to a format which can be displayed.
     *
     * @internal This is a purely internal method. If you rely on this method, you are advised to
     *           copy/paste the code as this method may change, or be removed without prior notice.
     *
     * @param array $params
     * @param array $types
     *
     * @return array
     */
    public function resolveParams(array $params, array $types)
    {
        $resolvedParams = array();

        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
        if (is_int(key($params))) {
            // Positional parameters
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
            $bindIndex = 1;
            foreach ($params as $value) {
                $typeIndex = $bindIndex + $typeOffset;
                if (isset($types[$typeIndex])) {
                    $type = $types[$typeIndex];
                    list($value,) = $this->getBindingInfo($value, $type);
                    $resolvedParams[$bindIndex] = $value;
                } else {
                    $resolvedParams[$bindIndex] = $value;
                }
                ++$bindIndex;
            }
        } else {
            // Named parameters
            foreach ($params as $name => $value) {
                if (isset($types[$name])) {
                    $type = $types[$name];
                    list($value,) = $this->getBindingInfo($value, $type);
                    $resolvedParams[$name] = $value;
                } else {
                    $resolvedParams[$name] = $value;
                }
            }
        }

        return $resolvedParams;
    }

1559
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1560
     * Creates a new instance of a SQL query builder.
1561
     *
1562
     * @return \Doctrine\DBAL\Query\QueryBuilder
1563 1564 1565 1566 1567
     */
    public function createQueryBuilder()
    {
        return new Query\QueryBuilder($this);
    }
1568 1569

    /**
1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586
     * Ping the server
     *
     * When the server is not available the method returns FALSE.
     * It is responsibility of the developer to handle this case
     * and abort the request or reconnect manually:
     *
     * @example
     *
     *   if ($conn->ping() === false) {
     *      $conn->close();
     *      $conn->connect();
     *   }
     *
     * It is undefined if the underlying driver attempts to reconnect
     * or disconnect when the connection is not available anymore
     * as long it returns TRUE when a reconnect succeeded and
     * FALSE when the connection was dropped.
1587 1588 1589 1590 1591
     *
     * @return bool
     */
    public function ping()
    {
1592
        $this->connect();
1593

1594 1595 1596 1597 1598
        if ($this->_conn instanceof PingableConnection) {
            return $this->_conn->ping();
        }

        try {
1599
            $this->query($this->platform->getDummySelectSQL());
till's avatar
till committed
1600

1601 1602
            return true;
        } catch (DBALException $e) {
1603
            return false;
1604
        }
1605
    }
1606
}