Connection.php 51.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
19
 * <http://www.phpdoctrine.org>.
20
 */
21
Doctrine::autoload('Doctrine_Configurable');
22 23 24
/**
 * Doctrine_Connection
 *
zYne's avatar
zYne committed
25 26 27 28 29 30 31 32 33 34 35 36
 * A wrapper layer on top of PDO / Doctrine_Adapter
 *
 * Doctrine_Connection is the heart of any Doctrine based application.
 *
 * 1. Event listeners
 *    An easy to use, pluggable eventlistener architecture. Aspects such as
 *    logging, query profiling and caching can be easily implemented through
 *    the use of these listeners
 *
 * 2. Lazy-connecting
 *    Creating an instance of Doctrine_Connection does not connect
 *    to database. Connecting to database is only invoked when actually needed
37
 *    (for example when query() is being called)
zYne's avatar
zYne committed
38 39
 *
 * 3. Convenience methods
zYne's avatar
zYne committed
40
 *    Doctrine_Connection provides many convenience methods such as fetchAll(), fetchOne() etc.
zYne's avatar
zYne committed
41 42 43
 *
 * 4. Modular structure
 *    Higher level functionality such as schema importing, exporting, sequence handling etc.
44
 *    is divided into modules. For a full list of connection modules see
zYne's avatar
zYne committed
45 46
 *    Doctrine_Connection::$_modules
 *
47
 * @package     Doctrine
48
 * @subpackage  Connection
49
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
50
 * @link        www.phpdoctrine.org
51 52 53 54
 * @since       1.0
 * @version     $Revision$
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
 * @author      Lukas Smith <smith@pooteeweet.org> (MDB2 library)
romanb's avatar
romanb committed
55
 * @author      Roman Borschel <roman@code-factory.org>
romanb's avatar
romanb committed
56 57 58
 * @todo Split up into Doctrine::DBAL::Connection & Doctrine::ORM::EntityManager.
 *       Doctrine::DBAL::Connection must have no dependencies on ORM components since
 *       it sits one layer below.
59 60 61 62
 */
abstract class Doctrine_Connection extends Doctrine_Configurable implements Countable, IteratorAggregate
{
    /**
63 64 65
     * The PDO database handle. 
     *
     * @var PDO                 
66 67
     */
    protected $dbh;
68

69
    /**
romanb's avatar
romanb committed
70
     * The metadata factory is used to retrieve the metadata of entity classes.
71
     *
72
     * @var Doctrine_ClassMetadata_Factory
romanb's avatar
romanb committed
73
     * @todo package:orm
74
     */
75
    protected $_metadataFactory;
76

77
    /**
78 79
     * An array of mapper objects currently maintained by this connection.
     *
romanb's avatar
romanb committed
80 81
     * @var array
     * @todo package:orm 
82 83
     */
    protected $_mappers = array();
84

85 86 87 88 89 90 91 92
    /**
     * $_name
     *
     * Name of the connection
     *
     * @var string $_name
     */
    protected $_name;
93

94
    /**
95 96 97
     * The name of this connection driver.
     *
     * @var string $driverName                  
98 99
     */
    protected $driverName;
100

101
    /**
102 103 104
     * Whether or not a connection has been established.
     *
     * @var boolean $isConnected                
105
     */
106
    protected $isConnected = false;
107

108
    /**
109 110 111 112
     * An array containing all features this driver supports, keys representing feature
     * names and values as one of the following (true, false, 'emulated').
     *
     * @var array $supported                    
113 114
     */
    protected $supported        = array();
115

116 117 118 119 120 121
    /**
     * @var array $pendingAttributes            An array of pending attributes. When setting attributes
     *                                          no connection is needed. When connected all the pending
     *                                          attributes are passed to the underlying adapter (usually PDO) instance.
     */
    protected $pendingAttributes  = array();
122

123 124 125 126 127 128 129 130 131 132
    /**
     * @var array $modules                      an array containing all modules
     *              transaction                 Doctrine_Transaction driver, handles savepoint and transaction isolation abstraction
     *
     *              expression                  Doctrine_Expression driver, handles expression abstraction
     *
     *              dataDict                    Doctrine_DataDict driver, handles datatype abstraction
     *
     *              export                      Doctrine_Export driver, handles db structure modification abstraction (contains
     *                                          methods such as alterTable, createConstraint etc.)
133
     *              import                      Doctrine_Import driver, handles db schema reading
zYne's avatar
zYne committed
134 135
     *
     *              sequence                    Doctrine_Sequence driver, handles sequential id generation and retrieval
136
     *
zYne's avatar
zYne committed
137 138 139 140 141
     *              unitOfWork                  Doctrine_Connection_UnitOfWork handles many orm functionalities such as object
     *                                          deletion and saving
     *
     *              formatter                   Doctrine_Formatter handles data formatting, quoting and escaping
     *
142 143 144 145 146
     * @see Doctrine_Connection::__get()
     * @see Doctrine_DataDict
     * @see Doctrine_Expression
     * @see Doctrine_Export
     * @see Doctrine_Transaction
zYne's avatar
zYne committed
147
     * @see Doctrine_Sequence
zYne's avatar
zYne committed
148 149
     * @see Doctrine_Connection_UnitOfWork
     * @see Doctrine_Formatter
150 151 152 153 154
     */
    private $modules = array('transaction' => false,
                             'expression'  => false,
                             'dataDict'    => false,
                             'export'      => false,
155
                             'import'      => false,
zYne's avatar
zYne committed
156
                             'sequence'    => false,
157
                             'unitOfWork'  => false,
158 159
                             'formatter'   => false,
                             'util'        => false,
160
                             );
161

162 163 164 165
    /**
     * @var array $properties               an array of connection properties
     */
    protected $properties = array('sql_comments'        => array(array('start' => '--', 'end' => "\n", 'escape' => false),
zYne's avatar
zYne committed
166 167
                                                                 array('start' => '/*', 'end' => '*/', 'escape' => false)),
                                  'identifier_quoting'  => array('start' => '"', 'end' => '"','escape' => '"'),
168 169 170
                                  'string_quoting'      => array('start' => "'",
                                                                 'end' => "'",
                                                                 'escape' => false,
zYne's avatar
zYne committed
171
                                                                 'escape_pattern' => false),
zYne's avatar
zYne committed
172 173
                                  'wildcards'           => array('%', '_'),
                                  'varchar_max_length'  => 255,
174
                                  );
175

zYne's avatar
zYne committed
176 177 178 179
    /**
     * @var array $serverInfo
     */
    protected $serverInfo = array();
180

181 182 183
    /**
     *
     */
184
    protected $options    = array();
185

186
    /**
187
     * @var array $availableDrivers         an array containing all available drivers
188
     */
189
    private static $availableDrivers = array(
190 191 192 193 194 195 196 197
                                        'Mysql',
                                        'Pgsql',
                                        'Oracle',
                                        'Informix',
                                        'Mssql',
                                        'Sqlite',
                                        'Firebird'
                                        );
198

199 200 201 202 203
    /**
     * The query count. Represents the number of executed database queries by the connection.
     *
     * @var integer
     */
zYne's avatar
zYne committed
204
    protected $_count = 0;
205 206 207 208 209 210

