Coverage for Doctrine_Connection

Back to coverage report

1 <?php
2 /*
3  *  $Id: Connection.php 3223 2007-11-25 19:07:30Z romanb $
4  *
5  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16  *
17  * This software consists of voluntary contributions made by many individuals
18  * and is licensed under the LGPL. For more information, see
19  * <http://www.phpdoctrine.org>.
20  */
21 Doctrine::autoload('Doctrine_Configurable');
22 /**
23  * Doctrine_Connection
24  *
25  * A wrapper layer on top of PDO / Doctrine_Adapter
26  *
27  * Doctrine_Connection is the heart of any Doctrine based application.
28  *
29  * 1. Event listeners
30  *    An easy to use, pluggable eventlistener architecture. Aspects such as
31  *    logging, query profiling and caching can be easily implemented through
32  *    the use of these listeners
33  *
34  * 2. Lazy-connecting
35  *    Creating an instance of Doctrine_Connection does not connect
36  *    to database. Connecting to database is only invoked when actually needed
37  *    (for example when query() is being called) 
38  *
39  * 3. Convenience methods
40  *    Doctrine_Connection provides many convenience methods such as fetchAll(), fetchOne() etc.
41  *
42  * 4. Modular structure
43  *    Higher level functionality such as schema importing, exporting, sequence handling etc.
44  *    is divided into modules. For a full list of connection modules see 
45  *    Doctrine_Connection::$_modules
46  *
47  * @package     Doctrine
48  * @subpackage  Connection
49  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
50  * @link        www.phpdoctrine.org
51  * @since       1.0
52  * @version     $Revision: 3223 $
53  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
54  * @author      Lukas Smith <smith@pooteeweet.org> (MDB2 library)
55  */
56 abstract class Doctrine_Connection extends Doctrine_Configurable implements Countable, IteratorAggregate
57 {
58     /**
59      * @var $dbh                                the database handler
60      */
61     protected $dbh;
62
63     /**
64      * @var array $tables                       an array containing all the initialized Doctrine_Table objects
65      *                                          keys representing Doctrine_Table component names and values as Doctrine_Table objects
66      */
67     protected $tables           = array();
68
69     /**
70      * @var string $driverName                  the name of this connection driver
71      */
72     protected $driverName;
73
74     /**
75      * @var boolean $isConnected                whether or not a connection has been established
76      */
77     protected $isConnected      = false;
78
79     /**
80      * @var array $supported                    an array containing all features this driver supports,
81      *                                          keys representing feature names and values as
82      *                                          one of the following (true, false, 'emulated')
83      */
84     protected $supported        = array();
85
86     /**
87      * @var array $pendingAttributes            An array of pending attributes. When setting attributes
88      *                                          no connection is needed. When connected all the pending
89      *                                          attributes are passed to the underlying adapter (usually PDO) instance.
90      */
91     protected $pendingAttributes  = array();
92
93     /**
94      * @var array $modules                      an array containing all modules
95      *              transaction                 Doctrine_Transaction driver, handles savepoint and transaction isolation abstraction
96      *
97      *              expression                  Doctrine_Expression driver, handles expression abstraction
98      *
99      *              dataDict                    Doctrine_DataDict driver, handles datatype abstraction
100      *
101      *              export                      Doctrine_Export driver, handles db structure modification abstraction (contains
102      *                                          methods such as alterTable, createConstraint etc.)
103      *              import                      Doctrine_Import driver, handles db schema reading
104      *
105      *              sequence                    Doctrine_Sequence driver, handles sequential id generation and retrieval
106      *
107      *              unitOfWork                  Doctrine_Connection_UnitOfWork handles many orm functionalities such as object
108      *                                          deletion and saving
109      *
110      *              formatter                   Doctrine_Formatter handles data formatting, quoting and escaping
111      *
112      * @see Doctrine_Connection::__get()
113      * @see Doctrine_DataDict
114      * @see Doctrine_Expression
115      * @see Doctrine_Export
116      * @see Doctrine_Transaction
117      * @see Doctrine_Sequence
118      * @see Doctrine_Connection_UnitOfWork
119      * @see Doctrine_Formatter
120      */
121     private $modules = array('transaction' => false,
122                              'expression'  => false,
123                              'dataDict'    => false,
124                              'export'      => false,
125                              'import'      => false,
126                              'sequence'    => false,
127                              'unitOfWork'  => false,
128                              'formatter'   => false,
129                              'util'        => false,
130                              );
131
132     /**
133      * @var array $properties               an array of connection properties
134      */
135     protected $properties = array('sql_comments'        => array(array('start' => '--', 'end' => "\n", 'escape' => false),
136                                                                  array('start' => '/*', 'end' => '*/', 'escape' => false)),
137                                   'identifier_quoting'  => array('start' => '"', 'end' => '"','escape' => '"'),
138                                   'string_quoting'      => array('start' => "'",
139                                                                  'end' => "'",
140                                                                  'escape' => false,
141                                                                  'escape_pattern' => false),
142                                   'wildcards'           => array('%', '_'),
143                                   'varchar_max_length'  => 255,
144                                   );
145
146     /**
147      * @var array $serverInfo
148      */
149     protected $serverInfo = array();
150     
151     protected $options    = array();
152
153     /**
154      * @var array $availableDrivers         an array containing all available drivers
155      */
156     private static $availableDrivers    = array(
157                                         'Mysql',
158                                         'Pgsql',
159                                         'Oracle',
160                                         'Informix',
161                                         'Mssql',
162                                         'Sqlite',
163                                         'Firebird'
164                                         );
165     protected $_count = 0;
166
167     /**
168      * the constructor
169      *
170      * @param Doctrine_Manager $manager                 the manager object
171      * @param PDO|Doctrine_Adapter_Interface $adapter   database driver
172      */
173     public function __construct(Doctrine_Manager $manager, $adapter, $user = null, $pass = null)
174     {
175         if (is_object($adapter)) {
176             if ( ! ($adapter instanceof PDO) && ! in_array('Doctrine_Adapter_Interface', class_implements($adapter))) {
177                 throw new Doctrine_Connection_Exception('First argument should be an instance of PDO or implement Doctrine_Adapter_Interface');
178             }
179             $this->dbh = $adapter;
180
181             $this->isConnected = true;
182
183         } else if (is_array($adapter)) {
184             $this->pendingAttributes[Doctrine::ATTR_DRIVER_NAME] = $adapter['scheme'];
185
186             $this->options['dsn']      = $adapter['dsn'];
187             $this->options['username'] = $adapter['user'];
188             $this->options['password'] = $adapter['pass'];
189             
190             $this->options['other'] = array();  
191             if (isset($adapter['other'])) {
192                 $this->options['other'] = array(Doctrine::ATTR_PERSISTENT => $adapter['persistent']);
193             }
194
195         }
196
197         $this->setParent($manager);
198
199         $this->setAttribute(Doctrine::ATTR_CASE, Doctrine::CASE_NATURAL);
200         $this->setAttribute(Doctrine::ATTR_ERRMODE, Doctrine::ERRMODE_EXCEPTION);
201
202         $this->getAttribute(Doctrine::ATTR_LISTENER)->onOpen($this);
203     }
204
205     /**
206      * getOption
207      * 
208      * Retrieves option
209      *
210      * @param string $option 
211      * @return void
212      */
213     public function getOption($option)
214     {
215         if (isset($this->options[$option])) {
216             return $this->options[$option];
217         }
218     }
219
220     /**
221      * getAttribute
222      * retrieves a database connection attribute
223      *
224      * @param integer $attribute
225      * @return mixed
226      */
227     public function getAttribute($attribute)
228     {
229
230         if ($attribute >= 100) {
231             if ( ! isset($this->attributes[$attribute])) {
232                 return parent::getAttribute($attribute);
233             }
234             return $this->attributes[$attribute];
235         }
236
237         if ($this->isConnected) {
238             try {
239                 return $this->dbh->getAttribute($attribute);
240             } catch(Exception $e) {
241                 throw new Doctrine_Connection_Exception('Attribute ' . $attribute . ' not found.');
242             }
243         } else {
244             if ( ! isset($this->pendingAttributes[$attribute])) {
245                 $this->connect();
246                 $this->getAttribute($attribute);
247             }
248
249             return $this->pendingAttributes[$attribute];
250         }
251     }
252
253     /**
254      * returns an array of available PDO drivers
255      */
256     public static function getAvailableDrivers()
257     {
258         return PDO::getAvailableDrivers();
259     }
260
261     /**
262      * setAttribute
263      * sets an attribute
264      *
265      * @todo why check for >= 100? has this any special meaning when creating 
266      * attributes?
267      *
268      * @param integer $attribute
269      * @param mixed $value
270      * @return boolean
271      */
272     public function setAttribute($attribute, $value)
273     {
274         if ($attribute >= 100) {
275             parent::setAttribute($attribute, $value);
276         } else {
277             if ($this->isConnected) {
278                 $this->dbh->setAttribute($attribute, $value);
279             } else {
280                 $this->pendingAttributes[$attribute] = $value;
281             }
282         }
283         return $this;
284     }
285
286     /**
287      * getName
288      * returns the name of this driver
289      *
290      * @return string           the name of this driver
291      */
292     public function getName()
293     {
294         return $this->driverName;
295     }
296
297     /**
298      * __get
299      * lazy loads given module and returns it
300      *
301      * @see Doctrine_DataDict
302      * @see Doctrine_Expression
303      * @see Doctrine_Export
304      * @see Doctrine_Transaction
305      * @see Doctrine_Connection::$modules       all availible modules
306      * @param string $name                      the name of the module to get
307      * @throws Doctrine_Connection_Exception    if trying to get an unknown module
308      * @return Doctrine_Connection_Module       connection module
309      */
310     public function __get($name)
311     {
312         if (isset($this->properties[$name])) {
313             return $this->properties[$name];
314         }
315
316         if ( ! isset($this->modules[$name])) {
317             throw new Doctrine_Connection_Exception('Unknown module / property ' . $name);
318         }
319         if ($this->modules[$name] === false) {
320             switch ($name) {
321                 case 'unitOfWork':
322                     $this->modules[$name] = new Doctrine_Connection_UnitOfWork($this);
323                     break;
324                 case 'formatter':
325                     $this->modules[$name] = new Doctrine_Formatter($this);
326                     break;
327                 default:
328                     $class = 'Doctrine_' . ucwords($name) . '_' . $this->getName();
329                     $this->modules[$name] = new $class($this);
330                 }
331         }
332
333         return $this->modules[$name];
334     }
335
336     /**
337      * returns the manager that created this connection
338      *
339      * @return Doctrine_Manager
340      */
341     public function getManager()
342     {
343         return $this->getParent();
344     }
345
346     /**
347      * returns the database handler of which this connection uses
348      *
349      * @return PDO              the database handler
350      */
351     public function getDbh()
352     {
353         $this->connect();
354         
355         return $this->dbh;
356     }
357
358     /**
359      * connect
360      * connects into database
361      *
362      * @return boolean
363      */
364     public function connect()
365     {
366
367         if ($this->isConnected) {
368             return false;
369         }
370
371         $event = new Doctrine_Event($this, Doctrine_Event::CONN_CONNECT);
372
373         $this->getListener()->preConnect($event);
374
375         $e     = explode(':', $this->options['dsn']);
376         $found = false;
377         
378         if (extension_loaded('pdo')) {
379             if (in_array($e[0], PDO::getAvailableDrivers())) {
380                 $this->dbh = new PDO($this->options['dsn'], $this->options['username'], 
381                                      $this->options['password'], $this->options['other']);
382                                      
383                 $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
384                 $found = true;
385             }
386         }
387
388         if ( ! $found) {
389             $class = 'Doctrine_Adapter_' . ucwords($e[0]);
390
391             if (class_exists($class)) {
392                 $this->dbh = new $class($this->options['dsn'], $this->options['username'], $this->options['password']);
393             } else {
394                 throw new Doctrine_Connection_Exception("Couldn't locate driver named " . $e[0]);          
395             }
396         }
397
398         // attach the pending attributes to adapter
399         foreach($this->pendingAttributes as $attr => $value) {
400             // some drivers don't support setting this so we just skip it
401             if ($attr == Doctrine::ATTR_DRIVER_NAME) {
402                 continue;
403             }
404             $this->dbh->setAttribute($attr, $value);
405         }
406
407         $this->isConnected = true;
408
409         $this->getListener()->postConnect($event);
410         return true;
411     }
412     
413     public function incrementQueryCount() 
414     {
415         $this->_count++;
416     }
417
418     /**
419      * converts given driver name
420      *
421      * @param
422      */
423     public function driverName($name)
424     {
425     }
426
427     /**
428      * supports
429      *
430      * @param string $feature   the name of the feature
431      * @return boolean          whether or not this drivers supports given feature
432      */
433     public function supports($feature)
434     {
435         return (isset($this->supported[$feature])
436                   && ($this->supported[$feature] === 'emulated'
437                    || $this->supported[$feature]));
438     }
439
440     /**
441      * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
442      * query, except that if there is already a row in the table with the same
443      * key field values, the REPLACE query just updates its values instead of
444      * inserting a new row.
445      *
446      * The REPLACE type of query does not make part of the SQL standards. Since
447      * practically only MySQL and SQLIte implement it natively, this type of
448      * query isemulated through this method for other DBMS using standard types
449      * of queries inside a transaction to assure the atomicity of the operation.
450      *
451      * @param                   string  name of the table on which the REPLACE query will
452      *                          be executed.
453      *
454      * @param   array           an associative array that describes the fields and the
455      *                          values that will be inserted or updated in the specified table. The
456      *                          indexes of the array are the names of all the fields of the table.
457      *
458      *                          The values of the array are values to be assigned to the specified field.
459      *
460      * @param array $keys       an array containing all key fields (primary key fields
461      *                          or unique index fields) for this table
462      *
463      *                          the uniqueness of a row will be determined according to
464      *                          the provided key fields
465      *
466      *                          this method will fail if no key fields are specified
467      *
468      * @throws Doctrine_Connection_Exception        if this driver doesn't support replace
469      * @throws Doctrine_Connection_Exception        if some of the key values was null
470      * @throws Doctrine_Connection_Exception        if there were no key fields
471      * @throws PDOException                         if something fails at PDO level
472      * @ return integer                              number of rows affected
473      */
474     public function replace(Doctrine_Table $table, array $fields, array $keys)
475     {
476         if (empty($keys)) {
477             throw new Doctrine_Connection_Exception('Not specified which fields are keys');
478         }
479         $condition = $values = array();
480
481         foreach ($fields as $fieldName => $value) {
482             $values[$fieldName] = $value;
483
484             if (in_array($fieldName, $keys)) {
485                 if ($value === null)
486                     throw new Doctrine_Connection_Exception('key value '.$fieldName.' may not be null');
487
488                 $condition[] = $table->getColumnName($fieldName) . ' = ?';
489                 $conditionValues[] = $value;
490             }
491         }
492
493         $query = 'DELETE FROM ' . $this->quoteIdentifier($table->getTableName())
494                 . ' WHERE ' . implode(' AND ', $condition);
495         $affectedRows = $this->exec($query, $conditionValues);
496
497         $this->insert($table, $values);
498
499         $affectedRows++;
500
501         return $affectedRows;
502     }
503
504     /**
505      * deletes table row(s) matching the specified identifier
506      *
507      * @throws Doctrine_Connection_Exception    if something went wrong at the database level
508      * @param string $table         The table to delete data from
509      * @param array $identifier     An associateve array containing identifier column-value pairs.
510      * @return integer              The number of affected rows
511      */
512     public function delete(Doctrine_Table $table, array $identifier)
513     {
514         $tmp = array();
515
516         foreach (array_keys($identifier) as $id) {
517             $tmp[] = $table->getColumnName($id) . ' = ?';
518         }
519
520         $query = 'DELETE FROM '
521                . $this->quoteIdentifier($table->getTableName())
522                . ' WHERE ' . implode(' AND ', $tmp);
523
524
525         return $this->exec($query, array_values($identifier));
526     }
527
528     /**
529      * Updates table row(s) with specified data
530      *
531      * @throws Doctrine_Connection_Exception    if something went wrong at the database level
532      * @param string $table     The table to insert data into
533      * @param array $values     An associateve array containing column-value pairs.
534      * @return mixed            boolean false if empty value array was given,
535      *                          otherwise returns the number of affected rows
536      */
537     public function update(Doctrine_Table $table, array $fields, array $identifier)
538     {
539         if (empty($fields)) {
540             return false;
541         }
542
543         $set = array();
544         foreach ($fields as $fieldName => $value) {
545             if ($value instanceof Doctrine_Expression) {
546                 $set[] = $table->getColumnName($fieldName) . ' = ' . $value->getSql();
547                 unset($values[$name]);
548             } else {
549                 $set[] = $table->getColumnName($fieldName) . ' = ?';
550             }
551         }
552
553         $params = array_merge(array_values($fields), array_values($identifier));
554
555         $sql  = 'UPDATE ' . $this->quoteIdentifier($table->getTableName())
556               . ' SET ' . implode(', ', $set)
557               . ' WHERE ' . implode(' = ? AND ', $table->getIdentifierColumnNames())
558               . ' = ?';
559
560         return $this->exec($sql, $params);
561     }
562
563     /**
564      * Inserts a table row with specified data.
565      *
566      * @param string $table     The table to insert data into.
567      * @param array $values     An associateve array containing column-value pairs.
568      * @return mixed            boolean false if empty value array was given,
569      *                          otherwise returns the number of affected rows
570      */
571     public function insert(Doctrine_Table $table, array $fields) {
572         if (empty($fields)) {
573             return false;
574         }
575
576         $tableName = $table->getTableName();
577
578         // column names are specified as array keys
579         $cols = array();
580         // the query VALUES will contain either expresions (eg 'NOW()') or ?
581         $a = array();
582         foreach ($fields as $fieldName => $value) {
583             $cols[] = $this->quoteIdentifier($table->getColumnName($fieldName));
584             if ($value instanceof Doctrine_Expression) {
585                 $a[] = $value->getSql();
586                 unset($fields[$fieldName]);
587             } else {
588                 $a[] = '?';
589             }
590         }
591
592         // build the statement
593         $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName) 
594                . ' (' . implode(', ', $cols) . ') '
595                . 'VALUES (';
596
597         $query .= implode(', ', $a) . ')';
598         // prepare and execute the statement
599
600         return $this->exec($query, array_values($fields));
601     }
602
603     /**
604      * Set the charset on the current connection
605      *
606      * @param string    charset
607      */
608     public function setCharset($charset)
609     {
610
611     }
612
613     /**
614      * Quote a string so it can be safely used as a table or column name
615      *
616      * Delimiting style depends on which database driver is being used.
617      *
618      * NOTE: just because you CAN use delimited identifiers doesn't mean
619      * you SHOULD use them.  In general, they end up causing way more
620      * problems than they solve.
621      *
622      * Portability is broken by using the following characters inside
623      * delimited identifiers:
624      *   + backtick (<kbd>`</kbd>) -- due to MySQL
625      *   + double quote (<kbd>"</kbd>) -- due to Oracle
626      *   + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access
627      *
628      * Delimited identifiers are known to generally work correctly under
629      * the following drivers:
630      *   + mssql
631      *   + mysql
632      *   + mysqli
633      *   + oci8
634      *   + pgsql
635      *   + sqlite
636      *
637      * InterBase doesn't seem to be able to use delimited identifiers
638      * via PHP 4.  They work fine under PHP 5.
639      *
640      * @param string $str           identifier name to be quoted
641      * @param bool $checkOption     check the 'quote_identifier' option
642      *
643      * @return string               quoted identifier string
644      */
645     public function quoteIdentifier($str, $checkOption = true)
646     {
647         // quick fix for the identifiers that contain a dot
648         if (strpos($str, '.')) {
649             $e = explode('.', $str);
650             
651             return $this->formatter->quoteIdentifier($e[0], $checkOption) . '.' 
652                  . $this->formatter->quoteIdentifier($e[1], $checkOption);
653         }
654         return $this->formatter->quoteIdentifier($str, $checkOption);
655     }
656
657     /**
658      * convertBooleans
659      * some drivers need the boolean values to be converted into integers
660      * when using DQL API
661      *
662      * This method takes care of that conversion
663      *
664      * @param array $item
665      * @return void
666      */
667     public function convertBooleans($item)
668     {
669         return $this->formatter->convertBooleans($item);
670     }
671
672     /**
673      * quote
674      * quotes given input parameter
675      *
676      * @param mixed $input      parameter to be quoted
677      * @param string $type
678      * @return mixed
679      */
680     public function quote($input, $type = null)
681     {
682         return $this->formatter->quote($input, $type);
683     }
684
685     /**
686      * Set the date/time format for the current connection
687      *
688      * @param string    time format
689      *
690      * @return void
691      */
692     public function setDateFormat($format = null)
693     {
694     }
695
696     /**
697      * fetchAll
698      *
699      * @param string $statement         sql query to be executed
700      * @param array $params             prepared statement params
701      * @return array
702      */
703     public function fetchAll($statement, array $params = array()) 
704     {
705         return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_ASSOC);
706     }
707
708     /**
709      * fetchOne
710      *
711      * @param string $statement         sql query to be executed
712      * @param array $params             prepared statement params
713      * @param int $colnum               0-indexed column number to retrieve
714      * @return mixed
715      */
716     public function fetchOne($statement, array $params = array(), $colnum = 0) 
717     {
718         return $this->execute($statement, $params)->fetchColumn($colnum);
719     }
720
721     /**
722      * fetchRow
723      *
724      * @param string $statement         sql query to be executed
725      * @param array $params             prepared statement params
726      * @return array
727      */
728     public function fetchRow($statement, array $params = array()) 
729     {
730         return $this->execute($statement, $params)->fetch(Doctrine::FETCH_ASSOC);
731     }
732
733     /**
734      * fetchArray
735      *
736      * @param string $statement         sql query to be executed
737      * @param array $params             prepared statement params
738      * @return array
739      */
740     public function fetchArray($statement, array $params = array()) 
741     {
742         return $this->execute($statement, $params)->fetch(Doctrine::FETCH_NUM);
743     }
744
745     /**
746      * fetchColumn
747      *
748      * @param string $statement         sql query to be executed
749      * @param array $params             prepared statement params
750      * @param int $colnum               0-indexed column number to retrieve
751      * @return array
752      */
753     public function fetchColumn($statement, array $params = array(), $colnum = 0) 
754     {
755         return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_COLUMN, $colnum);
756     }
757
758     /**
759      * fetchAssoc
760      *
761      * @param string $statement         sql query to be executed
762      * @param array $params             prepared statement params
763      * @return array
764      */
765     public function fetchAssoc($statement, array $params = array()) 
766     {
767         return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_ASSOC);
768     }
769
770     /**
771      * fetchBoth
772      *
773      * @param string $statement         sql query to be executed
774      * @param array $params             prepared statement params
775      * @return array
776      */
777     public function fetchBoth($statement, array $params = array()) 
778     {
779         return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_BOTH);
780     }
781
782     /**
783      * query
784      * queries the database using Doctrine Query Language
785      * returns a collection of Doctrine_Record objects
786      *
787      * <code>
788      * $users = $conn->query('SELECT u.* FROM User u');
789      *
790      * $users = $conn->query('SELECT u.* FROM User u WHERE u.name LIKE ?', array('someone'));
791      * </code>
792      *
793      * @param string $query             DQL query
794      * @param array $params             query parameters
795      * @param int $hydrationMode        Doctrine::FETCH_ARRAY or Doctrine::FETCH_RECORD
796      * @see Doctrine_Query
797      * @return Doctrine_Collection      Collection of Doctrine_Record objects
798      */
799     public function query($query, array $params = array(), $hydrationMode = null)
800     {
801         $parser = new Doctrine_Query($this);
802
803         return $parser->query($query, $params, $hydrationMode);
804     }
805
806     /**
807      * prepare
808      *
809      * @param string $statement
810      */
811     public function prepare($statement)
812     {
813         $this->connect();
814
815         try {
816             $event = new Doctrine_Event($this, Doctrine_Event::CONN_PREPARE, $statement);
817     
818             $this->getAttribute(Doctrine::ATTR_LISTENER)->prePrepare($event);
819
820             $stmt = false;
821     
822             if ( ! $event->skipOperation) {
823                 $stmt = $this->dbh->prepare($statement);
824             }
825     
826             $this->getAttribute(Doctrine::ATTR_LISTENER)->postPrepare($event);
827             
828             return new Doctrine_Connection_Statement($this, $stmt);
829         } catch(Doctrine_Adapter_Exception $e) {
830         } catch(PDOException $e) { }
831
832         $this->rethrowException($e, $this);
833     }
834
835     /**
836      * query
837      * queries the database using Doctrine Query Language and returns
838      * the first record found
839      *
840      * <code>
841      * $user = $conn->queryOne('SELECT u.* FROM User u WHERE u.id = ?', array(1));
842      *
843      * $user = $conn->queryOne('SELECT u.* FROM User u WHERE u.name LIKE ? AND u.password = ?',
844      *         array('someone', 'password')
845      *         );
846      * </code>
847      *
848      * @param string $query             DQL query
849      * @param array $params             query parameters
850      * @see Doctrine_Query
851      * @return Doctrine_Record|false    Doctrine_Record object on success,
852      *                                  boolean false on failure
853      */
854     public function queryOne($query, array $params = array()) 
855     {
856         $parser = new Doctrine_Query($this);
857
858         $coll = $parser->query($query, $params);
859         if ( ! $coll->contains(0)) {
860             return false;
861         }
862         return $coll[0];
863     }
864
865     /**
866      * queries the database with limit and offset
867      * added to the query and returns a Doctrine_Connection_Statement object
868      *
869      * @param string $query
870      * @param integer $limit
871      * @param integer $offset
872      * @return Doctrine_Connection_Statement
873      */
874     public function select($query, $limit = 0, $offset = 0)
875     {
876         if ($limit > 0 || $offset > 0) {
877             $query = $this->modifyLimitQuery($query, $limit, $offset);
878         }
879         return $this->execute($query);
880     }
881
882     /**
883      * standaloneQuery
884      *
885      * @param string $query     sql query
886      * @param array $params     query parameters
887      *
888      * @return PDOStatement|Doctrine_Adapter_Statement
889      */
890     public function standaloneQuery($query, $params = array())
891     {
892         return $this->execute($query, $params);
893     }
894
895     /**
896      * execute
897      * @param string $query     sql query
898      * @param array $params     query parameters
899      *
900      * @return PDOStatement|Doctrine_Adapter_Statement
901      */
902     public function execute($query, array $params = array())
903     {
904         $this->connect();
905
906         try {
907             if ( ! empty($params)) {
908                 $stmt = $this->prepare($query);
909                 $stmt->execute($params);
910                 return $stmt;
911             } else {
912                 $event = new Doctrine_Event($this, Doctrine_Event::CONN_QUERY, $query, $params);
913
914                 $this->getAttribute(Doctrine::ATTR_LISTENER)->preQuery($event);
915
916                 if ( ! $event->skipOperation) {
917                     $stmt = $this->dbh->query($query);
918                     $this->_count++;
919                 }
920                 $this->getAttribute(Doctrine::ATTR_LISTENER)->postQuery($event);
921
922                 return $stmt;
923             }
924         } catch (Doctrine_Adapter_Exception $e) {
925         } catch (PDOException $e) { }
926
927         $this->rethrowException($e, $this);
928     }
929
930     /**
931      * exec
932      * @param string $query     sql query
933      * @param array $params     query parameters
934      *
935      * @return PDOStatement|Doctrine_Adapter_Statement
936      */
937     public function exec($query, array $params = array()) {
938         $this->connect();
939
940         try {
941             if ( ! empty($params)) {
942                 $stmt = $this->prepare($query);
943                 $stmt->execute($params);
944
945                 return $stmt->rowCount();
946             } else {
947                 $event = new Doctrine_Event($this, Doctrine_Event::CONN_EXEC, $query, $params);
948
949                 $this->getAttribute(Doctrine::ATTR_LISTENER)->preExec($event);
950
951                 if ( ! $event->skipOperation) {
952                     $count = $this->dbh->exec($query);
953
954                     $this->_count++;
955                 }
956                 $this->getAttribute(Doctrine::ATTR_LISTENER)->postExec($event);
957
958                 return $count;
959             }
960         } catch (Doctrine_Adapter_Exception $e) {
961         } catch (PDOException $e) { }
962
963         $this->rethrowException($e, $this);
964     }
965
966     /**
967      * rethrowException
968      *
969      * @throws Doctrine_Connection_Exception
970      */
971     public function rethrowException(Exception $e, $invoker)
972     {
973         $event = new Doctrine_Event($this, Doctrine_Event::CONN_ERROR);
974
975         $this->getListener()->preError($event);
976         
977         $name = 'Doctrine_Connection_' . $this->driverName . '_Exception';
978
979         $exc  = new $name($e->getMessage(), (int) $e->getCode());
980         if ( ! is_array($e->errorInfo)) {
981             $e->errorInfo = array(null, null, null, null);
982         }
983         $exc->processErrorInfo($e->errorInfo);
984
985          if ($this->getAttribute(Doctrine::ATTR_THROW_EXCEPTIONS)) {
986             throw $exc;
987         }
988         
989         $this->getListener()->postError($event);
990     }
991
992     /**
993      * hasTable
994      * whether or not this connection has table $name initialized
995      *
996      * @param mixed $name
997      * @return boolean
998      */
999     public function hasTable($name)
1000     {
1001         return isset($this->tables[$name]);
1002     }
1003
1004     /**
1005      * returns a table object for given component name
1006      *
1007      * @param string $name              component name
1008      * @return object Doctrine_Table
1009      */
1010     public function getTable($name)
1011     {
1012         if (isset($this->tables[$name])) {
1013             return $this->tables[$name];
1014         }
1015         $class = $name . 'Table';
1016
1017         if (class_exists($class) && in_array('Doctrine_Table', class_parents($class))) {
1018             $table = new $class($name, $this, true);
1019         } else {
1020             $table = new Doctrine_Table($name, $this, true);
1021         }
1022
1023         $this->tables[$name] = $table;
1024
1025         return $table;
1026     }
1027
1028     /**
1029      * returns an array of all initialized tables
1030      *
1031      * @return array
1032      */
1033     public function getTables()
1034     {
1035         return $this->tables;
1036     }
1037
1038     /**
1039      * returns an iterator that iterators through all
1040      * initialized table objects
1041      *
1042      * <code>
1043      * foreach ($conn as $index => $table) {
1044      *      print $table;  // get a string representation of each table object
1045      * }
1046      * </code>
1047      *
1048      * @return ArrayIterator        SPL ArrayIterator object
1049      */
1050     public function getIterator()
1051     {
1052         return new ArrayIterator($this->tables);
1053     }
1054
1055     /**
1056      * returns the count of initialized table objects
1057      *
1058      * @return integer
1059      */
1060     public function count()
1061     {
1062         return $this->_count;
1063     }
1064
1065     /**
1066      * addTable
1067      * adds a Doctrine_Table object into connection registry
1068      *
1069      * @param $table                a Doctrine_Table object to be added into registry
1070      * @return boolean
1071      */
1072     public function addTable(Doctrine_Table $table)
1073     {
1074         $name = $table->getComponentName();
1075
1076         if (isset($this->tables[$name])) {
1077             return false;
1078         }
1079         $this->tables[$name] = $table;
1080         return true;
1081     }
1082
1083     /**
1084      * create
1085      * creates a record
1086      *
1087      * create                       creates a record
1088      * @param string $name          component name
1089      * @return Doctrine_Record      Doctrine_Record object
1090      */
1091     public function create($name)
1092     {
1093         return $this->getTable($name)->create();
1094     }
1095     
1096     /**
1097      * Creates a new Doctrine_Query object that operates on this connection.
1098      * 
1099      * @return Doctrine_Query 
1100      */
1101     public function createQuery()
1102     {
1103         return new Doctrine_Query($this);
1104     }
1105
1106     /**
1107      * flush
1108      * saves all the records from all tables
1109      * this operation is isolated using a transaction
1110      *
1111      * @throws PDOException         if something went wrong at database level
1112      * @return void
1113      */
1114     public function flush()
1115     {
1116         $this->beginTransaction();
1117         $this->unitOfWork->saveAll();
1118         $this->commit();
1119     }
1120
1121     /**
1122      * clear
1123      * clears all repositories
1124      *
1125      * @return void
1126      */
1127     public function clear()
1128     {
1129         foreach ($this->tables as $k => $table) {
1130             $table->getRepository()->evictAll();
1131             $table->clear();
1132         }
1133     }
1134
1135     /**
1136      * evictTables
1137      * evicts all tables
1138      *
1139      * @return void
1140      */
1141     public function evictTables()
1142     {
1143         $this->tables = array();
1144         $this->exported = array();
1145     }
1146
1147     /**
1148      * close
1149      * closes the connection
1150      *
1151      * @return void
1152      */
1153     public function close()
1154     {
1155         $event = new Doctrine_Event($this, Doctrine_Event::CONN_CLOSE);
1156
1157         $this->getAttribute(Doctrine::ATTR_LISTENER)->preClose($event);
1158
1159         $this->clear();
1160         
1161         unset($this->dbh);
1162         $this->isConnected = false;
1163
1164         $this->getAttribute(Doctrine::ATTR_LISTENER)->postClose($event);
1165     }
1166
1167     /**
1168      * get the current transaction nesting level
1169      *
1170      * @return integer
1171      */
1172     public function getTransactionLevel()
1173     {
1174         return $this->transaction->getTransactionLevel();
1175     }
1176
1177     /**
1178      * errorCode
1179      * Fetch the SQLSTATE associated with the last operation on the database handle
1180      *
1181      * @return integer
1182      */
1183     public function errorCode()
1184     {
1185         $this->connect();
1186
1187         return $this->dbh->errorCode();
1188     }
1189
1190     /**
1191      * errorInfo
1192      * Fetch extended error information associated with the last operation on the database handle
1193      *
1194      * @return array
1195      */
1196     public function errorInfo()
1197     {
1198         $this->connect();
1199
1200         return $this->dbh->errorInfo();
1201     }
1202     
1203     /**
1204      * getCacheDriver
1205      *
1206      * @return Doctrine_Cache_Interface
1207      * @deprecated Use getResultCacheDriver()
1208      */
1209     public function getCacheDriver()
1210     {
1211         return $this->getResultCacheDriver();
1212     }
1213     
1214     /**
1215      * getResultCacheDriver
1216      *
1217      * @return Doctrine_Cache_Interface
1218      */
1219     public function getResultCacheDriver()
1220     {
1221         if ( ! $this->getAttribute(Doctrine::ATTR_RESULT_CACHE)) {
1222             throw new Doctrine_Exception('Result Cache driver not initialized.');
1223         }
1224
1225         return $this->getAttribute(Doctrine::ATTR_RESULT_CACHE);
1226     }
1227     
1228     /**
1229      * getQueryCacheDriver
1230      *
1231      * @return Doctrine_Cache_Interface
1232      */
1233     public function getQueryCacheDriver()
1234     {
1235         if ( ! $this->getAttribute(Doctrine::ATTR_QUERY_CACHE)) {
1236             throw new Doctrine_Exception('Query Cache driver not initialized.');
1237         }
1238
1239         return $this->getAttribute(Doctrine::ATTR_QUERY_CACHE);
1240     }
1241
1242     /**
1243      * lastInsertId
1244      *
1245      * Returns the ID of the last inserted row, or the last value from a sequence object,
1246      * depending on the underlying driver.
1247      *
1248      * Note: This method may not return a meaningful or consistent result across different drivers, 
1249      * because the underlying database may not even support the notion of auto-increment fields or sequences.
1250      *
1251      * @param string $table     name of the table into which a new row was inserted
1252      * @param string $field     name of the field into which a new row was inserted
1253      */
1254     public function lastInsertId($table = null, $field = null)
1255     {
1256         return $this->sequence->lastInsertId($table, $field);
1257     }
1258
1259     /**
1260      * beginTransaction
1261      * Start a transaction or set a savepoint.
1262      *
1263      * if trying to set a savepoint and there is no active transaction
1264      * a new transaction is being started
1265      *
1266      * Listeners: onPreTransactionBegin, onTransactionBegin
1267      *
1268      * @param string $savepoint                 name of a savepoint to set
1269      * @throws Doctrine_Transaction_Exception   if the transaction fails at database level
1270      * @return integer                          current transaction nesting level
1271      */
1272     public function beginTransaction($savepoint = null)
1273     {
1274         $this->transaction->beginTransaction($savepoint);
1275     }
1276
1277     /**
1278      * commit
1279      * Commit the database changes done during a transaction that is in
1280      * progress or release a savepoint. This function may only be called when
1281      * auto-committing is disabled, otherwise it will fail.
1282      *
1283      * Listeners: onPreTransactionCommit, onTransactionCommit
1284      *
1285      * @param string $savepoint                 name of a savepoint to release
1286      * @throws Doctrine_Transaction_Exception   if the transaction fails at PDO level
1287      * @throws Doctrine_Validator_Exception     if the transaction fails due to record validations
1288      * @return boolean                          false if commit couldn't be performed, true otherwise
1289      */
1290     public function commit($savepoint = null)
1291     {
1292         $this->transaction->commit($savepoint);
1293     }
1294
1295     /**
1296      * rollback
1297      * Cancel any database changes done during a transaction or since a specific
1298      * savepoint that is in progress. This function may only be called when
1299      * auto-committing is disabled, otherwise it will fail. Therefore, a new
1300      * transaction is implicitly started after canceling the pending changes.
1301      *
1302      * this method can be listened with onPreTransactionRollback and onTransactionRollback
1303      * eventlistener methods
1304      *
1305      * @param string $savepoint                 name of a savepoint to rollback to   
1306      * @throws Doctrine_Transaction_Exception   if the rollback operation fails at database level
1307      * @return boolean                          false if rollback couldn't be performed, true otherwise
1308      */
1309     public function rollback($savepoint = null)
1310     {
1311         $this->transaction->rollback($savepoint);
1312     }
1313
1314     /**
1315      * returns a string representation of this object
1316      * @return string
1317      */
1318     public function __toString()
1319     {
1320         return Doctrine_Lib::getConnectionAsString($this);
1321     }
1322 }