root/tags/1.0.3/lib/Doctrine/Connection.php

Revision 5069, 50.3 KB (checked in by adrive, 22 months ago)

Fixes #1549: added delimiter of SQL queries in exported file as connection parameter

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