vendor/doctrine-dbal/lib/Doctrine/DBAL/Connection.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     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 LGPL. For more information, see
       
    17  * <http://www.doctrine-project.org>.
       
    18  */
       
    19 
       
    20 namespace Doctrine\DBAL;
       
    21 
       
    22 use PDO, Closure, Exception,
       
    23     Doctrine\DBAL\Types\Type,
       
    24     Doctrine\DBAL\Driver\Connection as DriverConnection,
       
    25     Doctrine\Common\EventManager,
       
    26     Doctrine\DBAL\DBALException;
       
    27 
       
    28 /**
       
    29  * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
       
    30  * events, transaction isolation levels, configuration, emulated transaction nesting,
       
    31  * lazy connecting and more.
       
    32  *
       
    33  * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
       
    34  * @link    www.doctrine-project.org
       
    35  * @since   2.0
       
    36  * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
       
    37  * @author  Jonathan Wage <jonwage@gmail.com>
       
    38  * @author  Roman Borschel <roman@code-factory.org>
       
    39  * @author  Konsta Vesterinen <kvesteri@cc.hut.fi>
       
    40  * @author  Lukas Smith <smith@pooteeweet.org> (MDB2 library)
       
    41  * @author  Benjamin Eberlei <kontakt@beberlei.de>
       
    42  */
       
    43 class Connection implements DriverConnection
       
    44 {
       
    45     /**
       
    46      * Constant for transaction isolation level READ UNCOMMITTED.
       
    47      */
       
    48     const TRANSACTION_READ_UNCOMMITTED = 1;
       
    49     
       
    50     /**
       
    51      * Constant for transaction isolation level READ COMMITTED.
       
    52      */
       
    53     const TRANSACTION_READ_COMMITTED = 2;
       
    54     
       
    55     /**
       
    56      * Constant for transaction isolation level REPEATABLE READ.
       
    57      */
       
    58     const TRANSACTION_REPEATABLE_READ = 3;
       
    59     
       
    60     /**
       
    61      * Constant for transaction isolation level SERIALIZABLE.
       
    62      */
       
    63     const TRANSACTION_SERIALIZABLE = 4;
       
    64     
       
    65     /**
       
    66      * Represents an array of ints to be expanded by Doctrine SQL parsing.
       
    67      * 
       
    68      * @var int
       
    69      */
       
    70     const PARAM_INT_ARRAY = 101;
       
    71     
       
    72     /**
       
    73      * Represents an array of strings to be expanded by Doctrine SQL parsing.
       
    74      * 
       
    75      * @var int
       
    76      */
       
    77     const PARAM_STR_ARRAY = 102;
       
    78     
       
    79     /**
       
    80      * Offset by which PARAM_* constants are detected as arrays of the param type.
       
    81      * 
       
    82      * @var int
       
    83      */
       
    84     const ARRAY_PARAM_OFFSET = 100;
       
    85 
       
    86     /**
       
    87      * The wrapped driver connection.
       
    88      *
       
    89      * @var Doctrine\DBAL\Driver\Connection
       
    90      */
       
    91     protected $_conn;
       
    92 
       
    93     /**
       
    94      * @var Doctrine\DBAL\Configuration
       
    95      */
       
    96     protected $_config;
       
    97 
       
    98     /**
       
    99      * @var Doctrine\Common\EventManager
       
   100      */
       
   101     protected $_eventManager;
       
   102     
       
   103     /**
       
   104      * @var Doctrine\DBAL\Query\ExpressionBuilder
       
   105      */
       
   106     protected $_expr;
       
   107 
       
   108     /**
       
   109      * Whether or not a connection has been established.
       
   110      *
       
   111      * @var boolean
       
   112      */
       
   113     private $_isConnected = false;
       
   114 
       
   115     /**
       
   116      * The transaction nesting level.
       
   117      *
       
   118      * @var integer
       
   119      */
       
   120     private $_transactionNestingLevel = 0;
       
   121 
       
   122     /**
       
   123      * The currently active transaction isolation level.
       
   124      *
       
   125      * @var integer
       
   126      */
       
   127     private $_transactionIsolationLevel;
       
   128 
       
   129     /**
       
   130      * If nested transations should use savepoints
       
   131      *
       
   132      * @var integer
       
   133      */
       
   134     private $_nestTransactionsWithSavepoints;
       
   135 
       
   136     /**
       
   137      * The parameters used during creation of the Connection instance.
       
   138      *
       
   139      * @var array
       
   140      */
       
   141     private $_params = array();
       
   142 
       
   143     /**
       
   144      * The DatabasePlatform object that provides information about the
       
   145      * database platform used by the connection.
       
   146      *
       
   147      * @var Doctrine\DBAL\Platforms\AbstractPlatform
       
   148      */
       
   149     protected $_platform;
       
   150 
       
   151     /**
       
   152      * The schema manager.
       
   153      *
       
   154      * @var Doctrine\DBAL\Schema\SchemaManager
       
   155      */
       
   156     protected $_schemaManager;
       
   157 
       
   158     /**
       
   159      * The used DBAL driver.
       
   160      *
       
   161      * @var Doctrine\DBAL\Driver
       
   162      */
       
   163     protected $_driver;
       
   164     
       
   165     /**
       
   166      * Flag that indicates whether the current transaction is marked for rollback only.
       
   167      * 
       
   168      * @var boolean
       
   169      */
       
   170     private $_isRollbackOnly = false;
       
   171 
       
   172     /**
       
   173      * Initializes a new instance of the Connection class.
       
   174      *
       
   175      * @param array $params  The connection parameters.
       
   176      * @param Driver $driver
       
   177      * @param Configuration $config
       
   178      * @param EventManager $eventManager
       
   179      */
       
   180     public function __construct(array $params, Driver $driver, Configuration $config = null,
       
   181             EventManager $eventManager = null)
       
   182     {
       
   183         $this->_driver = $driver;
       
   184         $this->_params = $params;
       
   185 
       
   186         if (isset($params['pdo'])) {
       
   187             $this->_conn = $params['pdo'];
       
   188             $this->_isConnected = true;
       
   189         }
       
   190 
       
   191         // Create default config and event manager if none given
       
   192         if ( ! $config) {
       
   193             $config = new Configuration();
       
   194         }
       
   195         
       
   196         if ( ! $eventManager) {
       
   197             $eventManager = new EventManager();
       
   198         }
       
   199 
       
   200         $this->_config = $config;
       
   201         $this->_eventManager = $eventManager;
       
   202         
       
   203         $this->_expr = new Query\Expression\ExpressionBuilder($this);
       
   204         
       
   205         if ( ! isset($params['platform'])) {
       
   206             $this->_platform = $driver->getDatabasePlatform();
       
   207         } else if ($params['platform'] instanceof Platforms\AbstractPlatform) {
       
   208             $this->_platform = $params['platform'];
       
   209         } else {
       
   210             throw DBALException::invalidPlatformSpecified();
       
   211         }
       
   212         
       
   213         $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel();
       
   214     }
       
   215 
       
   216     /**
       
   217      * Gets the parameters used during instantiation.
       
   218      *
       
   219      * @return array $params
       
   220      */
       
   221     public function getParams()
       
   222     {
       
   223         return $this->_params;
       
   224     }
       
   225 
       
   226     /**
       
   227      * Gets the name of the database this Connection is connected to.
       
   228      *
       
   229      * @return string $database
       
   230      */
       
   231     public function getDatabase()
       
   232     {
       
   233         return $this->_driver->getDatabase($this);
       
   234     }
       
   235     
       
   236     /**
       
   237      * Gets the hostname of the currently connected database.
       
   238      * 
       
   239      * @return string
       
   240      */
       
   241     public function getHost()
       
   242     {
       
   243         return isset($this->_params['host']) ? $this->_params['host'] : null;
       
   244     }
       
   245     
       
   246     /**
       
   247      * Gets the port of the currently connected database.
       
   248      * 
       
   249      * @return mixed
       
   250      */
       
   251     public function getPort()
       
   252     {
       
   253         return isset($this->_params['port']) ? $this->_params['port'] : null;
       
   254     }
       
   255     
       
   256     /**
       
   257      * Gets the username used by this connection.
       
   258      * 
       
   259      * @return string
       
   260      */
       
   261     public function getUsername()
       
   262     {
       
   263         return isset($this->_params['user']) ? $this->_params['user'] : null;
       
   264     }
       
   265     
       
   266     /**
       
   267      * Gets the password used by this connection.
       
   268      * 
       
   269      * @return string
       
   270      */
       
   271     public function getPassword()
       
   272     {
       
   273         return isset($this->_params['password']) ? $this->_params['password'] : null;
       
   274     }
       
   275 
       
   276     /**
       
   277      * Gets the DBAL driver instance.
       
   278      *
       
   279      * @return Doctrine\DBAL\Driver
       
   280      */
       
   281     public function getDriver()
       
   282     {
       
   283         return $this->_driver;
       
   284     }
       
   285 
       
   286     /**
       
   287      * Gets the Configuration used by the Connection.
       
   288      *
       
   289      * @return Doctrine\DBAL\Configuration
       
   290      */
       
   291     public function getConfiguration()
       
   292     {
       
   293         return $this->_config;
       
   294     }
       
   295 
       
   296     /**
       
   297      * Gets the EventManager used by the Connection.
       
   298      *
       
   299      * @return Doctrine\Common\EventManager
       
   300      */
       
   301     public function getEventManager()
       
   302     {
       
   303         return $this->_eventManager;
       
   304     }
       
   305 
       
   306     /**
       
   307      * Gets the DatabasePlatform for the connection.
       
   308      *
       
   309      * @return Doctrine\DBAL\Platforms\AbstractPlatform
       
   310      */
       
   311     public function getDatabasePlatform()
       
   312     {
       
   313         return $this->_platform;
       
   314     }
       
   315     
       
   316     /**
       
   317      * Gets the ExpressionBuilder for the connection.
       
   318      *
       
   319      * @return Doctrine\DBAL\Query\ExpressionBuilder
       
   320      */
       
   321     public function getExpressionBuilder()
       
   322     {
       
   323         return $this->_expr;
       
   324     }
       
   325     
       
   326     /**
       
   327      * Establishes the connection with the database.
       
   328      *
       
   329      * @return boolean TRUE if the connection was successfully established, FALSE if
       
   330      *                 the connection is already open.
       
   331      */
       
   332     public function connect()
       
   333     {
       
   334         if ($this->_isConnected) return false;
       
   335 
       
   336         $driverOptions = isset($this->_params['driverOptions']) ?
       
   337                 $this->_params['driverOptions'] : array();
       
   338         $user = isset($this->_params['user']) ? $this->_params['user'] : null;
       
   339         $password = isset($this->_params['password']) ?
       
   340                 $this->_params['password'] : null;
       
   341 
       
   342         $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
       
   343         $this->_isConnected = true;
       
   344 
       
   345         if ($this->_eventManager->hasListeners(Events::postConnect)) {
       
   346             $eventArgs = new Event\ConnectionEventArgs($this);
       
   347             $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
       
   348         }
       
   349 
       
   350         return true;
       
   351     }
       
   352 
       
   353     /**
       
   354      * Prepares and executes an SQL query and returns the first row of the result
       
   355      * as an associative array.
       
   356      * 
       
   357      * @param string $statement The SQL query.
       
   358      * @param array $params The query parameters.
       
   359      * @return array
       
   360      */
       
   361     public function fetchAssoc($statement, array $params = array())
       
   362     {
       
   363         return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_ASSOC);
       
   364     }
       
   365 
       
   366     /**
       
   367      * Prepares and executes an SQL query and returns the first row of the result
       
   368      * as a numerically indexed array.
       
   369      *
       
   370      * @param string $statement         sql query to be executed
       
   371      * @param array $params             prepared statement params
       
   372      * @return array
       
   373      */
       
   374     public function fetchArray($statement, array $params = array())
       
   375     {
       
   376         return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_NUM);
       
   377     }
       
   378 
       
   379     /**
       
   380      * Prepares and executes an SQL query and returns the value of a single column
       
   381      * of the first row of the result.
       
   382      * 
       
   383      * @param string $statement         sql query to be executed
       
   384      * @param array $params             prepared statement params
       
   385      * @param int $colnum               0-indexed column number to retrieve
       
   386      * @return mixed
       
   387      */
       
   388     public function fetchColumn($statement, array $params = array(), $colnum = 0)
       
   389     {
       
   390         return $this->executeQuery($statement, $params)->fetchColumn($colnum);
       
   391     }
       
   392 
       
   393     /**
       
   394      * Whether an actual connection to the database is established.
       
   395      *
       
   396      * @return boolean
       
   397      */
       
   398     public function isConnected()
       
   399     {
       
   400         return $this->_isConnected;
       
   401     }
       
   402 
       
   403     /**
       
   404      * Checks whether a transaction is currently active.
       
   405      * 
       
   406      * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
       
   407      */
       
   408     public function isTransactionActive()
       
   409     {
       
   410         return $this->_transactionNestingLevel > 0;
       
   411     }
       
   412 
       
   413     /**
       
   414      * Executes an SQL DELETE statement on a table.
       
   415      *
       
   416      * @param string $table The name of the table on which to delete.
       
   417      * @param array $identifier The deletion criteria. An associateve array containing column-value pairs.
       
   418      * @return integer The number of affected rows.
       
   419      */
       
   420     public function delete($tableName, array $identifier)
       
   421     {
       
   422         $this->connect();
       
   423 
       
   424         $criteria = array();
       
   425 
       
   426         foreach (array_keys($identifier) as $columnName) {
       
   427             $criteria[] = $columnName . ' = ?';
       
   428         }
       
   429 
       
   430         $query = 'DELETE FROM ' . $tableName . ' WHERE ' . implode(' AND ', $criteria);
       
   431 
       
   432         return $this->executeUpdate($query, array_values($identifier));
       
   433     }
       
   434 
       
   435     /**
       
   436      * Closes the connection.
       
   437      *
       
   438      * @return void
       
   439      */
       
   440     public function close()
       
   441     {
       
   442         unset($this->_conn);
       
   443         
       
   444         $this->_isConnected = false;
       
   445     }
       
   446 
       
   447     /**
       
   448      * Sets the transaction isolation level.
       
   449      *
       
   450      * @param integer $level The level to set.
       
   451      */
       
   452     public function setTransactionIsolation($level)
       
   453     {
       
   454         $this->_transactionIsolationLevel = $level;
       
   455         
       
   456         return $this->executeUpdate($this->_platform->getSetTransactionIsolationSQL($level));
       
   457     }
       
   458 
       
   459     /**
       
   460      * Gets the currently active transaction isolation level.
       
   461      *
       
   462      * @return integer The current transaction isolation level.
       
   463      */
       
   464     public function getTransactionIsolation()
       
   465     {
       
   466         return $this->_transactionIsolationLevel;
       
   467     }
       
   468 
       
   469     /**
       
   470      * Executes an SQL UPDATE statement on a table.
       
   471      *
       
   472      * @param string $table The name of the table to update.
       
   473      * @param array $identifier The update criteria. An associative array containing column-value pairs.
       
   474      * @return integer The number of affected rows.
       
   475      */
       
   476     public function update($tableName, array $data, array $identifier)
       
   477     {
       
   478         $this->connect();
       
   479         $set = array();
       
   480         foreach ($data as $columnName => $value) {
       
   481             $set[] = $columnName . ' = ?';
       
   482         }
       
   483 
       
   484         $params = array_merge(array_values($data), array_values($identifier));
       
   485 
       
   486         $sql  = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set)
       
   487                 . ' WHERE ' . implode(' = ? AND ', array_keys($identifier))
       
   488                 . ' = ?';
       
   489 
       
   490         return $this->executeUpdate($sql, $params);
       
   491     }
       
   492 
       
   493     /**
       
   494      * Inserts a table row with specified data.
       
   495      *
       
   496      * @param string $table The name of the table to insert data into.
       
   497      * @param array $data An associative array containing column-value pairs.
       
   498      * @return integer The number of affected rows.
       
   499      */
       
   500     public function insert($tableName, array $data)
       
   501     {
       
   502         $this->connect();
       
   503 
       
   504         // column names are specified as array keys
       
   505         $cols = array();
       
   506         $placeholders = array();
       
   507         
       
   508         foreach ($data as $columnName => $value) {
       
   509             $cols[] = $columnName;
       
   510             $placeholders[] = '?';
       
   511         }
       
   512 
       
   513         $query = 'INSERT INTO ' . $tableName
       
   514                . ' (' . implode(', ', $cols) . ')'
       
   515                . ' VALUES (' . implode(', ', $placeholders) . ')';
       
   516 
       
   517         return $this->executeUpdate($query, array_values($data));
       
   518     }
       
   519 
       
   520     /**
       
   521      * Sets the given charset on the current connection.
       
   522      *
       
   523      * @param string $charset The charset to set.
       
   524      */
       
   525     public function setCharset($charset)
       
   526     {
       
   527         $this->executeUpdate($this->_platform->getSetCharsetSQL($charset));
       
   528     }
       
   529 
       
   530     /**
       
   531      * Quote a string so it can be safely used as a table or column name, even if
       
   532      * it is a reserved name.
       
   533      *
       
   534      * Delimiting style depends on the underlying database platform that is being used.
       
   535      *
       
   536      * NOTE: Just because you CAN use quoted identifiers does not mean
       
   537      * you SHOULD use them. In general, they end up causing way more
       
   538      * problems than they solve.
       
   539      *
       
   540      * @param string $str The name to be quoted.
       
   541      * @return string The quoted name.
       
   542      */
       
   543     public function quoteIdentifier($str)
       
   544     {
       
   545         return $this->_platform->quoteIdentifier($str);
       
   546     }
       
   547 
       
   548     /**
       
   549      * Quotes a given input parameter.
       
   550      *
       
   551      * @param mixed $input Parameter to be quoted.
       
   552      * @param string $type Type of the parameter.
       
   553      * @return string The quoted parameter.
       
   554      */
       
   555     public function quote($input, $type = null)
       
   556     {
       
   557         $this->connect();
       
   558         
       
   559         return $this->_conn->quote($input, $type);
       
   560     }
       
   561 
       
   562     /**
       
   563      * Prepares and executes an SQL query and returns the result as an associative array.
       
   564      *
       
   565      * @param string $sql The SQL query.
       
   566      * @param array $params The query parameters.
       
   567      * @return array
       
   568      */
       
   569     public function fetchAll($sql, array $params = array())
       
   570     {
       
   571         return $this->executeQuery($sql, $params)->fetchAll(PDO::FETCH_ASSOC);
       
   572     }
       
   573 
       
   574     /**
       
   575      * Prepares an SQL statement.
       
   576      *
       
   577      * @param string $statement The SQL statement to prepare.
       
   578      * @return Doctrine\DBAL\Driver\Statement The prepared statement.
       
   579      */
       
   580     public function prepare($statement)
       
   581     {
       
   582         $this->connect();
       
   583 
       
   584         return new Statement($statement, $this);
       
   585     }
       
   586 
       
   587     /**
       
   588      * Executes an, optionally parameterized, SQL query.
       
   589      *
       
   590      * If the query is parameterized, a prepared statement is used.
       
   591      * If an SQLLogger is configured, the execution is logged.
       
   592      *
       
   593      * @param string $query The SQL query to execute.
       
   594      * @param array $params The parameters to bind to the query, if any.
       
   595      * @return Doctrine\DBAL\Driver\Statement The executed statement.
       
   596      * @internal PERF: Directly prepares a driver statement, not a wrapper.
       
   597      */
       
   598     public function executeQuery($query, array $params = array(), $types = array())
       
   599     {
       
   600         $this->connect();
       
   601 
       
   602         $hasLogger = $this->_config->getSQLLogger() !== null;
       
   603         if ($hasLogger) {
       
   604             $this->_config->getSQLLogger()->startQuery($query, $params, $types);
       
   605         }
       
   606 
       
   607         if ($params) {
       
   608             list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
       
   609             
       
   610             $stmt = $this->_conn->prepare($query);
       
   611             if ($types) {
       
   612                 $this->_bindTypedValues($stmt, $params, $types);
       
   613                 $stmt->execute();
       
   614             } else {
       
   615                 $stmt->execute($params);
       
   616             }
       
   617         } else {
       
   618             $stmt = $this->_conn->query($query);
       
   619         }
       
   620 
       
   621         if ($hasLogger) {
       
   622             $this->_config->getSQLLogger()->stopQuery();
       
   623         }
       
   624 
       
   625         return $stmt;
       
   626     }
       
   627 
       
   628     /**
       
   629      * Executes an, optionally parameterized, SQL query and returns the result,
       
   630      * applying a given projection/transformation function on each row of the result.
       
   631      *
       
   632      * @param string $query The SQL query to execute.
       
   633      * @param array $params The parameters, if any.
       
   634      * @param Closure $mapper The transformation function that is applied on each row.
       
   635      *                        The function receives a single paramater, an array, that
       
   636      *                        represents a row of the result set.
       
   637      * @return mixed The projected result of the query.
       
   638      */
       
   639     public function project($query, array $params, Closure $function)
       
   640     {
       
   641         $result = array();
       
   642         $stmt = $this->executeQuery($query, $params ?: array());
       
   643 
       
   644         while ($row = $stmt->fetch()) {
       
   645             $result[] = $function($row);
       
   646         }
       
   647 
       
   648         $stmt->closeCursor();
       
   649 
       
   650         return $result;
       
   651     }
       
   652 
       
   653     /**
       
   654      * Executes an SQL statement, returning a result set as a Statement object.
       
   655      * 
       
   656      * @param string $statement
       
   657      * @param integer $fetchType
       
   658      * @return Doctrine\DBAL\Driver\Statement
       
   659      */
       
   660     public function query()
       
   661     {
       
   662         $this->connect();
       
   663 
       
   664         $args = func_get_args();
       
   665 
       
   666         $logger = $this->getConfiguration()->getSQLLogger();
       
   667         if ($logger) {
       
   668             $logger->startQuery($args[0]);
       
   669         }
       
   670 
       
   671         $statement = call_user_func_array(array($this->_conn, 'query'), $args);
       
   672 
       
   673         if ($logger) {
       
   674             $logger->stopQuery();
       
   675         }
       
   676 
       
   677         return $statement;
       
   678     }
       
   679 
       
   680     /**
       
   681      * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
       
   682      * and returns the number of affected rows.
       
   683      * 
       
   684      * This method supports PDO binding types as well as DBAL mapping types.
       
   685      *
       
   686      * @param string $query The SQL query.
       
   687      * @param array $params The query parameters.
       
   688      * @param array $types The parameter types.
       
   689      * @return integer The number of affected rows.
       
   690      * @internal PERF: Directly prepares a driver statement, not a wrapper.
       
   691      */
       
   692     public function executeUpdate($query, array $params = array(), array $types = array())
       
   693     {
       
   694         $this->connect();
       
   695 
       
   696         $hasLogger = $this->_config->getSQLLogger() !== null;
       
   697         if ($hasLogger) {
       
   698             $this->_config->getSQLLogger()->startQuery($query, $params, $types);
       
   699         }
       
   700 
       
   701         if ($params) {
       
   702             list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
       
   703             
       
   704             $stmt = $this->_conn->prepare($query);
       
   705             if ($types) {
       
   706                 $this->_bindTypedValues($stmt, $params, $types);
       
   707                 $stmt->execute();
       
   708             } else {
       
   709                 $stmt->execute($params);
       
   710             }
       
   711             $result = $stmt->rowCount();
       
   712         } else {
       
   713             $result = $this->_conn->exec($query);
       
   714         }
       
   715 
       
   716         if ($hasLogger) {
       
   717             $this->_config->getSQLLogger()->stopQuery();
       
   718         }
       
   719 
       
   720         return $result;
       
   721     }
       
   722 
       
   723     /**
       
   724      * Execute an SQL statement and return the number of affected rows.
       
   725      * 
       
   726      * @param string $statement
       
   727      * @return integer The number of affected rows.
       
   728      */
       
   729     public function exec($statement)
       
   730     {
       
   731         $this->connect();
       
   732         return $this->_conn->exec($statement);
       
   733     }
       
   734 
       
   735     /**
       
   736      * Returns the current transaction nesting level.
       
   737      *
       
   738      * @return integer The nesting level. A value of 0 means there's no active transaction.
       
   739      */
       
   740     public function getTransactionNestingLevel()
       
   741     {
       
   742         return $this->_transactionNestingLevel;
       
   743     }
       
   744 
       
   745     /**
       
   746      * Fetch the SQLSTATE associated with the last database operation.
       
   747      *
       
   748      * @return integer The last error code.
       
   749      */
       
   750     public function errorCode()
       
   751     {
       
   752         $this->connect();
       
   753         return $this->_conn->errorCode();
       
   754     }
       
   755 
       
   756     /**
       
   757      * Fetch extended error information associated with the last database operation.
       
   758      *
       
   759      * @return array The last error information.
       
   760      */
       
   761     public function errorInfo()
       
   762     {
       
   763         $this->connect();
       
   764         return $this->_conn->errorInfo();
       
   765     }
       
   766 
       
   767     /**
       
   768      * Returns the ID of the last inserted row, or the last value from a sequence object,
       
   769      * depending on the underlying driver.
       
   770      *
       
   771      * Note: This method may not return a meaningful or consistent result across different drivers,
       
   772      * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
       
   773      * columns or sequences.
       
   774      *
       
   775      * @param string $seqName Name of the sequence object from which the ID should be returned.
       
   776      * @return string A string representation of the last inserted ID.
       
   777      */
       
   778     public function lastInsertId($seqName = null)
       
   779     {
       
   780         $this->connect();
       
   781         return $this->_conn->lastInsertId($seqName);
       
   782     }
       
   783 
       
   784     /**
       
   785      * Executes a function in a transaction.
       
   786      *
       
   787      * The function gets passed this Connection instance as an (optional) parameter.
       
   788      *
       
   789      * If an exception occurs during execution of the function or transaction commit,
       
   790      * the transaction is rolled back and the exception re-thrown.
       
   791      *
       
   792      * @param Closure $func The function to execute transactionally.
       
   793      */
       
   794     public function transactional(Closure $func)
       
   795     {
       
   796         $this->beginTransaction();
       
   797         try {
       
   798             $func($this);
       
   799             $this->commit();
       
   800         } catch (Exception $e) {
       
   801             $this->rollback();
       
   802             throw $e;
       
   803         }
       
   804     }
       
   805 
       
   806     /**
       
   807      * Set if nested transactions should use savepoints
       
   808      *
       
   809      * @param boolean
       
   810      * @return void
       
   811      */
       
   812     public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
       
   813     {
       
   814         if ($this->_transactionNestingLevel > 0) {
       
   815             throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
       
   816         }
       
   817 
       
   818         if (!$this->_platform->supportsSavepoints()) {
       
   819             throw ConnectionException::savepointsNotSupported();
       
   820         }
       
   821 
       
   822         $this->_nestTransactionsWithSavepoints = $nestTransactionsWithSavepoints;
       
   823     }
       
   824 
       
   825     /**
       
   826      * Get if nested transactions should use savepoints
       
   827      *
       
   828      * @return boolean
       
   829      */
       
   830     public function getNestTransactionsWithSavepoints()
       
   831     {
       
   832         return $this->_nestTransactionsWithSavepoints;
       
   833     }
       
   834 
       
   835     /**
       
   836      * Returns the savepoint name to use for nested transactions are false if they are not supported
       
   837      * "savepointFormat" parameter is not set
       
   838      *
       
   839      * @return mixed a string with the savepoint name or false
       
   840      */
       
   841     protected function _getNestedTransactionSavePointName()
       
   842     {
       
   843         return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
       
   844     }
       
   845 
       
   846     /**
       
   847      * Starts a transaction by suspending auto-commit mode.
       
   848      *
       
   849      * @return void
       
   850      */
       
   851     public function beginTransaction()
       
   852     {
       
   853         $this->connect();
       
   854 
       
   855         ++$this->_transactionNestingLevel;
       
   856 
       
   857         if ($this->_transactionNestingLevel == 1) {
       
   858             $this->_conn->beginTransaction();
       
   859         } else if ($this->_nestTransactionsWithSavepoints) {
       
   860             $this->createSavepoint($this->_getNestedTransactionSavePointName());
       
   861         }
       
   862     }
       
   863 
       
   864     /**
       
   865      * Commits the current transaction.
       
   866      *
       
   867      * @return void
       
   868      * @throws ConnectionException If the commit failed due to no active transaction or
       
   869      *                             because the transaction was marked for rollback only.
       
   870      */
       
   871     public function commit()
       
   872     {
       
   873         if ($this->_transactionNestingLevel == 0) {
       
   874             throw ConnectionException::noActiveTransaction();
       
   875         }
       
   876         if ($this->_isRollbackOnly) {
       
   877             throw ConnectionException::commitFailedRollbackOnly();
       
   878         }
       
   879 
       
   880         $this->connect();
       
   881 
       
   882         if ($this->_transactionNestingLevel == 1) {
       
   883             $this->_conn->commit();
       
   884         } else if ($this->_nestTransactionsWithSavepoints) {
       
   885             $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
       
   886         }
       
   887 
       
   888         --$this->_transactionNestingLevel;
       
   889     }
       
   890 
       
   891     /**
       
   892      * Cancel any database changes done during the current transaction.
       
   893      *
       
   894      * this method can be listened with onPreTransactionRollback and onTransactionRollback
       
   895      * eventlistener methods
       
   896      *
       
   897      * @throws ConnectionException If the rollback operation failed.
       
   898      */
       
   899     public function rollback()
       
   900     {
       
   901         if ($this->_transactionNestingLevel == 0) {
       
   902             throw ConnectionException::noActiveTransaction();
       
   903         }
       
   904 
       
   905         $this->connect();
       
   906 
       
   907         if ($this->_transactionNestingLevel == 1) {
       
   908             $this->_transactionNestingLevel = 0;
       
   909             $this->_conn->rollback();
       
   910             $this->_isRollbackOnly = false;
       
   911         } else if ($this->_nestTransactionsWithSavepoints) {
       
   912             $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
       
   913             --$this->_transactionNestingLevel;
       
   914         } else {
       
   915             $this->_isRollbackOnly = true;
       
   916             --$this->_transactionNestingLevel;
       
   917         }
       
   918     }
       
   919 
       
   920     /**
       
   921      * createSavepoint
       
   922      * creates a new savepoint
       
   923      *
       
   924      * @param string $savepoint     name of a savepoint to set
       
   925      * @return void
       
   926      */
       
   927     public function createSavepoint($savepoint)
       
   928     {
       
   929         if (!$this->_platform->supportsSavepoints()) {
       
   930             throw ConnectionException::savepointsNotSupported();
       
   931         }
       
   932 
       
   933         $this->_conn->exec($this->_platform->createSavePoint($savepoint));
       
   934     }
       
   935 
       
   936     /**
       
   937      * releaseSavePoint
       
   938      * releases given savepoint
       
   939      *
       
   940      * @param string $savepoint     name of a savepoint to release
       
   941      * @return void
       
   942      */
       
   943     public function releaseSavepoint($savepoint)
       
   944     {
       
   945         if (!$this->_platform->supportsSavepoints()) {
       
   946             throw ConnectionException::savepointsNotSupported();
       
   947         }
       
   948 
       
   949         if ($this->_platform->supportsReleaseSavepoints()) {
       
   950             $this->_conn->exec($this->_platform->releaseSavePoint($savepoint));
       
   951         }
       
   952     }
       
   953 
       
   954     /**
       
   955      * rollbackSavePoint
       
   956      * releases given savepoint
       
   957      *
       
   958      * @param string $savepoint     name of a savepoint to rollback to
       
   959      * @return void
       
   960      */
       
   961     public function rollbackSavepoint($savepoint)
       
   962     {
       
   963         if (!$this->_platform->supportsSavepoints()) {
       
   964             throw ConnectionException::savepointsNotSupported();
       
   965         }
       
   966 
       
   967         $this->_conn->exec($this->_platform->rollbackSavePoint($savepoint));
       
   968     }
       
   969 
       
   970     /**
       
   971      * Gets the wrapped driver connection.
       
   972      *
       
   973      * @return Doctrine\DBAL\Driver\Connection
       
   974      */
       
   975     public function getWrappedConnection()
       
   976     {
       
   977         $this->connect();
       
   978 
       
   979         return $this->_conn;
       
   980     }
       
   981 
       
   982     /**
       
   983      * Gets the SchemaManager that can be used to inspect or change the
       
   984      * database schema through the connection.
       
   985      *
       
   986      * @return Doctrine\DBAL\Schema\SchemaManager
       
   987      */
       
   988     public function getSchemaManager()
       
   989     {
       
   990         if ( ! $this->_schemaManager) {
       
   991             $this->_schemaManager = $this->_driver->getSchemaManager($this);
       
   992         }
       
   993 
       
   994         return $this->_schemaManager;
       
   995     }
       
   996 
       
   997     /**
       
   998      * Marks the current transaction so that the only possible
       
   999      * outcome for the transaction to be rolled back.
       
  1000      * 
       
  1001      * @throws ConnectionException If no transaction is active.
       
  1002      */
       
  1003     public function setRollbackOnly()
       
  1004     {
       
  1005         if ($this->_transactionNestingLevel == 0) {
       
  1006             throw ConnectionException::noActiveTransaction();
       
  1007         }
       
  1008         $this->_isRollbackOnly = true;
       
  1009     }
       
  1010 
       
  1011     /**
       
  1012      * Check whether the current transaction is marked for rollback only.
       
  1013      * 
       
  1014      * @return boolean
       
  1015      * @throws ConnectionException If no transaction is active.
       
  1016      */
       
  1017     public function isRollbackOnly()
       
  1018     {
       
  1019         if ($this->_transactionNestingLevel == 0) {
       
  1020             throw ConnectionException::noActiveTransaction();
       
  1021         }
       
  1022         return $this->_isRollbackOnly;
       
  1023     }
       
  1024 
       
  1025     /**
       
  1026      * Converts a given value to its database representation according to the conversion
       
  1027      * rules of a specific DBAL mapping type.
       
  1028      * 
       
  1029      * @param mixed $value The value to convert.
       
  1030      * @param string $type The name of the DBAL mapping type.
       
  1031      * @return mixed The converted value.
       
  1032      */
       
  1033     public function convertToDatabaseValue($value, $type)
       
  1034     {
       
  1035         return Type::getType($type)->convertToDatabaseValue($value, $this->_platform);
       
  1036     }
       
  1037 
       
  1038     /**
       
  1039      * Converts a given value to its PHP representation according to the conversion
       
  1040      * rules of a specific DBAL mapping type.
       
  1041      * 
       
  1042      * @param mixed $value The value to convert.
       
  1043      * @param string $type The name of the DBAL mapping type.
       
  1044      * @return mixed The converted type.
       
  1045      */
       
  1046     public function convertToPHPValue($value, $type)
       
  1047     {
       
  1048         return Type::getType($type)->convertToPHPValue($value, $this->_platform);
       
  1049     }
       
  1050 
       
  1051     /**
       
  1052      * Binds a set of parameters, some or all of which are typed with a PDO binding type
       
  1053      * or DBAL mapping type, to a given statement.
       
  1054      * 
       
  1055      * @param $stmt The statement to bind the values to.
       
  1056      * @param array $params The map/list of named/positional parameters.
       
  1057      * @param array $types The parameter types (PDO binding types or DBAL mapping types).
       
  1058      * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
       
  1059      *           raw PDOStatement instances.
       
  1060      */
       
  1061     private function _bindTypedValues($stmt, array $params, array $types)
       
  1062     {
       
  1063         // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
       
  1064         if (is_int(key($params))) {
       
  1065             // Positional parameters
       
  1066             $typeOffset = array_key_exists(0, $types) ? -1 : 0;
       
  1067             $bindIndex = 1;
       
  1068             foreach ($params as $position => $value) {
       
  1069                 $typeIndex = $bindIndex + $typeOffset;
       
  1070                 if (isset($types[$typeIndex])) {
       
  1071                     $type = $types[$typeIndex];
       
  1072                     if (is_string($type)) {
       
  1073                         $type = Type::getType($type);
       
  1074                     }
       
  1075                     if ($type instanceof Type) {
       
  1076                         $value = $type->convertToDatabaseValue($value, $this->_platform);
       
  1077                         $bindingType = $type->getBindingType();
       
  1078                     } else {
       
  1079                         $bindingType = $type; // PDO::PARAM_* constants
       
  1080                     }
       
  1081                     $stmt->bindValue($bindIndex, $value, $bindingType);
       
  1082                 } else {
       
  1083                     $stmt->bindValue($bindIndex, $value);
       
  1084                 }
       
  1085                 ++$bindIndex;
       
  1086             }
       
  1087         } else {
       
  1088             // Named parameters
       
  1089             foreach ($params as $name => $value) {
       
  1090                 if (isset($types[$name])) {
       
  1091                     $type = $types[$name];
       
  1092                     if (is_string($type)) {
       
  1093                         $type = Type::getType($type);
       
  1094                     }
       
  1095                     if ($type instanceof Type) {
       
  1096                         $value = $type->convertToDatabaseValue($value, $this->_platform);
       
  1097                         $bindingType = $type->getBindingType();
       
  1098                     } else {
       
  1099                         $bindingType = $type; // PDO::PARAM_* constants
       
  1100                     }
       
  1101                     $stmt->bindValue($name, $value, $bindingType);
       
  1102                 } else {
       
  1103                     $stmt->bindValue($name, $value);
       
  1104                 }
       
  1105             }
       
  1106         }
       
  1107     }
       
  1108     
       
  1109     /**
       
  1110      * Create a new instance of a SQL query builder.
       
  1111      * 
       
  1112      * @return Query\QueryBuilder 
       
  1113      */
       
  1114     public function createQueryBuilder()
       
  1115     {
       
  1116         return new Query\QueryBuilder($this);
       
  1117     }
       
  1118 }