Coverage for Doctrine_Connection

Back to coverage report

1 <?php
2 /*
3  *  $Id: Connection.php 2963 2007-10-21 06:23:59Z Jonathan.Wage $
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.com>.
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.com
51  * @since       1.0
52  * @version     $Revision: 2963 $
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 availible 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         } elseif (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 $this->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      * @param integer $attribute
266      * @param mixed $value
267      * @return boolean
268      */
269     public function setAttribute($attribute, $value)
270     {
271         if ($attribute >= 100) {
272             parent::setAttribute($attribute, $value);
273         } else {
274             if ($this->isConnected) {
275                 $this->dbh->setAttribute($attribute, $value);
276             } else {
277                 $this->pendingAttributes[$attribute] = $value;
278             }
279         }
280         return $this;
281     }
282
283     /**
284      * getName
285      * returns the name of this driver
286      *
287      * @return string           the name of this driver
288      */
289     public function getName()
290     {
291         return $this->driverName;
292     }
293
294     /**
295      * __get
296      * lazy loads given module and returns it
297      *
298      * @see Doctrine_DataDict
299      * @see Doctrine_Expression
300      * @see Doctrine_Export
301      * @see Doctrine_Transaction
302      * @see Doctrine_Connection::$modules       all availible modules
303      * @param string $name                      the name of the module to get
304      * @throws Doctrine_Connection_Exception    if trying to get an unknown module
305      * @return Doctrine_Connection_Module       connection module
306      */
307     public function __get($name)
308     {
309         if (isset($this->properties[$name])) {
310             return $this->properties[$name];
311         }
312
313         if ( ! isset($this->modules[$name])) {
314             throw new Doctrine_Connection_Exception('Unknown module / property ' . $name);
315         }
316         if ($this->modules[$name] === false) {
317             switch ($name) {
318                 case 'unitOfWork':
319                     $this->modules[$name] = new Doctrine_Connection_UnitOfWork($this);
320                     break;
321                 case 'formatter':
322                     $this->modules[$name] = new Doctrine_Formatter($this);
323                     break;
324                 default:
325                     $class = 'Doctrine_' . ucwords($name) . '_' . $this->getName();
326                     $this->modules[$name] = new $class($this);
327                 }
328         }
329
330         return $this->modules[$name];
331     }
332
333     /**
334      * returns the manager that created this connection
335      *
336      * @return Doctrine_Manager
337      */
338     public function getManager()
339     {
340         return $this->getParent();
341     }
342
343     /**
344      * returns the database handler of which this connection uses
345      *
346      * @return PDO              the database handler
347      */
348     public function getDbh()
349     {
350         $this->connect();
351         
352         return $this->dbh;
353     }
354
355     /**
356      * connect
357      * connects into database
358      *
359      * @return boolean
360      */
361     public function connect()
362     {
363
364         if ($this->isConnected) {
365             return false;
366         }
367
368         $event = new Doctrine_Event($this, Doctrine_Event::CONN_CONNECT);
369
370         $this->getListener()->preConnect($event);
371
372         $e     = explode(':', $this->options['dsn']);
373         $found = false;
374         
375         if (extension_loaded('pdo')) {
376             if (in_array($e[0], PDO::getAvailableDrivers())) {
377                 $this->dbh = new PDO($this->options['dsn'], $this->options['username'], 
378                                      $this->options['password'], $this->options['other']);
379                                      
380                 $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
381                 $found = true;
382             }
383         }
384
385         if ( ! $found) {
386             $class = 'Doctrine_Adapter_' . ucwords($e[0]);
387
388             if (class_exists($class)) {
389                 $this->dbh = new $class($this->options['dsn'], $this->options['username'], $this->options['password']);
390             } else {
391                 throw new Doctrine_Connection_Exception("Couldn't locate driver named " . $e[0]);          
392             }
393         }
394
395         // attach the pending attributes to adapter
396         foreach($this->pendingAttributes as $attr => $value) {
397             // some drivers don't support setting this so we just skip it
398             if ($attr == Doctrine::ATTR_DRIVER_NAME) {
399                 continue;
400             }
401             $this->dbh->setAttribute($attr, $value);
402         }
403
404         $this->isConnected = true;
405
406         $this->getListener()->postConnect($event);
407         return true;
408     }
409     
410     public function incrementQueryCount() 
411     {
412         $this->_count++;
413     }
414
415     /**
416      * converts given driver name
417      *
418      * @param
419      */
420     public function driverName($name)
421     {
422     }
423
424     /**
425      * supports
426      *
427      * @param string $feature   the name of the feature
428      * @return boolean          whether or not this drivers supports given feature
429      */
430     public function supports($feature)
431     {
432         return (isset($this->supported[$feature])
433                   && ($this->supported[$feature] === 'emulated'
434                    || $this->supported[$feature]));
435     }
436
437     /**
438      * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
439      * query, except that if there is already a row in the table with the same
440      * key field values, the REPLACE query just updates its values instead of
441      * inserting a new row.
442      *
443      * The REPLACE type of query does not make part of the SQL standards. Since
444      * practically only MySQL and SQLIte implement it natively, this type of
445      * query isemulated through this method for other DBMS using standard types
446      * of queries inside a transaction to assure the atomicity of the operation.
447      *
448      * @param                   string  name of the table on which the REPLACE query will
449      *                          be executed.
450      *
451      * @param   array           an associative array that describes the fields and the
452      *                          values that will be inserted or updated in the specified table. The
453      *                          indexes of the array are the names of all the fields of the table.
454      *
455      *                          The values of the array are values to be assigned to the specified field.
456      *
457      * @param array $keys       an array containing all key fields (primary key fields
458      *                          or unique index fields) for this table
459      *
460      *                          the uniqueness of a row will be determined according to
461      *                          the provided key fields
462      *
463      *                          this method will fail if no key fields are specified
464      *
465      * @throws Doctrine_Connection_Exception        if this driver doesn't support replace
466      * @throws Doctrine_Connection_Exception        if some of the key values was null
467      * @throws Doctrine_Connection_Exception        if there were no key fields
468      * @throws PDOException                         if something fails at PDO level
469      * @return integer                              number of rows affected
470      */
471     public function replace($table, array $fields, array $keys)
472     {
473         //if ( ! $this->supports('replace'))
474         //    throw new Doctrine_Connection_Exception('replace query is not supported');
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 $name => $value) {
482             $values[$name] = $value;
483
484             if (in_array($name, $keys)) {
485                 if ($value === null)
486                     throw new Doctrine_Connection_Exception('key value '.$name.' may not be null');
487
488                 $condition[]       = $name . ' = ?';
489                 $conditionValues[] = $value;
490             }
491         }
492
493         $query          = 'DELETE FROM ' . $this->quoteIdentifier($table) . ' WHERE ' . implode(' AND ', $condition);
494         $affectedRows   = $this->exec($query);
495
496         $this->insert($table, $values);
497
498         $affectedRows++;
499
500
501         return $affectedRows;
502     }
503
504     /**
505      * Inserts a table row with specified data.
506      *
507      * @param string $table     The table to insert data into.
508      * @param array $values     An associateve array containing column-value pairs.
509      * @return boolean
510      */
511     public function insert($table, array $values = array()) {
512         if (empty($values)) {
513             return false;
514         }
515
516         // column names are specified as array keys
517         $cols = array();
518         // the query VALUES will contain either expresions (eg 'NOW()') or ?
519         $a = array();
520         foreach ($values as $k => $value) {
521             $cols[] = $this->quoteIdentifier($k);
522             if ($value instanceof Doctrine_Expression) {
523                 $a[] = $value->getSql();
524                 unset($values[$k]);
525             } else {
526                 $a[] = '?';
527             }
528         }
529
530         // build the statement
531         $query = 'INSERT INTO ' . $this->quoteIdentifier($table) 
532                . ' (' . implode(', ', $cols) . ') '
533                . 'VALUES (';
534
535         $query .= implode(', ', $a) . ')';
536         // prepare and execute the statement
537
538         $this->exec($query, array_values($values));
539
540         return true;
541     }
542
543     /**
544      * Set the charset on the current connection
545      *
546      * @param string    charset
547      *
548      * @return void
549      */
550     public function setCharset($charset)
551     {
552
553     }
554
555     /**
556      * Quote a string so it can be safely used as a table or column name
557      *
558      * Delimiting style depends on which database driver is being used.
559      *
560      * NOTE: just because you CAN use delimited identifiers doesn't mean
561      * you SHOULD use them.  In general, they end up causing way more
562      * problems than they solve.
563      *
564      * Portability is broken by using the following characters inside
565      * delimited identifiers:
566      *   + backtick (<kbd>`</kbd>) -- due to MySQL
567      *   + double quote (<kbd>"</kbd>) -- due to Oracle
568      *   + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access
569      *
570      * Delimited identifiers are known to generally work correctly under
571      * the following drivers:
572      *   + mssql
573      *   + mysql
574      *   + mysqli
575      *   + oci8
576      *   + pgsql
577      *   + sqlite
578      *
579      * InterBase doesn't seem to be able to use delimited identifiers
580      * via PHP 4.  They work fine under PHP 5.
581      *
582      * @param string $str           identifier name to be quoted
583      * @param bool $checkOption     check the 'quote_identifier' option
584      *
585      * @return string               quoted identifier string
586      */
587     public function quoteIdentifier($str, $checkOption = true)
588     {
589         // quick fix for the identifiers that contain a dot
590         if (strpos($str, '.')) {
591             $e = explode('.', $str);
592             
593             return $this->formatter->quoteIdentifier($e[0], $checkOption) . '.' 
594                  . $this->formatter->quoteIdentifier($e[1], $checkOption);
595         }
596         return $this->formatter->quoteIdentifier($str, $checkOption);
597     }
598
599     /**
600      * convertBooleans
601      * some drivers need the boolean values to be converted into integers
602      * when using DQL API
603      *
604      * This method takes care of that conversion
605      *
606      * @param array $item
607      * @return void
608      */
609     public function convertBooleans($item)
610     {
611         return $this->formatter->convertBooleans($item);
612     }
613
614     /**
615      * quote
616      * quotes given input parameter
617      *
618      * @param mixed $input      parameter to be quoted
619      * @param string $type
620      * @return mixed
621      */
622     public function quote($input, $type = null)
623     {
624         return $this->formatter->quote($input, $type);
625     }
626
627     /**
628      * Set the date/time format for the current connection
629      *
630      * @param string    time format
631      *
632      * @return void
633      */
634     public function setDateFormat($format = null)
635     {
636     }
637
638     /**
639      * fetchAll
640      *
641      * @param string $statement         sql query to be executed
642      * @param array $params             prepared statement params
643      * @return array
644      */
645     public function fetchAll($statement, array $params = array()) 
646     {
647         return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_ASSOC);
648     }
649
650     /**
651      * fetchOne
652      *
653      * @param string $statement         sql query to be executed
654      * @param array $params             prepared statement params
655      * @param int $colnum               0-indexed column number to retrieve
656      * @return mixed
657      */
658     public function fetchOne($statement, array $params = array(), $colnum = 0) 
659     {
660         return $this->execute($statement, $params)->fetchColumn($colnum);
661     }
662
663     /**
664      * fetchRow
665      *
666      * @param string $statement         sql query to be executed
667      * @param array $params             prepared statement params
668      * @return array
669      */
670     public function fetchRow($statement, array $params = array()) 
671     {
672         return $this->execute($statement, $params)->fetch(Doctrine::FETCH_ASSOC);
673     }
674
675     /**
676      * fetchArray
677      *
678      * @param string $statement         sql query to be executed
679      * @param array $params             prepared statement params
680      * @return array
681      */
682     public function fetchArray($statement, array $params = array()) 
683     {
684         return $this->execute($statement, $params)->fetch(Doctrine::FETCH_NUM);
685     }
686
687     /**
688      * fetchColumn
689      *
690      * @param string $statement         sql query to be executed
691      * @param array $params             prepared statement params
692      * @param int $colnum               0-indexed column number to retrieve
693      * @return array
694      */
695     public function fetchColumn($statement, array $params = array(), $colnum = 0) 
696     {
697         return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_COLUMN, $colnum);
698     }
699
700     /**
701      * fetchAssoc
702      *
703      * @param string $statement         sql query to be executed
704      * @param array $params             prepared statement params
705      * @return array
706      */
707     public function fetchAssoc($statement, array $params = array()) 
708     {
709         return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_ASSOC);
710     }
711
712     /**
713      * fetchBoth
714      *
715      * @param string $statement         sql query to be executed
716      * @param array $params             prepared statement params
717      * @return array
718      */
719     public function fetchBoth($statement, array $params = array()) 
720     {
721         return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_BOTH);
722     }
723
724     /**
725      * query
726      * queries the database using Doctrine Query Language
727      * returns a collection of Doctrine_Record objects
728      *
729      * <code>
730      * $users = $conn->query('SELECT u.* FROM User u');
731      *
732      * $users = $conn->query('SELECT u.* FROM User u WHERE u.name LIKE ?', array('someone'));
733      * </code>
734      *
735      * @param string $query             DQL query
736      * @param array $params             query parameters
737      * @param int $hydrationMode        Doctrine::FETCH_ARRAY or Doctrine::FETCH_RECORD
738      * @see Doctrine_Query
739      * @return Doctrine_Collection      Collection of Doctrine_Record objects
740      */
741     public function query($query, array $params = array(), $hydrationMode = null)
742     {
743         $parser = new Doctrine_Query($this);
744
745         return $parser->query($query, $params, $hydrationMode);
746     }
747
748     /**
749      * prepare
750      *
751      * @param string $statement
752      */
753     public function prepare($statement)
754     {
755         $this->connect();
756
757         try {
758             $event = new Doctrine_Event($this, Doctrine_Event::CONN_PREPARE, $statement);
759     
760             $this->getAttribute(Doctrine::ATTR_LISTENER)->prePrepare($event);
761
762             $stmt = false;
763     
764             if ( ! $event->skipOperation) {
765                 $stmt = $this->dbh->prepare($statement);
766             }
767     
768             $this->getAttribute(Doctrine::ATTR_LISTENER)->postPrepare($event);
769             
770             return new Doctrine_Connection_Statement($this, $stmt);
771         } catch(Doctrine_Adapter_Exception $e) {
772         } catch(PDOException $e) { }
773
774         $this->rethrowException($e, $this);
775     }
776
777     /**
778      * query
779      * queries the database using Doctrine Query Language and returns
780      * the first record found
781      *
782      * <code>
783      * $user = $conn->queryOne('SELECT u.* FROM User u WHERE u.id = ?', array(1));
784      *
785      * $user = $conn->queryOne('SELECT u.* FROM User u WHERE u.name LIKE ? AND u.password = ?',
786      *         array('someone', 'password')
787      *         );
788      * </code>
789      *
790      * @param string $query             DQL query
791      * @param array $params             query parameters
792      * @see Doctrine_Query
793      * @return Doctrine_Record|false    Doctrine_Record object on success,
794      *                                  boolean false on failure
795      */
796     public function queryOne($query, array $params = array()) 
797     {
798         $parser = new Doctrine_Query($this);
799
800         $coll = $parser->query($query, $params);
801         if ( ! $coll->contains(0)) {
802             return false;
803         }
804         return $coll[0];
805     }
806
807     /**
808      * queries the database with limit and offset
809      * added to the query and returns a Doctrine_Connection_Statement object
810      *
811      * @param string $query
812      * @param integer $limit
813      * @param integer $offset
814      * @return Doctrine_Connection_Statement
815      */
816     public function select($query, $limit = 0, $offset = 0)
817     {
818         if ($limit > 0 || $offset > 0) {
819             $query = $this->modifyLimitQuery($query, $limit, $offset);
820         }
821         return $this->execute($query);
822     }
823
824     /**
825      * standaloneQuery
826      *
827      * @param string $query     sql query
828      * @param array $params     query parameters
829      *
830      * @return PDOStatement|Doctrine_Adapter_Statement
831      */
832     public function standaloneQuery($query, $params = array())
833     {
834         return $this->execute($query, $params);
835     }
836
837     /**
838      * execute
839      * @param string $query     sql query
840      * @param array $params     query parameters
841      *
842      * @return PDOStatement|Doctrine_Adapter_Statement
843      */
844     public function execute($query, array $params = array())
845     {
846         $this->connect();
847
848         try {
849             if ( ! empty($params)) {
850                 $stmt = $this->prepare($query);
851                 $stmt->execute($params);
852                 return $stmt;
853             } else {
854                 $event = new Doctrine_Event($this, Doctrine_Event::CONN_QUERY, $query, $params);
855
856                 $this->getAttribute(Doctrine::ATTR_LISTENER)->preQuery($event);
857
858                 if ( ! $event->skipOperation) {
859                     $stmt = $this->dbh->query($query);
860
861                     $this->_count++;
862                 }
863                 $this->getAttribute(Doctrine::ATTR_LISTENER)->postQuery($event);
864
865                 return $stmt;
866             }
867         } catch(Doctrine_Adapter_Exception $e) {
868         } catch(PDOException $e) { }
869
870         $this->rethrowException($e, $this);
871     }
872
873     /**
874      * exec
875      * @param string $query     sql query
876      * @param array $params     query parameters
877      *
878      * @return PDOStatement|Doctrine_Adapter_Statement
879      */
880     public function exec($query, array $params = array()) {
881         $this->connect();
882
883         try {
884             if ( ! empty($params)) {
885                 $stmt = $this->prepare($query);
886                 $stmt->execute($params);
887
888                 return $stmt->rowCount();
889             } else {
890                 $event = new Doctrine_Event($this, Doctrine_Event::CONN_EXEC, $query, $params);
891
892                 $this->getAttribute(Doctrine::ATTR_LISTENER)->preExec($event);
893
894                 if ( ! $event->skipOperation) {
895                     $count = $this->dbh->exec($query);
896
897                     $this->_count++;
898                 }
899                 $this->getAttribute(Doctrine::ATTR_LISTENER)->postExec($event);
900
901                 return $count;
902             }
903         } catch(Doctrine_Adapter_Exception $e) {
904         } catch(PDOException $e) { }
905
906         $this->rethrowException($e, $this);
907     }
908
909     /**
910      * rethrowException
911      *
912      * @throws Doctrine_Connection_Exception
913      */
914     public function rethrowException(Exception $e, $invoker)
915     {
916         $event = new Doctrine_Event($this, Doctrine_Event::CONN_ERROR);
917
918         $this->getListener()->preError($event);
919
920         $name = 'Doctrine_Connection_' . $this->driverName . '_Exception';
921
922         $exc  = new $name($e->getMessage(), (int) $e->getCode());
923         if ( ! is_array($e->errorInfo)) {
924             $e->errorInfo = array(null, null, null, null);
925         }
926         $exc->processErrorInfo($e->errorInfo);
927
928          if ($this->getAttribute(Doctrine::ATTR_THROW_EXCEPTIONS)) {
929             throw $exc;
930         }
931         
932         $this->getListener()->postError($event);
933     }
934
935     /**
936      * hasTable
937      * whether or not this connection has table $name initialized
938      *
939      * @param mixed $name
940      * @return boolean
941      */
942     public function hasTable($name)
943     {
944         return isset($this->tables[$name]);
945     }
946
947     /**
948      * returns a table object for given component name
949      *
950      * @param string $name              component name
951      * @return object Doctrine_Table
952      */
953     public function getTable($name)
954     {
955         if (isset($this->tables[$name])) {
956             return $this->tables[$name];
957         }
958         $class = $name . 'Table';
959
960         if (class_exists($class) && in_array('Doctrine_Table', class_parents($class))) {
961             $table = new $class($name, $this, true);
962         } else {
963             $table = new Doctrine_Table($name, $this, true);
964         }
965
966         $this->tables[$name] = $table;
967
968         return $table;
969     }
970
971     /**
972      * returns an array of all initialized tables
973      *
974      * @return array
975      */
976     public function getTables()
977     {
978         return $this->tables;
979     }
980
981     /**
982      * returns an iterator that iterators through all
983      * initialized table objects
984      *
985      * <code>
986      * foreach ($conn as $index => $table) {
987      *      print $table;  // get a string representation of each table object
988      * }
989      * </code>
990      *
991      * @return ArrayIterator        SPL ArrayIterator object
992      */
993     public function getIterator()
994     {
995         return new ArrayIterator($this->tables);
996     }
997
998     /**
999      * returns the count of initialized table objects
1000      *
1001      * @return integer
1002      */
1003     public function count()
1004     {
1005         return $this->_count;
1006     }
1007
1008     /**
1009      * addTable
1010      * adds a Doctrine_Table object into connection registry
1011      *
1012      * @param $table                a Doctrine_Table object to be added into registry
1013      * @return boolean
1014      */
1015     public function addTable(Doctrine_Table $table)
1016     {
1017         $name = $table->getComponentName();
1018
1019         if (isset($this->tables[$name])) {
1020             return false;
1021         }
1022         $this->tables[$name] = $table;
1023         return true;
1024     }
1025
1026     /**
1027      * create
1028      * creates a record
1029      *
1030      * create                       creates a record
1031      * @param string $name          component name
1032      * @return Doctrine_Record      Doctrine_Record object
1033      */
1034     public function create($name)
1035     {
1036         return $this->getTable($name)->create();
1037     }
1038
1039     /**
1040      * flush
1041      * saves all the records from all tables
1042      * this operation is isolated using a transaction
1043      *
1044      * @throws PDOException         if something went wrong at database level
1045      * @return void
1046      */
1047     public function flush()
1048     {
1049         $this->beginTransaction();
1050         $this->unitOfWork->saveAll();
1051         $this->commit();
1052     }
1053
1054     /**
1055      * clear
1056      * clears all repositories
1057      *
1058      * @return void
1059      */
1060     public function clear()
1061     {
1062         foreach ($this->tables as $k => $table) {
1063             $table->getRepository()->evictAll();
1064             $table->clear();
1065         }
1066     }
1067
1068     /**
1069      * evictTables
1070      * evicts all tables
1071      *
1072      * @return void
1073      */
1074     public function evictTables()
1075     {
1076         $this->tables = array();
1077         $this->exported = array();
1078     }
1079
1080     /**
1081      * close
1082      * closes the connection
1083      *
1084      * @return void
1085      */
1086     public function close()
1087     {
1088         $event = new Doctrine_Event($this, Doctrine_Event::CONN_CLOSE);
1089
1090         $this->getAttribute(Doctrine::ATTR_LISTENER)->preClose($event);
1091
1092         $this->clear();
1093         
1094         unset($this->dbh);
1095         $this->isConnected = false;
1096
1097         $this->getAttribute(Doctrine::ATTR_LISTENER)->postClose($event);
1098     }
1099
1100     /**
1101      * get the current transaction nesting level
1102      *
1103      * @return integer
1104      */
1105     public function getTransactionLevel()
1106     {
1107         return $this->transaction->getTransactionLevel();
1108     }
1109
1110     /**
1111      * errorCode
1112      * Fetch the SQLSTATE associated with the last operation on the database handle
1113      *
1114      * @return integer
1115      */
1116     public function errorCode()
1117     {
1118         $this->connect();
1119
1120         return $this->dbh->errorCode();
1121     }
1122
1123     /**
1124      * errorInfo
1125      * Fetch extended error information associated with the last operation on the database handle
1126      *
1127      * @return array
1128      */
1129     public function errorInfo()
1130     {
1131         $this->connect();
1132
1133         return $this->dbh->errorInfo();
1134     }
1135
1136     /**
1137      * lastInsertId
1138      *
1139      * Returns the ID of the last inserted row, or the last value from a sequence object,
1140      * depending on the underlying driver.
1141      *
1142      * Note: This method may not return a meaningful or consistent result across different drivers, 
1143      * because the underlying database may not even support the notion of auto-increment fields or sequences.
1144      *
1145      * @param string $table     name of the table into which a new row was inserted
1146      * @param string $field     name of the field into which a new row was inserted
1147      */
1148     public function lastInsertId($table = null, $field = null)
1149     {
1150         return $this->sequence->lastInsertId($table, $field);
1151     }
1152
1153     /**
1154      * beginTransaction
1155      * Start a transaction or set a savepoint.
1156      *
1157      * if trying to set a savepoint and there is no active transaction
1158      * a new transaction is being started
1159      *
1160      * Listeners: onPreTransactionBegin, onTransactionBegin
1161      *
1162      * @param string $savepoint                 name of a savepoint to set
1163      * @throws Doctrine_Transaction_Exception   if the transaction fails at database level
1164      * @return integer                          current transaction nesting level
1165      */
1166     public function beginTransaction($savepoint = null)
1167     {
1168         $this->transaction->beginTransaction($savepoint);
1169     }
1170
1171     /**
1172      * commit
1173      * Commit the database changes done during a transaction that is in
1174      * progress or release a savepoint. This function may only be called when
1175      * auto-committing is disabled, otherwise it will fail.
1176      *
1177      * Listeners: onPreTransactionCommit, onTransactionCommit
1178      *
1179      * @param string $savepoint                 name of a savepoint to release
1180      * @throws Doctrine_Transaction_Exception   if the transaction fails at PDO level
1181      * @throws Doctrine_Validator_Exception     if the transaction fails due to record validations
1182      * @return boolean                          false if commit couldn't be performed, true otherwise
1183      */
1184     public function commit($savepoint = null)
1185     {
1186         $this->transaction->commit($savepoint);
1187     }
1188
1189     /**
1190      * rollback
1191      * Cancel any database changes done during a transaction or since a specific
1192      * savepoint that is in progress. This function may only be called when
1193      * auto-committing is disabled, otherwise it will fail. Therefore, a new
1194      * transaction is implicitly started after canceling the pending changes.
1195      *
1196      * this method can be listened with onPreTransactionRollback and onTransactionRollback
1197      * eventlistener methods
1198      *
1199      * @param string $savepoint                 name of a savepoint to rollback to   
1200      * @throws Doctrine_Transaction_Exception   if the rollback operation fails at database level
1201      * @return boolean                          false if rollback couldn't be performed, true otherwise
1202      */
1203     public function rollback($savepoint = null)
1204     {
1205         $this->transaction->rollback($savepoint);
1206     }
1207
1208     /**
1209      * returns a string representation of this object
1210      * @return string
1211      */
1212     public function __toString()
1213     {
1214         return Doctrine_Lib::getConnectionAsString($this);
1215     }
1216 }