    /**
     * the constructor
     *
     * @param Doctrine_Manager $manager                 the manager object
     * @param PDO|Doctrine_Adapter_Interface $adapter   database driver
romanb's avatar
romanb committed
211
     * @todo Remove the dependency on the Manager for DBAL/ORM separation.
212
     */
213
    public function __construct(Doctrine_Manager $manager, $adapter, $user = null, $pass = null)
214
    {
215
        if (is_object($adapter)) {
216 217
            if ( ! ($adapter instanceof PDO) && ! in_array('Doctrine_Adapter_Interface', class_implements($adapter))) {
                throw new Doctrine_Connection_Exception('First argument should be an instance of PDO or implement Doctrine_Adapter_Interface');
218
            }
219 220
            $this->dbh = $adapter;
            $this->isConnected = true;
221
        } else if (is_array($adapter)) {
222
            $this->pendingAttributes[Doctrine::ATTR_DRIVER_NAME] = $adapter['scheme'];
223

224 225 226
            $this->options['dsn']      = $adapter['dsn'];
            $this->options['username'] = $adapter['user'];
            $this->options['password'] = $adapter['pass'];
227 228

            $this->options['other'] = array();
229 230 231 232
            if (isset($adapter['other'])) {
                $this->options['other'] = array(Doctrine::ATTR_PERSISTENT => $adapter['persistent']);
            }

233
        }
234

romanb's avatar
romanb committed
235
        $this->setConfigurableParent($manager);
236

237 238
        $this->setAttribute(Doctrine::ATTR_CASE, Doctrine::CASE_NATURAL);
        $this->setAttribute(Doctrine::ATTR_ERRMODE, Doctrine::ERRMODE_EXCEPTION);
239 240 241

        $this->getAttribute(Doctrine::ATTR_LISTENER)->onOpen($this);
    }
242

243 244
    /**
     * getOption
245
     *
246 247
     * Retrieves option
     *
248
     * @param string $option
249 250 251 252 253 254 255 256
     * @return void
     */
    public function getOption($option)
    {
        if (isset($this->options[$option])) {
            return $this->options[$option];
        }
    }
257

jwage's avatar
jwage committed
258 259 260 261 262 263 264 265 266 267 268 269 270
    /**
     * setOption
     * 
     * Set option value
     *
     * @param string $option 
     * @return void
     */
    public function setOption($option, $value)
    {
      return $this->options[$option] = $value;
    }

271 272 273 274 275 276 277 278 279
    /**
     * getAttribute
     * retrieves a database connection attribute
     *
     * @param integer $attribute
     * @return mixed
     */
    public function getAttribute($attribute)
    {
280
        if ($attribute >= 100) {
romanb's avatar
romanb committed
281
            if ( ! isset($this->_attributes[$attribute])) {
gnat's avatar
gnat committed
282
                return parent::getAttribute($attribute);
283
            }
romanb's avatar
romanb committed
284
            return $this->_attributes[$attribute];
285
        }
286 287 288 289

        if ($this->isConnected) {
            try {
                return $this->dbh->getAttribute($attribute);
290
            } catch (Exception $e) {
291 292 293 294 295 296 297 298 299 300 301
                throw new Doctrine_Connection_Exception('Attribute ' . $attribute . ' not found.');
            }
        } else {
            if ( ! isset($this->pendingAttributes[$attribute])) {
                $this->connect();
                $this->getAttribute($attribute);
            }

            return $this->pendingAttributes[$attribute];
        }
    }
302

303 304 305 306 307 308 309
    /**
     * returns an array of available PDO drivers
     */
    public static function getAvailableDrivers()
    {
        return PDO::getAvailableDrivers();
    }
310

311 312 313 314
    /**
     * setAttribute
     * sets an attribute
     *
315
     * @todo why check for >= 100? has this any special meaning when creating
316 317
     * attributes?
     *
318 319 320 321 322 323
     * @param integer $attribute
     * @param mixed $value
     * @return boolean
     */
    public function setAttribute($attribute, $value)
    {
324
        if ($attribute >= 100) {
325
            parent::setAttribute($attribute, $value);
326
        } else {
327 328 329 330 331 332 333 334
            if ($this->isConnected) {
                $this->dbh->setAttribute($attribute, $value);
            } else {
                $this->pendingAttributes[$attribute] = $value;
            }
        }
        return $this;
    }
335

336 337 338 339 340 341 342
    /**
     * getName
     * returns the name of this driver
     *
     * @return string           the name of this driver
     */
    public function getName()
343 344 345
    {
        return $this->_name;
    }
346

347 348 349 350 351 352 353 354
    /**
     * setName
     *
     * Sets the name of the connection
     *
     * @param string $name 
     * @return void
     */
355 356 357 358
    public function setName($name)
    {
        $this->_name = $name;
    }
359

360 361 362 363 364 365 366
    /**
     * getDriverName
     *
     * Gets the name of the instance driver
     *
     * @return void
     */
367
    public function getDriverName()
368 369 370
    {
        return $this->driverName;
    }
371

372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
    /**
     * __get
     * lazy loads given module and returns it
     *
     * @see Doctrine_DataDict
     * @see Doctrine_Expression
     * @see Doctrine_Export
     * @see Doctrine_Transaction
     * @see Doctrine_Connection::$modules       all availible modules
     * @param string $name                      the name of the module to get
     * @throws Doctrine_Connection_Exception    if trying to get an unknown module
     * @return Doctrine_Connection_Module       connection module
     */
    public function __get($name)
    {
zYne's avatar
zYne committed
387
        if (isset($this->properties[$name])) {
388
            return $this->properties[$name];
389
        }
390 391 392 393 394 395 396 397 398

        if ( ! isset($this->modules[$name])) {
            throw new Doctrine_Connection_Exception('Unknown module / property ' . $name);
        }
        if ($this->modules[$name] === false) {
            switch ($name) {
                case 'unitOfWork':
                    $this->modules[$name] = new Doctrine_Connection_UnitOfWork($this);
                    break;
zYne's avatar
zYne committed
399 400 401
                case 'formatter':
                    $this->modules[$name] = new Doctrine_Formatter($this);
                    break;
402
                default:
403
                    $class = 'Doctrine_' . ucwords($name) . '_' . $this->getDriverName();
404 405 406 407 408 409
                    $this->modules[$name] = new $class($this);
                }
        }

        return $this->modules[$name];
    }
410

411 412 413 414 415 416 417 418 419
    /**
     * returns the manager that created this connection
     *
     * @return Doctrine_Manager
     */
    public function getManager()
    {
        return $this->getParent();
    }
420

421
    /**
422
     * returns the database handler which this connection uses
423 424 425 426 427
     *
     * @return PDO              the database handler
     */
    public function getDbh()
    {
428
        $this->connect();
429
        
430 431
        return $this->dbh;
    }
432

433
    /**
434
     * Establishes the connection with the database.
435 436 437 438 439 440 441 442 443
     *
     * @return boolean
     */
    public function connect()
    {
        if ($this->isConnected) {
            return false;
        }

444
        $event = new Doctrine_Event($this, Doctrine_Event::CONN_CONNECT);
445
        $this->getListener()->preConnect($event);
446

447
        $e = explode(':', $this->options['dsn']);
448
        $found = false;
449

450 451
        if (extension_loaded('pdo')) {
            if (in_array($e[0], PDO::getAvailableDrivers())) {
452
                $this->dbh = new PDO($this->options['dsn'], $this->options['username'],
453
                                     $this->options['password'], $this->options['other']);
454

455 456 457 458 459 460 461 462 463 464 465
                $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
                $found = true;
            }
        }

        if ( ! $found) {
            $class = 'Doctrine_Adapter_' . ucwords($e[0]);

            if (class_exists($class)) {
                $this->dbh = new $class($this->options['dsn'], $this->options['username'], $this->options['password']);
            } else {
466
                throw new Doctrine_Connection_Exception("Couldn't locate driver named " . $e[0]);
467 468 469
            }
        }

470
        // attach the pending attributes to adapter
471 472
        foreach($this->pendingAttributes as $attr => $value) {
            // some drivers don't support setting this so we just skip it
473
            if ($attr == Doctrine::ATTR_DRIVER_NAME) {
474 475 476 477 478 479 480
                continue;
            }
            $this->dbh->setAttribute($attr, $value);
        }

        $this->isConnected = true;

481
        $this->getListener()->postConnect($event);
482 483
        return true;
    }
484 485 486 487 488
    
