<?php
/* SVN FILE: $Id: dbo_oracle.php 5860 2007-10-22 16:54:36Z mariano.iglesias $ */
/**
 * Oracle layer for DBO.
 *
 * Long description for file
 *
 * PHP versions 4 and 5
 *
 * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>                                             
 * Copyright 2005-2007, Cake Software Foundation, Inc.
 *                                1785 E. Sahara Avenue, Suite 490-204
 *                                Las Vegas, Nevada 89104
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright 2005-2007, Cake Software Foundation, Inc.
 * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
 * @package            cake
 * @subpackage        cake.cake.libs.model.datasources.dbo
 * @since            CakePHP v 1.2.0.4041
 * @version            $Revision: 5860 $
 * @modifiedby        $LastChangedBy: Chris Thompson - www.thompsonbd.com $
 * @lastmodified    $Date: 2007-10-22 11:54:36 -0500 (Mon, 22 Oct 2007) $
 * @license            http://www.opensource.org/licenses/mit-license.php The MIT License
 */
/**
 * Short description for class.
 *
 * Long description for class
 *
 * @package        cake
 * @subpackage    cake.cake.libs.model.datasources.dbo
 */
class DboOracle extends DboSource {
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access public
 */
    
var $config;
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access public
 */
    
var $alias '';
/**
  * The name of the model's sequence
  *
  * @var unknown_type
  */
    
var $sequence '';
/**
 * Transaction in progress flag
 *
 * @var boolean
 */
    
var $__transactionStarted false;
    
/**
 * Transaction in progress flag
 *
 * @var boolean
 */
    
var $_error false;    
    
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access public
 */
    
var $columns = array(
        
'primary_key' => array('name' => 'number NOT NULL'),
        
'string' =>      array('name' => 'varchar2''limit' => '4000'),
        
'text' =>        array('name' => 'clob','placeholder' => 'EMPTY_CLOB()'),
        
'integer' =>     array('name' => 'numeric'),
        
'float' =>       array('name' => 'float'),
        
'datetime' =>    array('name' => 'date''format' => 'Y-m-d H:i:s'),
        
'timestamp' =>   array('name' => 'date''format' => 'Y-m-d H:i:s'),
        
'time' =>        array('name' => 'date''format' => 'Y-m-d H:i:s'),
        
'date' =>        array('name' => 'date''format' => 'Y-m-d H:i:s'),
        
'binary' =>      array('name' => 'blob','placeholder' => 'EMPTY_BLOB()'),
        
'boolean' =>     array('name' => 'boolean'),
        
'number' =>      array('name' => 'numeric'),
        
'inet' =>        array('name' => 'inet'));
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access protected
 */
    
var $connection;
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access protected
 */
    
var $_limit = -1;
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access protected
 */
    
var $_offset 0;
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access protected
 */
    
var $_map;
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access protected
 */
    
var $_currentRow;
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access protected
 */
    
var $_numRows;
/**
 * Enter description here...
 *
 * @var unknown_type
 * @access protected
 */
    
var $_results;
/**
 * array of bind values to be used
 *
 * @var array
 * @access protected
 */
    
var $_binds;
/**
 * array of bind values to be used that are keys
 *
 * @var array
 * @access protected
 */
    
var $_keyBinds;
    var 
$_fkBinds;
    var 
$_bindColumnType = array();
    var 
$_modifyQuery    false;
    var 
$_returning      false;
/**
 * array of bound values
 *
 * @var array
 * @access protected
 */
    
var $_bound;
/**
 * Connects to the database using options in the given configuration array.
 *
 * @return boolean True if the database could be connected, else false
 * @access public
 */
    
function connect() {
        
$config $this->config;     
        
$connect $config['connect'];
        
$this->connected false;
        
$config['charset'] = !empty($config['charset']) ? $config['charset'] : null;
        
$this->connection $connect($config['login'], $config['password'], $config['database'], $config['charset']);

        if (
$this->connection) {
            
$this->connected true;
            
            if (!empty(
$config['nls_sort'])) {
                
$this->execute('ALTER SESSION SET NLS_SORT='.$config['nls_sort']);
            }

            if (!empty(
$config['nls_comp'])) {
                
$this->execute('ALTER SESSION SET NLS_COMP='.$config['nls_comp']);
            }
            
$this->execute("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
        } else {
            
$this->connected false;
        }
        return 
$this->connected;
    }
/**
 * Sets the encoding language of the session
 *
 * @param string $lang language constant
 * @return bool
 */
    
function setEncoding($lang) {
        if (!
$this->execute('ALTER SESSION SET NLS_LANGUAGE='.$lang)) {
            return 
false;
        }
        return 
true;
    }
/**
 * Gets the current encoding language
 *
 * @return string language constant
 */
    
function getEncoding() {
        
$sql 'SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER=\'NLS_LANGUAGE\'';
        if (!
$this->execute($sql)) {
            return 
false;
        }

        if (!
$row $this->fetchRow()) {
            return 
false;
        }
        return 
$row[0]['VALUE'];
    }
/**
 * Disconnects from database.
 *
 * @return boolean True if the database could be disconnected, else false
 * @access public
 */
    
function disconnect() {
        if (
$this->connection) {
            
$this->connected = !ocilogoff($this->connection);
            return !
$this->connected;
        }
    }
/**
 * Scrape the incoming SQL to create the association map. This is an extremely
 * experimental method that creates the association maps since Oracle will not tell us.
 *
 * @param string $sql
 * @return false if sql is nor a SELECT
 * @access protected
 */
    
function _scrapeSQL($sql) {
        
$sql str_replace("\""''$sql);
        
$preFrom preg_split('/\bFROM\b/'$sql);
        
$preFrom $preFrom[0];
        
$find = array('SELECT');
        
$replace = array('');
        
$fieldList trim(str_replace($find$replace$preFrom));
        
$fields preg_split('/,\s+/'$fieldList);
        
$lastTableName    '';

        foreach(
$fields as $key => $value) {
            if (
$value != 'COUNT(*) AS count') {
                if (
preg_match('/\s+(\w+(\.\w+)*)$/'$value$matches)) {
                    
$fields[$key]    = $matches[1];

                    if (
preg_match('/^(\w+\.)/'$value$matches)) {
                        
$fields[$key]    = $matches[1] . $fields[$key];
                        
$lastTableName    $matches[1];
                    }
                }
            }
        }
        
$this->_map = array();

        foreach(
$fields as $f) {
            
$e explode('.'$f);
            if (
count($e) > 1) {
                
$table $e[0];
                
$field strtolower($e[1]);
            } else {
                
$table 0;
                
$field $e[0];
            }
            
$this->_map[] = array($table$field);
        }
    }
/**
 * Modify a SQL query to limit (and offset) the result set
 *
 * @param integer $limit Maximum number of rows to return
 * @param integer $offset Row to begin returning
 * @return modified SQL Query
 * @access public
 */
    
function limit($limit = -1$offset 0) {        
        
$this->_limit = (int) $limit;
        
$this->_offset = (int) $offset;
    }
/**
 * Returns number of rows in previous resultset. If no previous resultset exists,
 * this returns false.
 *
 * @return integer Number of rows in resultset
 * @access public
 */
    
function lastNumRows() {
        return 
$this->_numRows;
    }
/**
 * Executes given SQL statement. This is an overloaded method.
 *
 * @param string $sql SQL statement
 * @return resource Result resource identifier or null
 * @access protected
 */
    
function _execute(&$sql,$binds=false) {
        if (!
$this->_statementId ociparse($this->connection$sql)) {
            
debug(oci_error($this->_statementId));
        }
                
        
$this->_bound = array();
        
$lobData      = array();
        
        
$queryType ocistatementtype($this->_statementId);                                            
        
        switch(
$queryType) {
            case 
'INSERT':
                
$this->_modifyQuery true;
                
$mode OCI_DEFAULT;
        
                
//  this will add the RETURNING  piece to the sql
                //  ---------------------------------------------
                
if($this->_returning) {
                    
$sql $sql ' RETURNING ';
                    
                    foreach(
$this->_returning as $bind => $field) {
                        
$sql $sql $field ' INTO ' ':bind' $bind;
                    }    
                }
            break;
            case 
'UPDATE':
                
$this->_modifyQuery true;
                
$mode OCI_DEFAULT;
        
                
//  this will add the RETURNING  piece to the sql
                //  ---------------------------------------------
                
if($this->_returning) {
                    
$sql $sql ' RETURNING ';
                    
                    foreach(
$this->_returning as $bind => $field) {
                        
$sql $sql $field ' INTO ' ':bind' $bind;
                    }   
                }
            break;
            default:
                
$mode OCI_COMMIT_ON_SUCCESS;
            break;
        }
        
        
//  look in the sql and find all binds
        //  ----------------------------------
        
if(!$binds) {
            
preg_match_all("/:bind(\d+)/",$sql,$matches);

            
//  there are binds
            //  ---------------
            
if(!empty($matches[1])) {                
                foreach(
$matches[1] as $key => $val) {
                    
//  check if this val is actually a reference to another bind,
                    //  if so, replace the val with the bind val it is looking for
                    //  ---------------------------------------------------------                    
                    
preg_match_all("/:bind(\d+)/",$this->_binds[$val],$valBind);
                    if(!empty(
$valBind[1]) && isset($valBind[1][0])) {
                        
$binds[$val] = $this->_binds[$valBind[1][0]];
                    } else {
                        
$binds[$val] = $this->_binds[$val];
                    }                         
                }     
            }
        }
        
        
//  attempting to create a LOB descriptor for each blob/clob
        //  --------------------------------------------------------
        
if(is_array($binds)) {
            
//  locate all the clobs that are binds for this query
            
$bindClobs = @array_intersect_key($binds,$this->_bindColumnType);
            
            foreach(
$this->_bindColumnType as $key => $val) {               
                
//  determine if this is an update, if so, then we need to bind clob fields differently
                //  -----------------------------------------------------------------------------------
                
if($this->_bindColumnType[$key] == 'text' && array_key_exists($key,$bindClobs)) {
                    
$lobData[$key] = $binds[$key];
                    
                    if(!
$binds[$key] = oci_new_descriptor($this->connection,OCI_D_LOB)) {
                        
$this->_error true;
                    }
                }
            }
        }
        
        
//  parse the connection, it could have changed
        //  -------------------------------------------
        
if (!$this->_statementId oci_parse($this->connection$sql)) {
            
$this->_error true;
        }        
        
        if(
is_array($binds)) {            
            foreach(
$binds as $key => $val) {
                
$this->_bound[] = $binds[$key];  //  passed along for the debug statement 
                
                //  determine if this is an update, if so, then we need to bind clob fields differently
                //  -----------------------------------------------------------------------------------
                
if(isset($this->_bindColumnType[$key]) && $this->_bindColumnType[$key] == 'text') {
                    if(!
oci_bind_by_name($this->_statementId,':bind' $key,$binds[$key], -1OCI_B_CLOB)) { 
                        
$this->_error true;
                    }
                    
                    
//  exchange the real data for what is in the array
                    //  -----------------------------------------------
                    
$this->_bound[(count($this->_bound) - 1)] = $lobData[$key];
                } 
                elseif(isset(
$this->_bindColumnType[$key]) && $this->_bindColumnType[$key] == 'binary') {
                    
$binds[$key] = oci_new_descriptor($this->connection,OCI_D_LOB);
                    
                    if(!
oci_bind_by_name($this->_statementId,':bind' $key,$binds[$key], -1OCI_B_BLOB)) { 
                        
$this->_error true;
                    }
                } 
                else {                
                    if(!
oci_bind_by_name($this->_statementId,':bind' $key,$binds[$key], -1)) { 
                        
$this->_error true;
                    }
                }              
            }

            if(!
oci_execute($this->_statementId$mode)) {            
                
$this->_error true;
            }
        }
        else {
            if(!
$this->_statementId oci_parse($this->connection$sql)) {
                
$this->_error true;   
            }
            
            if (!
ociexecute($this->_statementId$mode)) {
                
//  taken from https://trac.cakephp.org/attachment/ticket/2294/dbo_oracle.php
                //  -------------------------------------------------------------------------
                
$this->_error true;
                
$this->log("OCIEXECUTE error: ".$this->_error"\nSQL was: ".$sql);
                
                return 
false;
            }
            
            switch(
$queryType) {
                case 
'DESCRIBE':
                case 
'SELECT':
                    
$this->_scrapeSQL($sql);
                break;
                default:
                    return 
$this->_statementId;
                break;
            }
                 
            if (
$this->_limit >= 1) {
                
ocisetprefetch($this->_statementId$this->_limit);
            } else {
                
ocisetprefetch($this->_statementId3000);
            }
        }
        
        
$this->_returning false;
            
        switch(
ocistatementtype($this->_statementId)) {
            case 
'DESCRIBE':
            case 
'SELECT':
                
$this->_scrapeSQL($sql);       
        
                
$this->_numRows ocifetchstatement($this->_statementId$this->_results$this->_offset$this->_limitOCI_NUM OCI_FETCHSTATEMENT_BY_ROW);
                                 
                
$this->_currentRow 0;
                
$this->limit();
            break; 
            case 
'INSERT':            
                foreach(
$lobData as $key => $val) {
                    if(
$binds[$key]->save($lobData[$key]) === false) {
                        
$this->_error '<pre>' print_r(oci_error($this->_statementId),true) . '</pre><pre>' print_r($binds,true) . '</pre>';
                    }
                }
                
                
//  attempt to commit
                
if(!oci_commit($this->connection)) {            
                    
$this->_error '<pre>' print_r(oci_error($this->_statementId),true) . '</pre><pre>' print_r($binds,true) . '</pre>';          
                } else {            
                    foreach(
$lobData as $key => $val) {
                        
OCIFreeDesc($binds[$key]);
                    }     
                }
                
                return 
$this->_statementId;
            break;
            case 
'UPDATE':            
                foreach(
$lobData as $key => $val) {
                    if(
$binds[$key]->save($lobData[$key]) === false) {
                        
$this->_error '<pre>' print_r(oci_error($this->_statementId),true) . '</pre><pre>' print_r($binds,true) . '</pre>';
                    }
                }
                
                
//  attempt to commit
                
if(!oci_commit($this->connection)) {            
                    
$this->_error '<pre>' print_r(oci_error($this->_statementId),true) . '</pre><pre>' print_r($binds,true) . '</pre>';          
                } else {            
                    foreach(
$lobData as $key => $val) {
                        
OCIFreeDesc($binds[$key]);
                    }     
                }
                
                return 
$this->_statementId;
            break;
            default:
                return 
$this->_statementId;
            break;
        }
        
        return 
$this->_statementId;
    }
/**
 * Enter description here...
 *
 * @return unknown
 * @access public
 */
    
function fetchRow() {
        if (
$this->_currentRow >= $this->_numRows) {
            
ocifreestatement($this->_statementId);
            
$this->_map null;
            
$this->_results null;
            
$this->_currentRow null;
            
$this->_numRows null;
            return 
false;
        }
        
$resultRow = array();

        foreach(
$this->_results[$this->_currentRow] as $index => $field) {
            list(
$table$column) = $this->_map[$index];

            if (
strpos($column' count')) {
                
$resultRow[0]['count'] = $field;
            } else {
                
$resultRow[$table][$column] = $this->_results[$this->_currentRow][$index];
            }
        }
        
$this->_currentRow++;
        return 
$resultRow;
    }
/**
 * Checks to see if a named sequence exists
 *
 * @param string $sequence
 * @return bool
 * @access public
 */
    
function sequenceExists($sequence) {
        
$sql "SELECT SEQUENCE_NAME FROM USER_SEQUENCES WHERE SEQUENCE_NAME = '$sequence'";
        if (!
$this->execute($sql)) {
            return 
false;
        }
        return 
$this->fetchRow();
    }
/**
 * Creates a database sequence
 *
 * @param string $sequence
 * @return bool
 * @access public
 */
    
function createSequence($sequence) {
        
$sql "CREATE SEQUENCE $sequence";
        return 
$this->execute($sql);
    }
/**
 * Enter description here...
 *
 * @param unknown_type $table
 * @return unknown
 * @access public
 */
    
function createTrigger($table) {
        
$sql "CREATE OR REPLACE TRIGGER pk_$table" "_trigger BEFORE INSERT ON $table FOR EACH ROW BEGIN SELECT pk_$table.NEXTVAL INTO :NEW.ID FROM DUAL; END;";
        return 
$this->execute($sql);
    }
/**
 * Returns an array of tables in the database. If there are no tables, an error is
 * raised and the application exits.
 *
 * @return array tablenames in the database
 * @access public
 */
    
function listSources() {
        
$cache parent::listSources();
        if (
$cache != null) {
            return 
$cache;
        }
        
$sql 'SELECT view_name AS name FROM user_views UNION SELECT table_name AS name FROM user_tables';

        if (!
$this->execute($sql)) {
            return 
false;
        }
        
$sources = array();

        while(
$r $this->fetchRow()) {
            
$sources[] = $r[0]['name'];
        }
        
parent::listSources($sources);
        return 
$sources;
    }
/**
 * Returns an array of the fields in given table name.
 *
 * @param object instance of a model to inspect
 * @return array Fields in table. Keys are name and type
 * @access public
 */
    
function describe(&$model) {
        
$cache parent::describe($model);

        if (
$cache != null) {
            return 
$cache;
        }
        
$sql 'SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH FROM user_tab_columns WHERE table_name = \'';
        
$sql .= strtoupper($this->fullTableName($model)) . '\'';

        if (!
$this->execute($sql)) {
            return 
false;
        }
        
$fields = array();

        for(
$i=0$row $this->fetchRow(); $i++) {
            
$fields[strtolower($row[0]['COLUMN_NAME'])] = array('type'=> $this->column($row[0]['DATA_TYPE']),
                                                                 
'length'=> $row[0]['DATA_LENGTH']);
        }
        
$this->__cacheDescription($this->fullTableName($modelfalse), $fields);
        return 
$fields;
    }
/**
 * This method should quote Oracle identifiers. Well it doesn't.
 * It would break all scaffolding and all of Cake's default assumptions.
 *
 * @param unknown_type $var
 * @return unknown
 * @access public
 */
    
function name($var) {
        switch(
$var) {
            case 
'_create':
            case 
'_read':
            case 
'_update':
            case 
'_delete':
                return 
"\"$var\"";
            break;
            default:
                return 
$var;
            break;
        }
    }
/**
 * Begin a transaction
 *
 * @param unknown_type $model
 * @return boolean True on success, false on fail
 * (i.e. if the database/model does not support transactions).
 */
    
function begin() {
        
$this->__transactionStarted true;
        return 
true;
    }
/**
 * Rollback a transaction
 *
 * @param unknown_type $model
 * @return boolean True on success, false on fail
 * (i.e. if the database/model does not support transactions,
 * or a transaction has not started).
 */
    
function rollback() {
        return 
ocirollback($this->connection);
    }
/**
 * Commit a transaction
 *
 * @param unknown_type $model
 * @return boolean True on success, false on fail
 * (i.e. if the database/model does not support transactions,
 * or a transaction has not started).
 */
    
function commit() {
        
$this->__transactionStarted false;
        return 
ocicommit($this->connection);
    }
/**
 * Converts database-layer column types to basic types
 *
 * @param string $real Real database-layer column type (i.e. "varchar(255)")
 * @return string Abstract column type (i.e. "string")
 * @access public
 */
    
function column($real) {
        if (
is_array($real)) {
            
$col $real['name'];

            if (isset(
$real['limit'])) {
                
$col .= '('.$real['limit'].')';
            }
            return 
$col;
        } else {
            
$real strtolower($real);
        }
        
$col r(')'''$real);
        
$limit null;

        @list(
$col$limit) = explode('('$col);
        if (
in_array($col, array('date''timestamp'))) {
            return 
$col;
        }
        if (
strpos($col'number') !== false) {
            return 
'integer';
        }
        if (
strpos($col'integer') !== false) {
            return 
'integer';
        }
        if (
strpos($col'char') !== false) {
            return 
'string';
        }
        if (
strpos($col'text') !== false) {
            return 
'text';
        }
        if (
strpos($col'blob') !== false) {
            return 
'binary';
        }
        if (
in_array($col, array('float''double''decimal'))) {
            return 
'float';
        }
        if (
$col == 'boolean') {
            return 
$col;
        }
        return 
'text';
    }
/**
 * Returns a quoted and escaped string of $data for use in an SQL statement.
 *
 * @param string $data String to be prepared for use in an SQL statement
 * @return string Quoted and escaped
 * @access public
 */
    
function value($data$column null$safe false$field false) {
        
$parent parent::value($data$column$safe);

        if (
$parent != null) {
            return 
$parent;
        }

        if (
$data === null) {
            return 
'NULL';
        }

        if (
$data === '') {
            return  
"''";
        }
        
        if(!empty(
$column)) {
            switch(
$column) {
                case 
'date':
                    
$data date('Y-m-d H:i:s'strtotime($data));
                                    
                    
$this->_binds[] = $data;
                                                   
                    
$data "TO_DATE(:bind" . (count($this->_binds) - 1) . ", 'YYYY-MM-DD HH24:MI:SS')";
                    break;
                case 
null :
                    if (
is_numeric($data)) {
                        break;
                    }
                case 
'text':                                                                 
                    
$this->_binds[] = $data;
                    
                    if(
$this->_modifyQuery) {
                        
$this->_returning = array(
                            (
count($this->_binds) - 1) => $field 
                        
);
                        
$data $this->columns['text']['placeholder']; 
                    } else {                
                        
$data ':bind' . (count($this->_binds) - 1);                
                    }
                                        
                    
$this->_bindColumnType[count($this->_binds) - 1] = $column;
                                     
                    break;
                case 
'binary':
                    if(
$this->_modifyQuery) {
                        
                    }                               
                    
$this->_binds[] = $data;                
                    
$data ':bind' . (count($this->_binds) - 1);
                    
                    
$this->_bindColumnType[count($this->_binds) - 1] = $column;
                    
                    break;        
                default:                               
                    
$this->_binds[] = $data;                
                    
$data ':bind' . (count($this->_binds) - 1);
                    break;
            }
        } else {                               
            
$this->_binds[] = $data;                
            
$data ':bind' . (count($this->_binds) - 1);
        }
        
        return 
$data;
    }
/**
 * Returns the ID generated from the previous INSERT operation.
 *
 * @param string
 * @return integer
 * @access public
 */
    
function lastInsertId($source,$id=false) {
        
$sequence $source . (($id) ? '_seq' '');
        
$sql "SELECT $sequence.currval FROM dual";

        if (!
$this->execute($sql)) {
            return 
false;
        }

        while(
$row $this->fetchRow()) {
            return 
$row[$sequence]['currval'];
        }
        return 
false;
    }
/**
 * Returns a formatted error message from previous database operation.
 *
 * @return string Error message with error number
 * @access public
 */
    
function lastError() {
        
$error $this->_error;
        
$this->_error false;
        return 
$error;
    }
/**
 * Returns number of affected rows in previous database operation. If no previous operation exists, this returns false.
 *
 * @return int Number of affected rows
 * @access public
 */
    
function lastAffected() {
        return 
$this->_statementId ocirowcount($this->_statementId): false;
    }
/**
 * Inserts multiple values into a join table
 *
 * @param string $table
 * @param string $fields
 * @param array $values
 */
    
function insertMulti($table$fields$values) {
        
$count count($values);
        for (
$x 0$x $count$x++) {
            
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
        }
    }
/**
 * DataSource Query abstraction
 *
 * @return resource Result resource identifier
 */
    
function query() {
        
$args     func_get_args();
        
$fields   null;
        
$order    null;
        
$limit    null;
        
$page     null;
        
$recursive null;

        if (
count($args) == 1) {
            if(
is_array($args[0])) {
                
$this->_limit $args[0][1];
                
                
$args[0] = $args[0][0];
            }
            
            return 
$this->fetchAll($args[0]);

        } elseif (
count($args) > && (strpos(strtolower($args[0]), 'findby') === || strpos(strtolower($args[0]), 'findallby') === 0)) {
            
$params $args[1];

            if (
strpos(strtolower($args[0]), 'findby') === 0) {
                
$all  false;
                
$field Inflector::underscore(preg_replace('/findBy/i'''$args[0]));
            } else {
                
$all  true;
                
$field Inflector::underscore(preg_replace('/findAllBy/i'''$args[0]));
            }

            
$or = (strpos($field'_or_') !== false);
            if (
$or) {
                
$field explode('_or_'$field);
            } else {
                
$field explode('_and_'$field);
            }
            
$off count($field) - 1;

            if (isset(
$params[$off])) {
                
$fields $params[$off];
            }

            if (isset(
$params[$off])) {
                
$order $params[$off];
            }

            if (!
array_key_exists(0$params)) {
                return 
false;
            }

            
$c 0;
            
$query = array();
            foreach (
$field as $f) {
                if (!
is_array($params[$c]) && !empty($params[$c]) && $params[$c] !== true && $params[$c] !== false) {
                    
$query[$args[2]->alias '.' $f] = '= ' $params[$c];
                } else {
                    
$query[$args[2]->alias '.' $f] = $params[$c];
                }
                
$c++;
            }

            if (
$or) {
                
$query = array('OR' => $query);
            }

            if (
$all) {

                if (isset(
$params[$off])) {
                    
$limit $params[$off];
                }

                if (isset(
$params[$off])) {
                    
$page $params[$off];
                }

                if (isset(
$params[$off])) {
                    
$recursive $params[$off];
                }
                return 
$args[2]->findAll($query$fields$order$limit$page$recursive);
            } else {
                if (isset(
$params[$off])) {
                    
$recursive $params[$off];
                }
                return 
$args[2]->find($query$fields$order$recursive);
            }
        } else {
            if (isset(
$args[1]) && $args[1] === true) {
                return 
$this->fetchAll($args[0], true);
            }
            return 
$this->fetchAll($args[0], false);
        }
    }
/**
 * Queries the database with given SQL statement, and obtains some metadata about the result
 * (rows affected, timing, any errors, number of rows in resultset). The query is also logged.
 * If DEBUG is set, the log is shown all the time, else it is only shown on errors.
 *
 * @param string $sql
 * @param array $binds An array of binds, key being the same name as the :bindName
 * @return unknown
 */
    
function execute($sql,$binds=false) {
        
//  determine what type of query this is        
        
$t getMicrotime();
        
$this->_result $this->_execute($sql,$binds);
        
$this->affected $this->lastAffected();
        
$this->took round((getMicrotime() - $t) * 10000);
        
$this->error $this->lastError();
        
$this->numRows $this->lastNumRows($this->_result);
        
        if (
Configure::read() > 1) {
            
$this->logQuery($sql,$binds);
        }

        if (
$this->error) {
            return 
false;
        } else {
            return 
$this->_result;
        }
    }
/**
 * Log given SQL query.
 *
 * @param string $sql SQL statement
 * @param string $binds This could either be false, which means there are no binds passed by the query() function or an array of bind values
 * @todo: Add hook to log errors instead of returning false
 */
    
function logQuery($sql,$binds) {
        if(
count($this->_bound) > 0) {
            
$binds $this->_bound;        
        }
        
        
$this->_queriesCnt++;
        
$this->_queriesTime += $this->took;
        
$this->_queriesLog[] = array('query' => $sql,
                    
'error'    => $this->error,
                    
'affected' => $this->affected,
                    
'numRows'  => $this->numRows,
                    
'took'     => $this->took,
                    
'binds'    => $binds
        
);
        if (
count($this->_queriesLog) > $this->_queriesLogMax) {
            
array_pop($this->_queriesLog);
        }
        if (
$this->error) {
            return 
false;
        }
    }
/**
 * Outputs the contents of the queries log.
 *
 * @param boolean $sorted
 */
    
function showLog($sorted false) {
        if (
$sorted) {
            
$log sortByKey($this->_queriesLog'took''desc'SORT_NUMERIC);
        } else {
            
$log $this->_queriesLog;
        }

        if (
$this->_queriesCnt 1) {
            
$text 'queries';
        } else {
            
$text 'query';
        }
        
        if (
php_sapi_name() != 'cli') {
            print (
"<table class=\"cake-sql-log\" id=\"cakeSqlLog_" preg_replace('/[^A-Za-z0-9_]/''_'uniqid(time(), true)) . "\" summary=\"Cake SQL Log\" cellspacing=\"0\" border = \"0\">\n<caption>{$this->_queriesCnt} {$text} took {$this->_queriesTime} ms</caption>\n");
            print (
"<thead>\n<tr><th>Nr</th><th>Query</th><th>Error</th><th>Affected</th><th>Num. rows</th><th>Took (ms)</th></tr>\n</thead>\n<tbody>\n");
            
            foreach (
$log as $k => $i) {
                print (
"<tr><td>" . ($k 1) . "</td><td>" h($i['query']) . "</td><td>{$i['error']}</td><td style = \"text-align: right\">{$i['affected']}</td><td style = \"text-align: right\">{$i['numRows']}</td><td style = \"text-align: right\">{$i['took']}</td></tr>\n");
                
                if(
$i['binds'] && Configure::read('show_binds')) {
                    print (
"<tr><td></td><td>Binds: " implode(' || ',$i['binds']) . "</td></tr>");    
                }
            }
            print (
"</tbody></table>\n");
        } else {
            foreach (
$log as $k => $i) {
                print ((
$k 1) . ". {$i['query']} {$i['error']}\n");
            }
        }
    }
/**
 * Creates a WHERE clause by parsing given conditions array.  Used by DboSource::conditions().
 * Code should be identical, but with binds added
 *
 * @param array $conditions Array or string of conditions
 * @return string SQL fragment
 */
    
function conditionKeysToString($conditions$quoteValues true) {
        
$c 0;
        
$data $not null;
        
$out = array();
        
$bool = array('and''or''not''and not''or not''xor''||''&&');
        
$join ' AND ';

        foreach (
$conditions as $key => $value) {
            if (
is_numeric($key) && empty($value)) {
                continue;
            }
            
//  in testing, this never occurred, possibly need to come back to this
            //  ------------------------------------------------------------------- 
            
elseif (is_numeric($key) && is_string($value)) {
                
$out[] = $not $this->__quoteFields($value);
            }
            
//  in testing, this never occurred, possibly need to come back to this
            //  -------------------------------------------------------------------
            
elseif (in_array(strtolower(trim($key)), $bool)) {
                
$join ' ' strtoupper($key) . ' ';
                
$value $this->conditionKeysToString($value$quoteValues);
                if (
strpos($join'NOT') !== false) {
                    if (
strtoupper(trim($key)) == 'NOT') {
                        
$key 'AND ' $key;
                    }
                    
$not 'NOT ';
                } else {
                    
$not null;
                }
                
$out[] = $not '((' join(') ' strtoupper($key) . ' ('$value) . '))';
            } else {
                
//  this is for table joins that can't have binds
                //  ---------------------------------------------
                
if (is_string($value) && preg_match('/^\{\$__cakeIdentifier\[(.*)\]__\$}$/'$value$identifier) && isset($identifier[1])) {
                    
$data .= $this->name($key) . ' = ' $this->name($identifier[1]);
                }
                
//  more non bind joining stuff
                //  ---------------------------
                
elseif (is_array($value) && !empty($value)) {
                    
$keys array_keys($value);
                    if (
$keys[0] === 0) {
                        
$data $this->name($key) . ' IN (';
                        if    (
strpos($value[0], '-!') === 0) {
                            
$value[0] = str_replace('-!'''$value[0]);
                            
$data .= $value[0];
                            
$data .= ')';
                        } else {
                            if (
$quoteValues) {
                                foreach (
$value as $valElement) {
                                    
$data .= $this->value($valElement) . ', ';
                                }
                            }
                            
$data[strlen($data) - 2] = ')';
                        }
                    } else {
                        
$ret $this->conditionKeysToString($value$quoteValues);
                        if (
count($ret) > 1) {
                            
$out[] = '(' join(') AND ('$ret) . ')';
                        } elseif (isset(
$ret[0])) {
                            
$out[] = $ret[0];
                        }
                    }
                }
                
//  more non bind joining stuff
                //  ---------------------------
                
elseif (is_numeric($key) && !empty($value)) {
                    
$data $this->__quoteFields($value);
                } elseif (
$value === null || (is_array($value) && empty($value))) {
                    
$data $this->name($key) . ' IS NULL';
                } elseif (
$value === false || $value === true) {
                    
$data $this->name($key) . " = " $this->value($value'boolean');
                } elseif (
$value === '') {
                    
$data $this->name($key) . " = ''";
                } elseif (
preg_match('/^([a-z]+\\([a-z0-9]*\\)\\x20+|(?:' join('\\x20)|(?:'$this->__sqlOps) . '\\x20)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)?(.*)/is'$value$match)) {
                    if (
preg_match('/(\\x20[\\w]*\\x20)/'$key$regs)) {
                        
$clause $regs['1'];
                        
$key preg_replace('/' $regs['1'] . '/'''$key);
                    }

                    
$not false;
                    
$mValue trim($match['1']);
                    if (empty(
$match['1'])) {
                        
$match['1'] = ' = ';
                    } elseif (empty(
$mValue)) {
                        
$match['1'] = ' = ';
                        
$match['2'] = $match['0'];
                    } elseif (!isset(
$match['2'])) {
                        
$match['1'] = ' = ';
                        
$match['2'] = $match['0'];
                    } elseif (
strtolower($mValue) == 'not') {
                        
$not $this->conditionKeysToString(array($mValue => array($key => $match[2])), $quoteValues);
                    }

                    if (
$not) {
                        
$data $not[0];
                    } elseif (
strpos($match['2'], '-!') === 0) {
                        
$match['2'] = str_replace('-!'''$match['2']);
                        
$data $this->name($key) . ' ' $match['1'] . ' ' $match['2'];
                    } else {
                        if (!empty(
$match['2']) && $quoteValues) {
                            if (!
preg_match('/[A-Za-z]+\\([a-z0-9]*\\),?\\x20+/'$match['2'])) {
                                
$match['2'] = $this->value($match['2']);
                            }
                            
$match['2'] = str_replace(' AND '"' AND '"$match['2']);
                        }
                        
$data $this->__quoteFields($key);
                        if (
$data === $key) {
                            
$data $this->name($key) . ' ' $match['1'] . ' :bind' . (count($this->_binds));
                            
$this->_binds[] = $match['2'];
                        } else {
                            
$data $data ' ' $match['1'] . ' ' $match['2'];
                        }
                    }
                }

                if (
$data != null) {
                    
$out[] = $data;
                    
$data null;
                }
            }
            
$c++;
        }

        return 
$out;
    }
    
/**
 * This is an exact copy of the function found in datasource.php except with stuff for binds built in
 * Instead, of looking for the key in $query
 *
 * @param unknown_type $query
 * @param unknown_type $data
 * @param unknown_type $association
 * @param unknown_type $assocData
 * @param Model $model
 * @param Model $linkModel
 * @param array $stack
 * @return unknown
 */
    
function insertQueryData($query$data$association$assocData, &$model, &$linkModel$stack) {
        
$keys = array('{$__cakeID__$}''{$__cakeForeignKey__$}');

        foreach (
$keys as $key) {
            
$val null;

            
//  this is used by assoicated joins
            //  --------------------------------
            
if (strpos($query$key) !== false) {
                switch(
$key) {
                    case 
'{$__cakeID__$}':
                        if (isset(
$data[$model->alias]) || isset($data[$association])) {
                            if (isset(
$data[$model->alias][$model->primaryKey])) {
                                
$val $data[$model->alias][$model->primaryKey];
                            } elseif (isset(
$data[$association][$model->primaryKey])) {
                                
$val $data[$association][$model->primaryKey];
                            }
                        } else {
                            
$found false;
                            foreach (
array_reverse($stack) as $assoc) {
                                if (isset(
$data[$assoc]) && isset($data[$assoc][$model->primaryKey])) {
                                    
$val $data[$assoc][$model->primaryKey];
                                    
$found true;
                                    break;
                                }
                            }
                            if (!
$found) {
                                
$val '';
                            }
                        }                    
                    break;
                    case 
'{$__cakeForeignKey__$}':                    
                        foreach (
$model->__associations as $id => $name) {
                            foreach (
$model->$name as $assocName => $assoc) {
                                if (
$assocName === $association) {
                                    if (isset(
$assoc['foreignKey'])) {
                                        
$foreignKey $assoc['foreignKey'];

                                        if (isset(
$data[$model->alias][$foreignKey])) {
                                            
$val $data[$model->alias][$foreignKey];
                                        } elseif (isset(
$data[$association][$foreignKey])) {
                                            
$val $data[$association][$foreignKey];
                                        } else {
                                            
$found false;
                                            foreach (
array_reverse($stack) as $assoc) {
                                                if (isset(
$data[$assoc]) && isset($data[$assoc][$foreignKey])) {
                                                    
$val $data[$assoc][$foreignKey];
                                                    
$found true;
                                                    break;
                                                }
                                            }
                                            if (!
$found) {
                                                
$val '';
                                            }
                                        }
                                    }
                                    break 
3;
                                }
                            }
                        }
                    break;
                }
                if (empty(
$val) && $val !== '0') {
                    return 
false;
                }
                
                if(
$key == '{$__cakeID__$}') {
                    
$query str_replace($key$this->value($val$model->getColumnType($model->primaryKey)), $query);
                    
                    
preg_match_all("/:bind(\d+)/",$query,$matches);
                    
                    if(!empty(
$matches[1])) {
                        return 
$this->_binds[$matches[1][0]];
                    }
                }
            }
            
            if (empty(
$val) && $val !== '0') {
                
//  for binds
                //  check how many binds are in the query
                //  -------------------------------------
                
preg_match_all("/:bind(\d+)/",$query,$matches);

                
//  there are binds
                //  ---------------
                
if(!empty($matches[1])) {
                    
//  get the key we want to find
                    //  ---------------------------
                    
$bindKey $this->_binds[$matches[1][0]];
                    
                    
//  if this is not a case, then something is wrong
                    //  check that the key is held is the keyBinds array
                    //  ------------------------------------------------
                    
if(is_numeric($bindKey)) {                        
                        
$bindKey $this->_keyBinds[$matches[1][0]];    
                    }
                    
                    switch(
$bindKey) {
                        case 
'{$__cakeID__$}':
                            if (isset(
$data[$model->alias]) || isset($data[$association])) {
                                if (isset(
$data[$model->alias][$model->primaryKey])) {
                                    
$val $data[$model->alias][$model->primaryKey];
                                } elseif (isset(
$data[$association][$model->primaryKey])) {
                                    
$val $data[$association][$model->primaryKey];
                                }
                            } else {
                                
$found false;
                                foreach (
array_reverse($stack) as $assoc) {
                                    if (isset(
$data[$assoc]) && isset($data[$assoc][$model->primaryKey])) {
                                        
$val $data[$assoc][$model->primaryKey];
                                        
$found true;
                                        break;
                                    }
                                }
                                if (!
$found) {
                                    
$val '';
                                }
                            }                    
                        break;
                        case 
'{$__cakeForeignKey__$}':
                            foreach (
$model->__associations as $id => $name) {
                                foreach (
$model->$name as $assocName => $assoc) {
                                    if (
$assocName === $association) {
                                        if (isset(
$assoc['foreignKey'])) {
                                            
$foreignKey $assoc['foreignKey'];

                                            if (isset(
$data[$model->alias][$foreignKey])) {
                                                
$val $data[$model->alias][$foreignKey];
                                            } elseif (isset(
$data[$association][$foreignKey])) {
                                                
$val $data[$association][$foreignKey];
                                            } else {
                                                
$found false;
                                                foreach (
array_reverse($stack) as $assoc) {
                                                    if (isset(
$data[$assoc]) && isset($data[$assoc][$foreignKey])) {
                                                        
$val $data[$assoc][$foreignKey];
                                                        
$found true;
                                                        break;
                                                    }
                                                }
                                                if (!
$found) {
                                                    
$val '';
                                                }
                                            }
                                        }
                                        break 
3;
                                    }
                                }
                            }
                        break;    
                    }
                }
                                
                
//  we want to leave the query alone, we want to look in the _binds array and replace the placeholder ($key) with the value ($val)
                //  ------------------------------------------------------------------------------------------------------------------------------
                
if(!empty($val) && !empty($bindKey)) {
                    
$this->swapKeyForValue($bindKey,$val);    
                } else {
                    return 
false;   
                }
            }
        }
        
        return 
$query;
    }
 
/**
 * Returns an array of all result rows for a given SQL query.
 * Returns false if no rows matched.
 *
 * @param string $sql SQL statement
 * @param boolean $cache Enables returning/storing cached query results
 * @return array Array of resultset rows, or false if no rows matched
 */
    
function fetchAll($sql$cache true$modelName null) {
        if (
$this->execute($sql)) {
            
$out = array();

            while (
$item $this->fetchRow()) {
                
$out[] = $item;
            }
            
            return 
$out;

        } else {
            return 
false;
        }
    }
    
    function 
swapKeyForValue($key,$val) {
        if(
$bindKey array_search($key,$this->_binds)) {
            
$this->_keyBinds[$bindKey] = $this->_binds[$bindKey]; 
            
            
$this->_binds[$bindKey] = $val;
        }
    }
/**
 * Enter description here...
 *
 * @param Model $model
 * @param unknown_type $linkModel
 * @param string $type Association type
 * @param unknown_type $association
 * @param unknown_type $assocData
 * @param unknown_type $queryData
 * @param unknown_type $external
 * @param unknown_type $resultSet
 * @param integer $recursive Number of levels of association
 * @param array $stack
 */
    
function queryAssociation(&$model, &$linkModel$type$association$assocData, &$queryData$external false, &$resultSet$recursive$stack) {
        if (
$query $this->generateAssociationQuery($model$linkModel$type$association$assocData$queryData$external$resultSet)) {
            if (!isset(
$resultSet) || !is_array($resultSet)) {
                if (
Configure::read() > 0) {
                    
e('<div style = "font: Verdana bold 12px; color: #FF0000">' sprintf(__('SQL Error in model %s:'true), $model->alias) . ' ');
                    if (isset(
$this->error) && $this->error != null) {
                        
e($this->error);
                    }
                    
e('</div>');
                }
                return 
null;
            }
            
$count count($resultSet);

            if (
$type === 'hasMany' && (!isset($assocData['limit']) || empty($assocData['limit']))) {
                
$ins $fetch = array();
                for (
$i 0$i $count$i++) {
                    if (
$in $this->insertQueryData('{$__cakeID__$}'$resultSet[$i], $association$assocData$model$linkModel$stack)) {
                        
$ins[] = $in;
                    }
                }
                
                if (!empty(
$ins)) {
                    
$fetch $this->fetchAssociated($model$query$ins);
                }

                if (!empty(
$fetch) && is_array($fetch)) {
                    if (
$recursive 0) {
                        foreach (
$linkModel->__associations as $type1) {
                            foreach (
$linkModel->{$type1} as $assoc1 => $assocData1) {
                                
$deepModel =& $linkModel->{$assoc1};
                                
$tmpStack $stack;
                                
$tmpStack[] = $assoc1;

                                if (
$linkModel->useDbConfig === $deepModel->useDbConfig) {
                                    
$db =& $this;
                                } else {
                                    
$db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
                                }
                                
$db->queryAssociation($linkModel$deepModel$type1$assoc1$assocData1$queryDatatrue$fetch$recursive 1$tmpStack);
                            }
                        }
                    }
                }
                return 
$this->__mergeHasMany($resultSet$fetch$association$model$linkModel$recursive);
            } elseif (
$type === 'hasAndBelongsToMany') {
                
$ins $fetch = array();
                for (
$i 0$i $count$i++) {
                    if (
$in $this->insertQueryData('{$__cakeID__$}'$resultSet[$i], $association$assocData$model$linkModel$stack)) {
                        
$ins[] = $in;
                    }
                }
                if (!empty(
$ins)) {
                    
$query str_replace('{$__cakeID__$}''(' .join(', '$ins) .')'$query);
                    
$query str_replace('=  (''IN ('$query);
                    
$query str_replace('  WHERE 1 = 1'''$query);
                }

                
$foreignKey $model->hasAndBelongsToMany[$association]['foreignKey'];
                
$joinKeys = array($foreignKey$model->hasAndBelongsToMany[$association]['associationForeignKey']);
                list(
$with$habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
                
$habtmFieldsCount count($habtmFields);

                
$q $this->insertQueryData($querynull$association$assocData$model$linkModel$stack);
                if (
$q != false) {
                    
$fetch $this->fetchAll($q$model->cacheQueries$model->alias);
                } else {
                    
$fetch null;
                }
            }               

            for (
$i 0$i $count$i++) {
                
$row =& $resultSet[$i];

                if (
$type !== 'hasAndBelongsToMany') {
                    
//  look at the query, find the bind var, if it's value is {$__cakeForeignKey__$} then we need to start a new _fkBinds sub array
                    //  ----------------------------------------------------------------------------------------------------------------------------
                    
preg_match_all("/:bind(\d+)/",$query,$matches);
                    
                    if(isset(
$matches[1][0]) && $this->_binds[$matches[1][0]] == '{$__cakeForeignKey__$}') {                        
                        
$q preg_replace("/:bind(\d+)/",':bind' count($this->_binds),$query);
                        if(isset(
$resultSet[$i][get_class($model)][$assocData['foreignKey']])) {
                            
$this->_binds[] = $resultSet[$i][get_class($model)][$assocData['foreignKey']];
                            
$fetch $this->fetchAll($q$model->cacheQueries$model->alias);
                        } else {
                            
$fetch null;        
                        }
                    } else {                      
                        
$q $this->insertQueryData($query$resultSet[$i], $association$assocData$model$linkModel$stack);
                    
                        if (
$q != false) {
                            
$fetch $this->fetchAll($q$model->cacheQueries$model->alias);
                        } else {
                            
$fetch null;
                        }
                    }
                }

                if (!empty(
$fetch) && is_array($fetch)) {
                    if (
$recursive 0) {
                        foreach (
$linkModel->__associations as $type1) {
                            foreach (
$linkModel->{$type1} as $assoc1 => $assocData1) {

                                
$deepModel =& $linkModel->{$assoc1};
                                if ((
$type1 === 'belongsTo') || ($deepModel->alias === $model->alias && $type === 'belongsTo') || ($deepModel->alias != $model->alias)) {
                                    
$tmpStack $stack;
                                    
$tmpStack[] = $assoc1;
                                    if (
$linkModel->useDbConfig == $deepModel->useDbConfig) {
                                        
$db =& $this;
                                    } else {
                                        
$db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
                                    }
                                    
$db->queryAssociation($linkModel$deepModel$type1$assoc1$assocData1$queryDatatrue$fetch$recursive 1$tmpStack);
                                }
                            }
                        }
                    }
                    if (
$type == 'hasAndBelongsToMany') {
                        
$merge = array();
                        foreach(
$fetch as $j => $data) {
                            if (isset(
$data[$with]) && $data[$with][$foreignKey] === $row[$model->alias][$model->primaryKey]) {
                                if (
$habtmFieldsCount 2) {
                                    
$merge[] = $data;
                                } else {
                                    
$merge[] = Set::diff($data, array($with => $data[$with]));
                                }
                            }
                        }
                        if (empty(
$merge) && !isset($row[$association])) {
                            
$row[$association] = $merge;
                        } else {
                            
$this->__mergeAssociation($resultSet[$i], $merge$association$type);
                        }
                    } else {
                        
$this->__mergeAssociation($resultSet[$i], $fetch$association$type);
                    }
                    
$resultSet[$i][$association] = $linkModel->afterfind($resultSet[$i][$association]);

                } else {
                    
$tempArray[0][$association] = false;
                    
$this->__mergeAssociation($resultSet[$i], $tempArray$association$type);
                }
            }
        }
    }
    
    function 
storeSession($type,$table,$fields) {
        
$this->_modifyQuery true;
        
        if(
$type == 'INSERT') {
            
$this->execute("
                INSERT INTO " 
$this->name($table) . "
                    (" 
$this->name('data') . "," $this->name('expires') . "," $this->name('id'). ") 
                VALUES 
                    (" 
$this->value($fields['data'],'text',false,$this->name('data')) . ", " $this->value($fields['expires']) . ", " $this->value($fields['id']) . ")
            "
);
        }
        else if(
$type == 'UPDATE') {
            
$this->execute("
                UPDATE 
                    " 
$this->name($table) . "
                SET 
                    " 
$this->name('data') . " = " $this->value($fields['data'],'text',false,$this->name('data')) . ",
                    " 
$this->name('expires') . " = " $this->value($fields['expires']) . "
                WHERE 
                    " 
$this->name('id') . " = " $this->value($fields['id'])
            );
        } 
    }
    
    
/**
     * Generates and executes an SQL UPDATE statement for given model, fields, and values.
     *
     * @param Model $model
     * @param array $fields
     * @param array $values
     * @param mixed $conditions
     * @return array
     */
    
function update(&$model$fields = array(), $values null$conditions null) {
        
$this->_modifyQuery true;
        
        return 
parent::update($model,$fields,$values,$conditions);
    }
    
    
/**
     * The "C" in CRUD
     *
     * @param Model $model
     * @param array $fields
     * @param array $values
     * @return boolean Success
     */
    
function create(&$model$fields null$values null) {    
        
$this->_modifyQuery true;
        
        
$fieldInsert = array();
        
$valueInsert = array();
        
$id null;

        if (
$fields == null) {
            unset(
$fields$values);
            
$fields array_keys($model->data);
            
$values array_values($model->data);
        }
        
$count count($fields);

        for (
$i 0$i $count$i++) {
            
$fieldInsert[] = $this->name($fields[$i]);
            if (
$fields[$i] == $model->primaryKey) {
                
$id $values[$i];
            }
        }
        
$count count($values);

        for (
$i 0$i $count$i++) {
            
$valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]),false,$fields[$i]);
        }

        if (
$this->execute('INSERT INTO ' $this->fullTableName($model) . ' (' join(','$fieldInsert). ') VALUES (' join(','$valueInsert) . ')')) {
            if (empty(
$id)) {
                if(!isset(
$model->sequence)) {
                    
$id $this->lastInsertId($this->fullTableName($modelfalse), $model->primaryKey);
                } else {
                    
$id $this->lastInsertId($model->sequence);    
                }
            }
            
$model->setInsertID($id);
            
$model->id $id;
            return 
true;
        } else {
            
$model->onError();
            return 
false;
        }
    }
    
/**
 * Quotes and prepares fields and values for an SQL UPDATE statement
 *
 * @param Model $model
 * @param array $fields
 * @param boolean $quoteValues If values should be quoted, or treated as SQL snippets
 * @param boolean $alias Include the model alias in the field name
 * @return array Fields and values, quoted and preparted
 * @access protected
 */
    
function _prepareUpdateFields(&$model$fields$quoteValues$alias) {
        foreach (
$fields as $field => $value) {
            if (
$alias) {
                
$quoted $model->escapeField($field);
            } else {
                
$quoted $this->name($field);
            }

            if (
$value === null) {
                
$updates[] = $quoted ' = NULL';
            } else {
                
$update $quoted ' = ';
                if (
$quoteValues) {
                    
$update .= $this->value($value$model->getColumnType($field),false,$field);
                } else {
                    
$update .= $value;
                }
                
$updates[] =  $update;
            }
        }
        return 
$updates;
    }
}
?>