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