    /**
     * @todo Remove. Breaks encapsulation.
     */
    public function incrementQueryCount() 
489 490 491
    {
        $this->_count++;
    }
492

493 494 495 496 497 498
    /**
     * converts given driver name
     *
     * @param
     */
    public function driverName($name)
499
    {}
500

501 502 503 504 505 506 507 508 509
    /**
     * supports
     *
     * @param string $feature   the name of the feature
     * @return boolean          whether or not this drivers supports given feature
     */
    public function supports($feature)
    {
        return (isset($this->supported[$feature])
zYne's avatar
zYne committed
510 511
                  && ($this->supported[$feature] === 'emulated'
                   || $this->supported[$feature]));
512
    }
513

514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
    /**
     * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
     * query, except that if there is already a row in the table with the same
     * key field values, the REPLACE query just updates its values instead of
     * inserting a new row.
     *
     * The REPLACE type of query does not make part of the SQL standards. Since
     * practically only MySQL and SQLIte implement it natively, this type of
     * query isemulated through this method for other DBMS using standard types
     * of queries inside a transaction to assure the atomicity of the operation.
     *
     * @param                   string  name of the table on which the REPLACE query will
     *                          be executed.
     *
     * @param   array           an associative array that describes the fields and the
     *                          values that will be inserted or updated in the specified table. The
     *                          indexes of the array are the names of all the fields of the table.
     *
     *                          The values of the array are values to be assigned to the specified field.
     *
     * @param array $keys       an array containing all key fields (primary key fields
     *                          or unique index fields) for this table
     *
     *                          the uniqueness of a row will be determined according to
     *                          the provided key fields
     *
     *                          this method will fail if no key fields are specified
     *
     * @throws Doctrine_Connection_Exception        if this driver doesn't support replace
     * @throws Doctrine_Connection_Exception        if some of the key values was null
     * @throws Doctrine_Connection_Exception        if there were no key fields
     * @throws PDOException                         if something fails at PDO level
546
     * @return integer                              number of rows affected
547
     */
romanb's avatar
romanb committed
548
    public function replace($tableName, array $data, array $keys)
549 550 551 552 553 554
    {
        if (empty($keys)) {
            throw new Doctrine_Connection_Exception('Not specified which fields are keys');
        }
        $condition = $values = array();

romanb's avatar
romanb committed
555 556
        foreach ($data as $columnName => $value) {
            $values[$columnName] = $value;
557

romanb's avatar
romanb committed
558
            if (in_array($columnName, $keys)) {
559
                if ($value === null)
romanb's avatar
romanb committed
560
                    throw new Doctrine_Connection_Exception('key value '.$columnName.' may not be null');
561

romanb's avatar
romanb committed
562
                $condition[] = $columnName . ' = ?';
563 564 565 566
                $conditionValues[] = $value;
            }
        }

romanb's avatar
romanb committed
567
        $query = 'DELETE FROM ' . $this->quoteIdentifier($tableName)
568 569
                . ' WHERE ' . implode(' AND ', $condition);
        $affectedRows = $this->exec($query, $conditionValues);
570 571 572 573 574 575 576

        $this->insert($table, $values);

        $affectedRows++;

        return $affectedRows;
    }
577

578
    /**
romanb's avatar
romanb committed
579
     * Deletes table row(s) matching the specified identifier.
580 581 582
     *
     * @throws Doctrine_Connection_Exception    if something went wrong at the database level
     * @param string $table         The table to delete data from
583
     * @param array $identifier     An associateve array containing identifier fieldname-value pairs.
584 585
     * @return integer              The number of affected rows
     */
romanb's avatar
romanb committed
586
    public function delete($tableName, array $identifier)
587
    {
588
        $criteria = array();
589
        foreach (array_keys($identifier) as $id) {
590
            $criteria[] = $this->quoteIdentifier($id) . ' = ?';
591 592 593
        }

        $query = 'DELETE FROM '
romanb's avatar
romanb committed
594
               . $this->quoteIdentifier($tableName)
595
               . ' WHERE ' . implode(' AND ', $criteria);
596

zYne's avatar
zYne committed
597
        return $this->exec($query, array_values($identifier));
598 599
    }

600 601 602 603 604 605 606 607 608
    /**
     * Updates table row(s) with specified data
     *
     * @throws Doctrine_Connection_Exception    if something went wrong at the database level
     * @param string $table     The table to insert data into
     * @param array $values     An associateve array containing column-value pairs.
     * @return mixed            boolean false if empty value array was given,
     *                          otherwise returns the number of affected rows
     */
romanb's avatar
romanb committed
609
    public function update($tableName, array $data, array $identifier)
610
    {
romanb's avatar
romanb committed
611
        if (empty($data)) {
612 613 614 615
            return false;
        }

        $set = array();
romanb's avatar
romanb committed
616
        foreach ($data as $columnName => $value) {
617
            if ($value instanceof Doctrine_Expression) {
618
                $set[] = $this->quoteIdentifier($columnName) . ' = ' . $value->getSql();
romanb's avatar
romanb committed
619
                unset($data[$columnName]);
620
            } else {
621
                $set[] = $this->quoteIdentifier($columnName) . ' = ?';
622 623 624
            }
        }

romanb's avatar
romanb committed
625
        $params = array_merge(array_values($data), array_values($identifier));
626

romanb's avatar
romanb committed
627
        $sql  = 'UPDATE ' . $this->quoteIdentifier($tableName)
628
              . ' SET ' . implode(', ', $set)
romanb's avatar
romanb committed
629
              . ' WHERE ' . implode(' = ? AND ', array_keys($identifier))
630 631 632 633 634
              . ' = ?';

        return $this->exec($sql, $params);
    }

635 636 637 638
    /**
     * Inserts a table row with specified data.
     *
     * @param string $table     The table to insert data into.
639
     * @param array $fields     An associateve array containing fieldname-value pairs.
640 641
     * @return mixed            boolean false if empty value array was given,
     *                          otherwise returns the number of affected rows
642
     */
romanb's avatar
romanb committed
643
    public function insert($tableName, array $data)
644
    {
romanb's avatar
romanb committed
645
        if (empty($data)) {
646 647 648
            return false;
        }

649 650 651
        // column names are specified as array keys
        $cols = array();
        // the query VALUES will contain either expresions (eg 'NOW()') or ?
652
        $a = array();
romanb's avatar
romanb committed
653 654
        foreach ($data as $columnName => $value) {
            $cols[] = $this->quoteIdentifier($columnName);
655
            if ($value instanceof Doctrine_Expression) {
656
                $a[] = $value->getSql();
romanb's avatar
romanb committed
657
                unset($data[$columnName]);
658
            } else {
659
                $a[] = '?';
660 661
            }
        }
662

663
        // build the statement
664
        $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName)
665 666 667
               . ' (' . implode(', ', $cols) . ') '
               . 'VALUES (';

668
        $query .= implode(', ', $a) . ')';
669
        // prepare and execute the statement
670

romanb's avatar
romanb committed
671
        return $this->exec($query, array_values($data));
672
    }
