Coverage for Doctrine_Connection

Back to coverage report

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