<?php

require_once 'orm/OrmBaseDb.php';
require_once 'MDB2.php';

/**
 * This is a class to centralize use of database.
 * 
 * This implementation uses Pear::MDB2 as the back handler for database. Also, 
 * it does all query work in <em>runQuery</em>, so left operation's stub 
 * methods unmodified (select, insert, update, and delete).
 * MDB2 raises an error if transactions aren't supported for DB. Here, this 
 * error is hidden, because we prefer that everything goes on without them.
 * Note that not all Pear::MDB2's DSN are supported. In some cases, creating 
 * the DSN object from a Collection could bring better results.
 * 
 * @author  $Author: pruna $
 * @package orm/db
 * @version $Revision: 1.3 $
 * @created 19-Oct-2005
 * @modified $Date: 2005/12/06 02:56:45 $
 */
class OrmDbPearMdb2 extends OrmBaseDb {
    
    /**
     * Internal holder for database result set.
     *
     * @var      object
     * @access   protected
     */
    protected $rs;
    
    /**
     * Starts a database transaction, and returns a transaction identifier.
     * Internally, also sets autoCommit to false.
     * 
     * @access  public
     * @return  mixed
     */
    public function beginTransaction() {
        $this->getConn()->beginTransaction();
        return parent::openTransactionBlock();
    }
    
    /**
     * Commits a database transaction.
     * 
     * @access  public
     * @param   mixed     $blockId     Transaction block identifier
     * @return  void
     */
    public function commit($blockId = null) {
        parent::commit($blockId);
        $this->getConn()->commit();
    }
    
    /**
     * Connects to database.
     * 
     * It creates a new database connection. It uses <em>Dsn</em> if provided,  
     * else call <em>getDsn</em>. If fail, throw an <em>OrmException</em>.
     * You also create a connection (getting this method called) by invoking 
     * <em>setDsn</em> with a not null parameter.
     *
     * @access  public
     * @param   Dsn     $dsn        DSN info for database connection
     * @throws  OrmException
     */
    public function connect($dsn = null) {
        if ($dsn) {
            $this->dsn = $dsn;
        }
        $this->setConn(MDB2::connect($this->getDsn()->implode()));
        $this->getConn()->setOption('portability',
                MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE ^ MDB2_PORTABILITY_EMPTY_TO_NULL);
        if (PEAR::isError($this->getConn())) {
            throw new OrmException('Error connecting to DB ['
                    . $this->getConn() . ']', 
                    OrmException::CODE_DB_CONNECTION_ERROR, $this);
        }
    }
    
    /**
     * Close the database connection, if it still open.
     * If there's a pending Transaction, it throws an Exception, because it 
     * shouldn't happen, but before it ensures to release all resources.
     * 
     * @throws  OrmException if transaction stills open.
     * @access  public
     * @return  void
     */
    public function disconnect() {
        if (!is_null($this->getConn())) {
            $this->getConn()->disconnect();
            $this->setConn(null);
            if (!is_null($this->getTransactionBlock())) {
                throw new OrmException('Closing connection with pending ' . 
                        'transactions', OrmException::CODE_DB_TRANSACTION_PENDING, 
                        $this);
            }
        }
    }
    
    /**
     * This method calculates a primary key before insert is intended. This 
     * method is useful when you need to get the id from another table, from 
     * a sequence, or any other source.
     * If you don't want to generate the PK here, just return null.
     * You can see @docs for a complete explanation about PK creation.
     * 
     * @access  public
     * @param   Bean          $bean   Bean that is being inserted in DB
     * @param   OrmCore       $dao    OrmCore that is performing the operation
     * @see     getCreatedPK
     * @return  mixed
     */
    function getCalculatedPK($bean, $dao) {
        return $this->getConn()->nextID($dao->getMap()->getTable());
    }
    