673

674 675 676 677 678 679 680
    /**
     * Set the charset on the current connection
     *
     * @param string    charset
     */
    public function setCharset($charset)
    {
681
        return true;
zYne's avatar
zYne committed
682
    }
683

zYne's avatar
zYne committed
684
    /**
685
     * Quote a string so it can be safely used as a table or column name.
zYne's avatar
zYne committed
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
     *
     * Delimiting style depends on which database driver is being used.
     *
     * NOTE: just because you CAN use delimited identifiers doesn't mean
     * you SHOULD use them.  In general, they end up causing way more
     * problems than they solve.
     *
     * Portability is broken by using the following characters inside
     * delimited identifiers:
     *   + backtick (<kbd>`</kbd>) -- due to MySQL
     *   + double quote (<kbd>"</kbd>) -- due to Oracle
     *   + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access
     *
     * Delimited identifiers are known to generally work correctly under
     * the following drivers:
     *   + mssql
     *   + mysql
     *   + mysqli
     *   + oci8
     *   + pgsql
     *   + sqlite
     *
     * InterBase doesn't seem to be able to use delimited identifiers
     * via PHP 4.  They work fine under PHP 5.
     *
     * @param string $str           identifier name to be quoted
     * @param bool $checkOption     check the 'quote_identifier' option
     *
     * @return string               quoted identifier string
     */
    public function quoteIdentifier($str, $checkOption = true)
    {
718
        // quick fix for the identifiers that contain a dot
zYne's avatar
zYne committed
719 720
        if (strpos($str, '.')) {
            $e = explode('.', $str);
721 722

            return $this->formatter->quoteIdentifier($e[0], $checkOption) . '.'
zYne's avatar
zYne committed
723 724
                 . $this->formatter->quoteIdentifier($e[1], $checkOption);
        }
zYne's avatar
zYne committed
725 726
        return $this->formatter->quoteIdentifier($str, $checkOption);
    }
727

zYne's avatar
zYne committed
728 729 730 731 732 733 734 735 736 737 738 739 740 741
    /**
     * convertBooleans
     * some drivers need the boolean values to be converted into integers
     * when using DQL API
     *
     * This method takes care of that conversion
     *
     * @param array $item
     * @return void
     */
    public function convertBooleans($item)
    {
        return $this->formatter->convertBooleans($item);
    }
742

zYne's avatar
zYne committed
743 744 745 746 747 748 749 750 751 752 753
    /**
     * quote
     * quotes given input parameter
     *
     * @param mixed $input      parameter to be quoted
     * @param string $type
     * @return mixed
     */
    public function quote($input, $type = null)
    {
        return $this->formatter->quote($input, $type);
754
    }
755

756 757 758 759 760 761 762 763 764 765
    /**
     * Set the date/time format for the current connection
     *
     * @param string    time format
     *
     * @return void
     */
    public function setDateFormat($format = null)
    {
    }
766

767 768 769 770 771 772 773
    /**
     * fetchAll
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
774
    public function fetchAll($statement, array $params = array())
zYne's avatar
zYne committed
775
    {
776
        return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_ASSOC);
777
    }
778

779 780 781 782 783 784 785 786
    /**
     * fetchOne
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @param int $colnum               0-indexed column number to retrieve
     * @return mixed
     */
787
    public function fetchOne($statement, array $params = array(), $colnum = 0)
zYne's avatar
zYne committed
788
    {
789 790
        return $this->execute($statement, $params)->fetchColumn($colnum);
    }
791

792 793 794 795 796 797 798
    /**
     * fetchRow
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
799
    public function fetchRow($statement, array $params = array())
zYne's avatar
zYne committed
800
    {
801
        return $this->execute($statement, $params)->fetch(Doctrine::FETCH_ASSOC);
802
    }
803

804 805 806 807 808 809 810
    /**
     * fetchArray
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
811
    public function fetchArray($statement, array $params = array())
zYne's avatar
zYne committed
812
    {
813
        return $this->execute($statement, $params)->fetch(Doctrine::FETCH_NUM);
814
    }
815

816 817 818 819 820 821 822 823
    /**
     * fetchColumn
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @param int $colnum               0-indexed column number to retrieve
     * @return array
     */
824
    public function fetchColumn($statement, array $params = array(), $colnum = 0)
zYne's avatar
zYne committed
825
    {
826
        return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_COLUMN, $colnum);
827
    }
828

829 830 831 832 833 834 835
    /**
     * fetchAssoc
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
836
    public function fetchAssoc($statement, array $params = array())
zYne's avatar
zYne committed
837
    {
838
        return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_ASSOC);
839
    }
840

841 842 843 844 845 846 847
    /**
     * fetchBoth
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
848
    public function fetchBoth($statement, array $params = array())
zYne's avatar
zYne committed
849
    {
850
        return $this->execute($statement, $params)->fetchAll(Doctrine::FETCH_BOTH);
851
    }
852

853 854 855 856 857 858 859 860 861 862 863 864 865
    /**
     * query
     * queries the database using Doctrine Query Language
     * returns a collection of Doctrine_Record objects
     *
     * <code>
     * $users = $conn->query('SELECT u.* FROM User u');
     *
     * $users = $conn->query('SELECT u.* FROM User u WHERE u.name LIKE ?', array('someone'));
     * </code>
     *
     * @param string $query             DQL query
     * @param array $params             query parameters
866
     * @param int $hydrationMode        Doctrine::FETCH_ARRAY or Doctrine::FETCH_RECORD
867 868
     * @see Doctrine_Query
     * @return Doctrine_Collection      Collection of Doctrine_Record objects
869
     * @todo package:orm
870
     */
871
    public function query($query, array $params = array(), $hydrationMode = null)
zYne's avatar
zYne committed
872
    {
873 874
        $parser = new Doctrine_Query($this);

875
        return $parser->query($query, $params, $hydrationMode);
876
    }
877

878 879 880 881 882 883 884 885 886
    /**
     * prepare
     *
     * @param string $statement
     */
    public function prepare($statement)
    {
        $this->connect();

zYne's avatar
zYne committed
887 888
        try {
            $event = new Doctrine_Event($this, Doctrine_Event::CONN_PREPARE, $statement);
889
    
zYne's avatar
zYne committed
890
            $this->getAttribute(Doctrine::ATTR_LISTENER)->prePrepare($event);
891

zYne's avatar
zYne committed
892
            $stmt = false;
893
    
zYne's avatar
zYne committed
894 895 896
            if ( ! $event->skipOperation) {
                $stmt = $this->dbh->prepare($statement);
            }
897
    
zYne's avatar
zYne committed
898
            $this->getAttribute(Doctrine::ATTR_LISTENER)->postPrepare($event);
899
            
zYne's avatar
zYne committed
900 901 902
            return new Doctrine_Connection_Statement($this, $stmt);
        } catch(Doctrine_Adapter_Exception $e) {
        } catch(PDOException $e) { }
903

zYne's avatar
zYne committed
904
        $this->rethrowException($e, $this);
905
    }
906

907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925
    /**
     * query
     * queries the database using Doctrine Query Language and returns
     * the first record found
     *
     * <code>
     * $user = $conn->queryOne('SELECT u.* FROM User u WHERE u.id = ?', array(1));
     *
     * $user = $conn->queryOne('SELECT u.* FROM User u WHERE u.name LIKE ? AND u.password = ?',
     *         array('someone', 'password')
     *         );
     * </code>
     *
     * @param string $query             DQL query
     * @param array $params             query parameters
     * @see Doctrine_Query
     * @return Doctrine_Record|false    Doctrine_Record object on success,
     *                                  boolean false on failure
     */
926
    public function queryOne($query, array $params = array())
zYne's avatar
zYne committed
927
    {
928 929 930 931 932 933 934 935
        $parser = new Doctrine_Query($this);

        $coll = $parser->query($query, $params);
        if ( ! $coll->contains(0)) {
            return false;
        }
        return $coll[0];
    }
936

937 938
    /**
     * queries the database with limit and offset
zYne's avatar
zYne committed
939
     * added to the query and returns a Doctrine_Connection_Statement object
940 941 942 943
     *
     * @param string $query
     * @param integer $limit
     * @param integer $offset
zYne's avatar
zYne committed
944
     * @return Doctrine_Connection_Statement
945
     */
946
    public function select($query, $limit = 0, $offset = 0)
947 948 949 950
    {
        if ($limit > 0 || $offset > 0) {
            $query = $this->modifyLimitQuery($query, $limit, $offset);
        }
zYne's avatar
zYne committed
951
        return $this->execute($query);
952
    }
953

zYne's avatar
zYne committed
954 955 956 957 958 959 960 961 962 963 964 965
    /**
     * standaloneQuery
     *
     * @param string $query     sql query
     * @param array $params     query parameters
     *
     * @return PDOStatement|Doctrine_Adapter_Statement
     */
    public function standaloneQuery($query, $params = array())
    {
        return $this->execute($query, $params);
    }
966

967 968 969 970 971 972 973
    /**
     * execute
     * @param string $query     sql query
     * @param array $params     query parameters
     *
     * @return PDOStatement|Doctrine_Adapter_Statement
     */
974
    public function execute($query, array $params = array())
zYne's avatar
zYne committed
975
    {
976
        $this->connect();
977

978 979
        try {
            if ( ! empty($params)) {
980
                //echo $query . "<br />";
981
                $stmt = $this->prepare($query);
982
                $stmt->execute($params);
zYne's avatar
zYne committed
983
                return $stmt;
984
            } else {
985
                $event = new Doctrine_Event($this, Doctrine_Event::CONN_QUERY, $query, $params);
986

987
                $this->getAttribute(Doctrine::ATTR_LISTENER)->preQuery($event);
988

989
                if ( ! $event->skipOperation) {
990 991 992 993 994 995 996
                    //try {
                        $stmt = $this->dbh->query($query);
                    /*} catch (Exception $e) {
                        if (strstr($e->getMessage(), 'no such column')) {
                            echo $query . "<br /><br />";
                        }
                    }*/
997

998 999 1000
                    $this->_count++;
                }
                $this->getAttribute(Doctrine::ATTR_LISTENER)->postQuery($event);
1001 1002

                return $stmt;
1003
            }
1004 1005
        } catch (Doctrine_Adapter_Exception $e) {
        } catch (PDOException $e) { }
zYne's avatar
zYne committed
1006

zYne's avatar
zYne committed
1007
        $this->rethrowException($e, $this);
1008
    }
1009

1010 1011 1012 1013 1014 1015 1016 1017
    /**
     * exec
     * @param string $query     sql query
     * @param array $params     query parameters
     *
     * @return PDOStatement|Doctrine_Adapter_Statement
     */
    public function exec($query, array $params = array()) {
1018
        $this->connect();
zYne's avatar
zYne committed
1019

1020 1021
        try {
            if ( ! empty($params)) {
1022
                $stmt = $this->prepare($query);
1023
                $stmt->execute($params);
zYne's avatar
zYne committed
1024
                return $stmt->rowCount();
1025
            } else {
1026
                $event = new Doctrine_Event($this, Doctrine_Event::CONN_EXEC, $query, $params);
1027

1028
                $this->getAttribute(Doctrine::ATTR_LISTENER)->preExec($event);
1029

1030 1031 1032 1033 1034
                if ( ! $event->skipOperation) {
                    $count = $this->dbh->exec($query);
                    $this->_count++;
                }
                $this->getAttribute(Doctrine::ATTR_LISTENER)->postExec($event);
1035 1036

                return $count;
1037
            }
1038 1039
        } catch (Doctrine_Adapter_Exception $e) {
        } catch (PDOException $e) { }
1040

zYne's avatar
zYne committed
1041
        $this->rethrowException($e, $this);
1042
    }
1043

1044 1045 1046 1047 1048
    /**
     * rethrowException
     *
     * @throws Doctrine_Connection_Exception
     */
zYne's avatar
zYne committed
1049
    public function rethrowException(Exception $e, $invoker)