    /**
     * Returns first value from query.
     * 
     * This method is really useful to get just the first column's value from 
     * first row of a query, regardless of this column's name. As with 
     * <em>runQuery</em>, you can pass a query or a OrmSqlSelect as first 
     * parameter.
     * We don't use Pear::MDB2's queryOne() method to ensure going throw the 
     * same path that any other query (using runQuery()).
     * 
     * @param   mixed       $sql      OrmSql or sql's string
     * @param   array       $vars     Variables to use in query
     * @return  mixed
     */
    public function getValue($sql) {
        $rs = $this->select($sql);
        $row = $rs->fetchRow($this->mapFetchmode(self::FETCHMODE_LIST));
        return $row[0];
    }
    
    /**
     * This method maps fetch mode from standard ones defined in 
     * <em>OrmIDb</em> to the ones from the specific implementation.
     * 
     * @access  public
     * @param   int       $ormFetchmode      Orm's standard fetch mode
     * @return  mixed
     */
    public function mapFetchmode($ormFetchmode) {
        switch ($ormFetchmode) {
            case self::FETCHMODE_LIST:
                return MDB2_FETCHMODE_ORDERED;
                break;
            case self::FETCHMODE_OBJ:
                return MDB2_FETCHMODE_OBJECT;
                break;
            case self::FETCHMODE_MAP:
            default:
        }
        return MDB2_FETCHMODE_ASSOC;
    }
    
    /**
     * Rollbacks a database transaction.
     * 
     * @access  public
     * @param   mixed     $blockId     Transaction block identifier
     * @return  void
     */
    public function rollback($blockId = null) {
        parent::rollback($blockId);
        $this->getConn()->rollback();
    }
    
    /**
     * Main SQL execution method.
     * 
     * Here, all query execution work is performed. So, it's an optimal place 
     * to add caches, log querys, debug, etc.
     * 
     * @access  public
     * @param   mixed       $sql      OrmSql or sql's string
     * @param   array       $vars     Variables to use in query
     * @return  resultset
     */
    public function runQuery($sql, $vars = null) {
        $vars = $this->prepareVars($sql, $vars);
        if (ObjectInspector::isA($sql, 'OrmSql')) {
            $sql = $sql->__toString();
        }
        //debug($sql);echo "\n";
        $sth = $this->getConn()->prepare($sql);
        $rs = $sth->execute($vars);
        if (Pear::isError($rs)) {
            throw new OrmException('Error in query [sql: ' . $sql 
                    . '] called with [values: ' . var_export($vars, TRUE) 
                    . '] caused by [' . $rs->getMessage() . ']', 
                    OrmException::CODE_QUERY_ERROR, $this);
        }
        
        // store resultset, and return it.
        $this->rs = $rs;
        return $rs;
    }
    
    /**
     * Special method to traverse results.
     *
     * This method aims to avoid that your code needs to iterate over the
     * resultset. So this method does the iteration, calling the
     * <em>callback</em> method for each row, passing the row data as
     * specified by <em>mode</em> parameter, and with optional <em>params</em>.
     *
     * @access  public
     * @param   $object    Object to call
     * @param   $callback  Method to call in object
     * @param   $rs        Resultset to iterate
     * @param   $params    Optional params to pass to callback with row data
     * @param   $mode      Mode for data in row passed to callback method
     */
    function traverse($object, $callback, $rs = null, $params = null, $mode = self::FETCHMODE_MAP) {
        if (is_null($rs)) {
            $rs = $this->rs;
        }
        $arr = array();
        while ($row = $rs->fetchRow($this->mapFetchmode($mode))) {
            if (is_null($params)) {
                $callbackParams = array();
            } elseif (!is_array($params)) {
                $callbackParams = array($params);
            } else {
                $callbackParams = $params;
            }
            array_unshift($callbackParams, $row);
            $arr[] = call_user_func_array(array($object, $callback), $callbackParams);
        }
        return $arr;
    }
    
}

?>