1050
    {
1051 1052
        $event = new Doctrine_Event($this, Doctrine_Event::CONN_ERROR);
        $this->getListener()->preError($event);
1053

1054
        if (strstr($e->getMessage(), 'may not be NULL')) {
1055
            echo $e->getMessage() . "<br />" . $e->getTraceAsString() . "<br />";
1056
        }
1057
        
1058 1059
        $name = 'Doctrine_Connection_' . $this->driverName . '_Exception';

1060
        $exc = new $name($e->getMessage(), (int) $e->getCode());
1061 1062 1063 1064 1065
        if ( ! is_array($e->errorInfo)) {
            $e->errorInfo = array(null, null, null, null);
        }
        $exc->processErrorInfo($e->errorInfo);

zYne's avatar
zYne committed
1066 1067 1068
         if ($this->getAttribute(Doctrine::ATTR_THROW_EXCEPTIONS)) {
            throw $exc;
        }
1069

zYne's avatar
zYne committed
1070
        $this->getListener()->postError($event);
1071
    }
1072

1073 1074 1075 1076 1077 1078
    /**
     * hasTable
     * whether or not this connection has table $name initialized
     *
     * @param mixed $name
     * @return boolean
romanb's avatar
romanb committed
1079 1080
     * @deprecated
     * @todo package:orm
1081 1082 1083 1084 1085
     */
    public function hasTable($name)
    {
        return isset($this->tables[$name]);
    }
1086

1087
    /**
1088
     * Returns the metadata for a class.
1089
     *
1090
     * @return Doctrine_Metadata
romanb's avatar
romanb committed
1091
     * @deprecated Use getClassMetadata()
1092
     * @todo package:orm
1093
     */
1094
    public function getMetadata($className)
1095
    {
1096 1097
        return $this->getClassMetadata($className);
    }
1098

1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
    /**
     * Returns the metadata for a class.
     *
     * @return Doctrine_Metadata
     * @todo package:orm
     */
    public function getClassMetadata($className)
    {
        if ( ! $this->_metadataFactory) {
            $this->_metadataFactory = new Doctrine_ClassMetadata_Factory($this,
                    new Doctrine_ClassMetadata_CodeDriver());
1110
        }
1111 1112
        
        return $this->_metadataFactory->getMetadataFor($className);
1113
    }
1114

1115 1116 1117 1118 1119
    /**
     * Sets the driver that is used to obtain metadata informations about entity
     * classes.
     *
     * @param $driver  The driver to use.
romanb's avatar
romanb committed
1120
     * @todo package:orm
1121 1122 1123 1124 1125 1126
     */
    public function setClassMetadataDriver($driver)
    {
        $this->_metadataFactory->setDriver($driver);
    }
    
1127
    /**
1128
     * Gets a mapper for the specified domain class that is used to map instances of
1129
     * the class between the relational database and their object representation.
1130
     *
romanb's avatar
romanb committed
1131
     * @param string $entityClassName  The name of the entity class.
1132 1133
     * @return Doctrine_Mapper  The mapper object.
     * @todo package:orm  
1134
     */
1135
    /*public function getMapper($entityName)
1136
    {
1137 1138
        if (isset($this->_mappers[$entityName])) {
            return $this->_mappers[$entityName];
1139
        }
1140

1141
        $metadata = $this->getClassMetadata($entityName);
romanb's avatar
romanb committed
1142 1143
        $customMapperClassName = $metadata->getCustomMapperClass();
        if ($customMapperClassName !== null) {
1144
            $mapper = new $customMapperClassName($entityName, $metadata);
1145
        } else {
1146
            // instantiate correct mapper type
1147
            $inheritanceType = $metadata->getInheritanceType();
romanb's avatar
romanb committed
1148
            if ($inheritanceType == Doctrine::INHERITANCE_TYPE_JOINED) {
1149
                $mapper = new Doctrine_Mapper_Joined($entityName, $metadata);
romanb's avatar
romanb committed
1150
            } else if ($inheritanceType == Doctrine::INHERITANCE_TYPE_SINGLE_TABLE) {
1151
                $mapper = new Doctrine_Mapper_SingleTable($entityName, $metadata);
romanb's avatar
romanb committed
1152
            } else if ($inheritanceType == Doctrine::INHERITANCE_TYPE_TABLE_PER_CLASS) {
1153
                $mapper = new Doctrine_Mapper_TablePerClass($entityName, $metadata);
1154 1155 1156
            } else {
                throw new Doctrine_Connection_Exception("Unknown inheritance type '$inheritanceType'. Can't create mapper.");
            }
1157
        }
1158

1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185
        $this->_mappers[$entityName] = $mapper;

        return $mapper;
    }*/
    
    /**
     * Gets a mapper for the specified domain class that is used to map instances of
     * the class between the relational database and their object representation.
     *
     * @param string $entityClassName  The name of the entity class.
     * @return Doctrine_Mapper  The mapper object.
     * @todo package:orm  
     */
    public function getMapper($entityName)
    {
        if (isset($this->_mappers[$entityName])) {
            return $this->_mappers[$entityName];
        }

        $metadata = $this->getClassMetadata($entityName);
        $customMapperClassName = $metadata->getCustomMapperClass();
        if ($customMapperClassName !== null) {
            $mapper = new $customMapperClassName($entityName, $metadata);
        } else {
            $mapper = new Doctrine_Mapper($entityName, $metadata);
        }
        $this->_mappers[$entityName] = $mapper;
1186

1187
        return $mapper;
1188
    }
1189

1190
    /**
1191
     * Gets all mappers that are currently maintained by the connection.
1192
     *
1193
     * @todo package:orm
1194
     */
1195 1196 1197 1198
    public function getMappers()
    {
        return $this->_mappers;
    }
1199

1200
    /**
1201
     * returns an iterator that iterates through all
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215
     * initialized table objects
     *
     * <code>
     * foreach ($conn as $index => $table) {
     *      print $table;  // get a string representation of each table object
     * }
     * </code>
     *
     * @return ArrayIterator        SPL ArrayIterator object
     */
    public function getIterator()
    {
        return new ArrayIterator($this->tables);
    }
1216

1217
    /**
1218
     * Returns the number of queries executed by the connection.
1219 1220
     *
     * @return integer
1221
     * @todo Better name: getQueryCount()
1222 1223 1224
     */
    public function count()
    {
1225
        return $this->_count;
1226
    }
1227

1228 1229 1230 1231 1232 1233 1234
    /**
     * create
     * creates a record
     *
     * create                       creates a record
     * @param string $name          component name
     * @return Doctrine_Record      Doctrine_Record object
1235 1236
     * @todo Any strong reasons why this should not be removed?
     * @todo package:orm
1237 1238 1239
     */
    public function create($name)
    {
1240
        return $this->getMapper($name)->create();
1241
    }
1242

romanb's avatar
romanb committed
1243 1244
    /**
     * Creates a new Doctrine_Query object that operates on this connection.
1245
     * 
1246
     * @return Doctrine_Query
1247
     * @todo package:orm
romanb's avatar
romanb committed
1248
     */
1249
    public function createQuery($dql = "")
romanb's avatar
romanb committed
1250
    {
1251 1252 1253 1254 1255 1256
        $query = new Doctrine_Query($this);
        if ( ! empty($dql)) {
            $query->parseQuery($dql);
        }
        
        return $query;
romanb's avatar
romanb committed
1257
    }
1258

1259 1260 1261 1262 1263 1264 1265
    /**
     * flush
     * saves all the records from all tables
     * this operation is isolated using a transaction
     *
     * @throws PDOException         if something went wrong at database level
     * @return void
1266
     * @todo package:orm
1267 1268 1269
     */
    public function flush()
    {
1270
        $this->beginInternalTransaction();
1271
        $this->unitOfWork->flush();
1272 1273
        $this->commit();
    }
1274

1275 1276 1277 1278 1279
    /**
     * clear
     * clears all repositories
     *
     * @return void
1280
     * @todo package:orm
1281 1282 1283
     */
    public function clear()
    {
1284
        $this->unitOfWork->detachAll();
1285
        foreach ($this->_mappers as $mapper) {
1286
            $mapper->clear(); // clear identity map of each mapper
1287 1288
        }
    }
1289

1290 1291 1292 1293 1294
    /**
     * evictTables
     * evicts all tables
     *
     * @return void
1295
     * @todo package:orm
1296
     * @deprecated
1297 1298 1299
     */
    public function evictTables()
    {
1300
        $this->clear();
1301
        $this->tables = array();
1302
        $this->_mappers = array();
1303
        $this->exported = array();
1304
    }
1305

1306
    /**
1307
     * Closes the connection.
1308 1309 1310 1311 1312
     *
     * @return void
     */
    public function close()
    {
1313
        $event = new Doctrine_Event($this, Doctrine_Event::CONN_CLOSE);
zYne's avatar
zYne committed
1314
        $this->getAttribute(Doctrine::ATTR_LISTENER)->preClose($event);
1315 1316

        $this->clear();
1317

zYne's avatar
zYne committed
1318
        unset($this->dbh);
1319
        $this->isConnected = false;
1320

zYne's avatar
zYne committed
1321
        $this->getAttribute(Doctrine::ATTR_LISTENER)->postClose($event);
1322
    }
1323

1324
    /**
1325
     * Returns the current total transaction nesting level.
1326
     *
1327
     * @return integer  The nesting level. A value of 0 means theres no active transaction.
1328 1329 1330 1331 1332
     */
    public function getTransactionLevel()
    {
        return $this->transaction->getTransactionLevel();
    }
1333

1334
    /**
1335
     * Returns the current internal transaction nesting level.
1336
     *
1337 1338
     * @return integer  The nesting level. A value of 0 means theres no active transaction.
     * @todo package:orm???
1339 1340 1341 1342 1343
     */
    public function getInternalTransactionLevel()
    {
        return $this->transaction->getInternalTransactionLevel();
    }
1344

1345 1346 1347 1348 1349 1350 1351 1352
    /**
     * errorCode
     * Fetch the SQLSTATE associated with the last operation on the database handle
     *
     * @return integer
     */
    public function errorCode()
    {
1353
        $this->connect();
1354
        
1355 1356
        return $this->dbh->errorCode();
    }
1357

1358 1359 1360 1361 1362 1363 1364 1365
    /**
     * errorInfo
     * Fetch extended error information associated with the last operation on the database handle
     *
     * @return array
     */
    public function errorInfo()
    {
1366
        $this->connect();
1367
        
1368 1369
        return $this->dbh->errorInfo();
    }
1370

1371 1372 1373 1374 1375 1376 1377 1378 1379 1380
    /**
     * getCacheDriver
     *
     * @return Doctrine_Cache_Interface
     * @deprecated Use getResultCacheDriver()
     */
    public function getCacheDriver()
    {
        return $this->getResultCacheDriver();
    }
1381

1382 1383 1384 1385
    /**
     * getResultCacheDriver
     *
     * @return Doctrine_Cache_Interface
1386
     * @todo package:orm
1387 1388 1389
     */
    public function getResultCacheDriver()
    {
1390
        if ( ! $this->getAttribute(Doctrine::ATTR_RESULT_CACHE)) {
1391 1392
            throw new Doctrine_Exception('Result Cache driver not initialized.');
        }
1393
        
1394 1395
        return $this->getAttribute(Doctrine::ATTR_RESULT_CACHE);
    }
1396

1397 1398 1399 1400
    /**
     * getQueryCacheDriver
     *
     * @return Doctrine_Cache_Interface
1401
     * @todo package:orm
1402 1403 1404 1405 1406 1407
     */
    public function getQueryCacheDriver()
    {
        if ( ! $this->getAttribute(Doctrine::ATTR_QUERY_CACHE)) {
            throw new Doctrine_Exception('Query Cache driver not initialized.');
        }
1408
        
1409
        return $this->getAttribute(Doctrine::ATTR_QUERY_CACHE);
1410
    }
1411

zYne's avatar
zYne committed
1412 1413 1414 1415 1416 1417
    /**
     * lastInsertId
     *
     * Returns the ID of the last inserted row, or the last value from a sequence object,
     * depending on the underlying driver.
     *
1418
     * Note: This method may not return a meaningful or consistent result across different drivers,
zYne's avatar
zYne committed
1419 1420
     * because the underlying database may not even support the notion of auto-increment fields or sequences.
     *
1421 1422
     * @param string $table     Name of the table into which a new row was inserted.
     * @param string $field     Name of the field into which a new row was inserted.
zYne's avatar
zYne committed
1423 1424 1425 1426 1427
     */
    public function lastInsertId($table = null, $field = null)
    {
        return $this->sequence->lastInsertId($table, $field);
    }
1428

1429 1430
    /**
     * beginTransaction
1431
     * Start a transaction or set a savepoint.
1432
     *
1433 1434
     * if trying to set a savepoint and there is no active transaction
     * a new transaction is being started
1435
     *
1436 1437 1438 1439 1440
     * Listeners: onPreTransactionBegin, onTransactionBegin
     *
     * @param string $savepoint                 name of a savepoint to set
     * @throws Doctrine_Transaction_Exception   if the transaction fails at database level
     * @return integer                          current transaction nesting level
1441
     */
1442
    public function beginTransaction($savepoint = null)
1443
    {
Jonathan.Wage's avatar
Jonathan.Wage committed
1444
        return $this->transaction->beginTransaction($savepoint);
1445
    }
1446

1447 1448 1449 1450 1451
    /**
     * Initiates a transaction.
     *
     * This method must only be used by Doctrine itself to initiate transactions.
     * Userland-code must use {@link beginTransaction()}.
1452 1453
     *
     * @todo package:orm???
1454
     */
1455 1456
    public function beginInternalTransaction($savepoint = null)
    {
Jonathan.Wage's avatar
Jonathan.Wage committed
1457
        return $this->transaction->beginInternalTransaction($savepoint);
1458
    }
1459

1460
    /**
1461 1462 1463 1464
     * commit
     * Commit the database changes done during a transaction that is in
     * progress or release a savepoint. This function may only be called when
     * auto-committing is disabled, otherwise it will fail.
1465
     *
1466 1467 1468 1469 1470 1471
     * Listeners: onPreTransactionCommit, onTransactionCommit
     *
     * @param string $savepoint                 name of a savepoint to release
     * @throws Doctrine_Transaction_Exception   if the transaction fails at PDO level
     * @throws Doctrine_Validator_Exception     if the transaction fails due to record validations
     * @return boolean                          false if commit couldn't be performed, true otherwise
1472
     */
1473
    public function commit($savepoint = null)
1474
    {
Jonathan.Wage's avatar
Jonathan.Wage committed
1475
        return $this->transaction->commit($savepoint);
1476
    }
1477

1478 1479
    /**
     * rollback
1480 1481 1482 1483
     * Cancel any database changes done during a transaction or since a specific
     * savepoint that is in progress. This function may only be called when
     * auto-committing is disabled, otherwise it will fail. Therefore, a new
     * transaction is implicitly started after canceling the pending changes.
1484
     *
1485 1486
     * this method can be listened with onPreTransactionRollback and onTransactionRollback
     * eventlistener methods
1487
     *
1488
     * @param string $savepoint                 name of a savepoint to rollback to
1489 1490
     * @throws Doctrine_Transaction_Exception   if the rollback operation fails at database level
     * @return boolean                          false if rollback couldn't be performed, true otherwise
1491
     */
1492
    public function rollback($savepoint = null)
1493
    {
1494
        $this->transaction->rollback($savepoint);
1495
    }
zYne's avatar
zYne committed
1496

1497 1498 1499
    /**
     * createDatabase
     *
jwage's avatar
-  
jwage committed
1500 1501 1502
     * Method for creating the database for the connection instance
     *
     * @return mixed Will return an instance of the exception thrown if the create database fails, otherwise it returns a string detailing the success
1503 1504 1505
     */
    public function createDatabase()
    {
jwage's avatar
-  
jwage committed
1506 1507 1508 1509
        try {
            if ( ! $dsn = $this->getOption('dsn')) {
                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');
            }
1510

jwage's avatar
-  
jwage committed
1511
            $manager = $this->getManager();
1512

jwage's avatar
-  
jwage committed
1513 1514 1515
            $info = $manager->parsePdoDsn($dsn);
            $username = $this->getOption('username');
            $password = $this->getOption('password');
1516

jwage's avatar
-  
jwage committed
1517 1518
            // Make connection without database specified so we can create it
            $connect = $manager->openConnection(new PDO($info['scheme'] . ':host=' . $info['host'], $username, $password), 'tmp_connection', false);
1519

jwage's avatar
-  
jwage committed
1520 1521
            // Create database
            $connect->export->createDatabase($info['dbname']);
1522

jwage's avatar
-  
jwage committed
1523 1524
            // Close the tmp connection with no database
            $manager->closeConnection($connect);
1525

jwage's avatar
-  
jwage committed
1526 1527 1528 1529 1530 1531 1532 1533 1534 1535
            // Close original connection
            $manager->closeConnection($this);

            // Reopen original connection with newly created database
            $manager->openConnection(new PDO($info['dsn'], $username, $password), $this->getName(), true);

            return 'Successfully created database for connection "' . $this->getName() . '" named "' . $info['dbname'] . '"';
        } catch (Exception $e) {
            return $e;
        }
1536 1537 1538 1539 1540
    }

    /**
     * dropDatabase
     *
jwage's avatar
-  
jwage committed
1541 1542 1543
     * Method for dropping the database for the connection instance
     *
     * @return mixed Will return an instance of the exception thrown if the drop database fails, otherwise it returns a string detailing the success
1544 1545 1546 1547
     */
    public function dropDatabase()
    {
      try {
jwage's avatar
-  
jwage committed
1548 1549 1550 1551 1552
          if ( ! $dsn = $this->getOption('dsn')) {
              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');
          }

          $info = $this->getManager()->parsePdoDsn($dsn);
1553

1554 1555 1556 1557 1558 1559 1560 1561
          $this->export->dropDatabase($info['dbname']);

          return 'Successfully dropped database for connection "' . $this->getName() . '" named "' . $info['dbname'] . '"';
      } catch (Exception $e) {
          return $e;
      }
    }

1562 1563 1564 1565 1566 1567 1568 1569
    /**
     * returns a string representation of this object
     * @return string
     */
    public function __toString()
    {
        return Doctrine_Lib::getConnectionAsString($this);
    }
romanb's avatar
romanb committed
1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613
    
    public function hasAttribute($key)
    {
        switch ($key) {
            case Doctrine::ATTR_COLL_KEY:
            case Doctrine::ATTR_LISTENER:
            case Doctrine::ATTR_RECORD_LISTENER:
            case Doctrine::ATTR_QUOTE_IDENTIFIER:
            case Doctrine::ATTR_SEQCOL_NAME:
            case Doctrine::ATTR_FIELD_CASE:
            case Doctrine::ATTR_IDXNAME_FORMAT:
            case Doctrine::ATTR_SEQNAME_FORMAT:
            case Doctrine::ATTR_DBNAME_FORMAT:
            case Doctrine::ATTR_TBLCLASS_FORMAT:
            case Doctrine::ATTR_TBLNAME_FORMAT:
            case Doctrine::ATTR_EXPORT:
            case Doctrine::ATTR_DECIMAL_PLACES:
            case Doctrine::ATTR_PORTABILITY:
            case Doctrine::ATTR_VALIDATE:
            case Doctrine::ATTR_QUERY_LIMIT:
            case Doctrine::ATTR_DEFAULT_TABLE_TYPE:
            case Doctrine::ATTR_DEF_TEXT_LENGTH:
            case Doctrine::ATTR_DEF_VARCHAR_LENGTH:
            case Doctrine::ATTR_DEF_TABLESPACE:
            case Doctrine::ATTR_EMULATE_DATABASE:
            case Doctrine::ATTR_USE_NATIVE_ENUM:
            case Doctrine::ATTR_CREATE_TABLES:
            case Doctrine::ATTR_COLL_LIMIT:
            case Doctrine::ATTR_CACHE: // deprecated
            case Doctrine::ATTR_RESULT_CACHE:
            case Doctrine::ATTR_CACHE_LIFESPAN: // deprecated
            case Doctrine::ATTR_RESULT_CACHE_LIFESPAN:
            case Doctrine::ATTR_LOAD_REFERENCES:
            case Doctrine::ATTR_THROW_EXCEPTIONS:
            case Doctrine::ATTR_QUERY_CACHE:
            case Doctrine::ATTR_QUERY_CACHE_LIFESPAN:
            case Doctrine::ATTR_MODEL_LOADING:
            case Doctrine::ATTR_METADATA_CACHE:
            case Doctrine::ATTR_METADATA_CACHE_LIFESPAN:
                return true;
            default:
                return false;
        }
    }
1614
}