<?php
/** 
 * @version V3.40 7 April 2003 (c) 2000-2003 John Lim (jlim@natsoft.com.my). All rights reserved.
 * Released under both BSD license and Lesser GPL library license. 
 * Whenever there is any discrepancy between the two licenses, 
 * the BSD license will take precedence. 
 *
 * Set tabs to 4 for best viewing.
 * 
 * Latest version is available at http://php.weblogs.com
 *
 */

    
    function ADODB_TransMonitor($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection)
    {
        /* print "Errorno ($fn errno=$errno m=$errmsg) "; */
        
        $thisConnection->_transOK = false;
        if ($thisConnection->_oldRaiseFn) {
            $fn = $thisConnection->_oldRaiseFn;
            $fn($dbms, $fn, $errno, $errmsg, $p1, $p2,$thisConnection);
        }
    }
    
    
    /**
     * Connection object. For connecting to databases, and executing queries.
     */ 
    class ADOConnection {
    /*  */
    /*  PUBLIC VARS  */
    /*  */
    var $dataProvider = 'native';
    var $databaseType = '';     /* / RDBMS currently in use, eg. odbc, mysql, mssql                 */
    var $database = '';         /* / Name of database to be used.   */
    var $host = '';         /* / The hostname of the database server    */
    var $user = '';         /* / The username which is used to connect to the database server.  */
    var $password = '';     /* / Password for the username. For security, we no longer store it. */
    var $debug = false;     /* / if set to true will output sql statements */
    var $maxblobsize = 256000;  /* / maximum size of blobs or large text fields -- some databases die otherwise like foxpro */
    var $concat_operator = '+'; /* / default concat operator -- change to || for Oracle/Interbase	 */
    var $fmtDate = "'Y-m-d'";   /* / used by DBDate() as the default date format used by the database */
    var $fmtTimeStamp = "'Y-m-d, h:i:s A'"; /* / used by DBTimeStamp as the default timestamp fmt. */
    var $true = '1';            /* / string that represents TRUE for a database */
    var $false = '0';           /* / string that represents FALSE for a database */
    var $replaceQuote = "\\'";  /* / string to use to replace quotes */
    var $charSet=false;     /* / character set to use - only for interbase */
    var $metaTablesSQL = '';
    /* -- */
    var $hasInsertID = false;       /* / supports autoincrement ID? */
    var $hasAffectedRows = false;   /* / supports affected rows for update/delete? */
    var $hasTop = false;            /* / support mssql/access SELECT TOP 10 * FROM TABLE */
    var $hasLimit = false;          /* / support pgsql/mysql SELECT * FROM TABLE LIMIT 10 */
    var $readOnly = false;          /* / this is a readonly database - used by phpLens */
    var $hasMoveFirst = false;  /* / has ability to run MoveFirst(), scrolling backwards */
    var $hasGenID = false;      /* / can generate sequences using GenID(); */
    var $hasTransactions = true; /* / has transactions */
    /* -- */
    var $genID = 0;         /* / sequence id used by GenID(); */
    var $raiseErrorFn = false;  /* / error function to call */
    var $upperCase = false; /* / uppercase function to call for searching/where */
    var $isoDates = false; /* / accepts dates in ISO format */
    var $cacheSecs = 3600; /* / cache for 1 hour */
    var $sysDate = false; /* / name of function that returns the current date */
    var $sysTimeStamp = false; /* / name of function that returns the current timestamp */
    var $arrayClass = 'ADORecordSet_array'; /* / name of class used to generate array recordsets, which are pre-downloaded recordsets */
    
    var $noNullStrings = false; /* / oracle specific stuff - if true ensures that '' is converted to ' ' */
    var $numCacheHits = 0; 
    var $numCacheMisses = 0;
    var $pageExecuteCountRows = true;
    var $uniqueSort = false; /* / indicates that all fields in order by must be unique */
    var $leftOuter = false; /* / operator to use for left outer join in WHERE clause */
    var $rightOuter = false; /* / operator to use for right outer join in WHERE clause */
    var $ansiOuter = false; /* / whether ansi outer join syntax supported */
    var $autoRollback = false; /*  autoRollback on PConnect(). */
    var $poorAffectedRows = false; /*  affectedRows not working or unreliable */
    
    var $fnExecute = false;
    var $fnCacheExecute = false;
    var $blobEncodeType = false; /*  false=not required, 'I'=encode to integer, 'C'=encode to char */
    var $dbxDriver = false;
    
     /*  */
     /*  PRIVATE VARS */
     /*  */
    var $_oldRaiseFn =  false;
    var $_transOK = null;
    var $_connectionID = false; /* / The returned link identifier whenever a successful database connection is made.    */
    var $_errorMsg = '';    /* / A variable which was used to keep the returned last error message.  The value will */
                                /* / then returned by the errorMsg() function	 */
                        
    var $_queryID = false;  /* / This variable keeps the last created result link identifier */
    
    var $_isPersistentConnection = false;   /* / A boolean variable to state whether its a persistent connection or normal connection.  */
    var $_bindInputArray = false; /* / set to true if ADOConnection.Execute() permits binding of array parameters. */
    var $autoCommit = true; /* / do not modify this yourself - actually private */
    var $transOff = 0;  /* / temporarily disable transactions */
    var $transCnt = 0;  /* / count of nested transactions */
    
    var $fetchMode=false;
    
    /**
     * Constructor
     */
    function ADOConnection()
    {
        die('Virtual Class -- cannot instantiate');
    }
    
    /**
        Get server version info...
        
        @returns An array with 2 elements: $arr['string'] is the description string, 
            and $arr[version] is the version (also a string).
    */
    function ServerInfo()
    {
        return array('description' => '', 'version' => '');
    }
    
    function _findvers($str)
    {
        if (preg_match('/([0-9]+\.([0-9\.])+)/',$str, $arr)) return $arr[1];
        else return '';
    }
    
    /**
    * All error messages go through this bottleneck function.
    * You can define your own handler by defining the function name in ADODB_OUTP.
    */
    function outp($msg,$newline=true)
    {
    global $HTTP_SERVER_VARS;
    
        if (defined('ADODB_OUTP')) {
            $fn = ADODB_OUTP;
            $fn($msg,$newline);
            return;
        }
        
        if ($newline) $msg .= "<br />\n";
        
        if (isset($HTTP_SERVER_VARS['HTTP_USER_AGENT'])) echo $msg;
        else echo strip_tags($msg);
        flush();
    }
    
    /**
     * Connect to database
     *
     * @param [argHostname]		Host to connect to
     * @param [argUsername]		Userid to login
     * @param [argPassword]		Associated password
     * @param [argDatabaseName]	database
     * @param [forceNew]        force new connection
     *
     * @return true or false
     */	  
    function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "", $forceNew = false) 
    {
        if ($argHostname != "") $this->host = $argHostname;
        if ($argUsername != "") $this->user = $argUsername;
        if ($argPassword != "") $this->password = $argPassword; /*  not stored for security reasons */
        if ($argDatabaseName != "") $this->database = $argDatabaseName;
        
        $this->_isPersistentConnection = false;	
        if ($fn = $this->raiseErrorFn) {
            if ($forceNew) {
                if ($this->_nconnect($this->host, $this->user, $this->password, $this->database)) return true;
            } else {
                 if ($this->_connect($this->host, $this->user, $this->password, $this->database)) return true;
            }
            $err = $this->ErrorMsg();
            if (empty($err)) $err = "Connection error to server '$argHostname' with user '$argUsername'";
            $fn($this->databaseType,'CONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this);
        } else {
            if ($forceNew) {
                if ($this->_nconnect($this->host, $this->user, $this->password, $this->database)) return true;
            } else {
                if ($this->_connect($this->host, $this->user, $this->password, $this->database)) return true;
            }
        }
        if ($this->debug) ADOConnection::outp( $this->host.': '.$this->ErrorMsg());
        
        return false;
    }	
    
     function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName)
     {
         return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName);
     }
    
    
    /**
     * Always force a new connection to database - currently only works with oracle
     *
     * @param [argHostname]		Host to connect to
     * @param [argUsername]		Userid to login
     * @param [argPassword]		Associated password
     * @param [argDatabaseName]	database
     *
     * @return true or false
     */	  
    function NConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") 
    {
        return $this->Connect($argHostname, $argUsername, $argPassword, $argDatabaseName, true);
    }
    
    /**
     * Establish persistent connect to database
     *
     * @param [argHostname]		Host to connect to
     * @param [argUsername]		Userid to login
     * @param [argPassword]		Associated password
     * @param [argDatabaseName]	database
     *
     * @return return true or false
     */	
    function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "")
    {
        if (defined('ADODB_NEVER_PERSIST')) 
            return $this->Connect($argHostname,$argUsername,$argPassword,$argDatabaseName);
        
        if ($argHostname != "") $this->host = $argHostname;
        if ($argUsername != "") $this->user = $argUsername;
        if ($argPassword != "") $this->password = $argPassword;
        if ($argDatabaseName != "") $this->database = $argDatabaseName;
            
        $this->_isPersistentConnection = true;
        
        if ($fn = $this->raiseErrorFn) {
            if ($this->_pconnect($this->host, $this->user, $this->password, $this->database)) return true;
            $err = $this->ErrorMsg();
            if (empty($err)) $err = "Connection error to server '$argHostname' with user '$argUsername'";
            $fn($this->databaseType,'PCONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this);
        } else 
            if ($this->_pconnect($this->host, $this->user, $this->password, $this->database)) return true;

        if ($this->debug) ADOConnection::outp( $this->host.': '.$this->ErrorMsg());
        
        return false;
    }

    /*  Format date column in sql string given an input format that understands Y M D */
    function SQLDate($fmt, $col=false)
    {	
        if (!$col) $col = $this->sysDate;
        return $col; /*  child class implement */
    }
    
    /**
     * Should prepare the sql statement and return the stmt resource.
     * For databases that do not support this, we return the $sql. To ensure
     * compatibility with databases that do not support prepare:
     *
     *   $stmt = $db->Prepare("insert into table (id, name) values (?,?)");
     *   $db->Execute($stmt,array(1,'Jill')) or die('insert failed');
     *   $db->Execute($stmt,array(2,'Joe')) or die('insert failed');
     *
     * @param sql   SQL to send to database
     *
     * @return return FALSE, or the prepared statement, or the original sql if
     *          if the database does not support prepare.
     *
     */	
    function Prepare($sql)
    {
        return $sql;
    }

    /**
     * Some databases, eg. mssql require a different function for preparing
     * stored procedures. So we cannot use Prepare().
     *
     * Should prepare the stored procedure  and return the stmt resource.
     * For databases that do not support this, we return the $sql. To ensure
     * compatibility with databases that do not support prepare:
     *
     * @param sql   SQL to send to database
     *
     * @return return FALSE, or the prepared statement, or the original sql if
     *          if the database does not support prepare.
     *
     */	
    function PrepareSP($sql)
    {
        return $this->Prepare($sql);
    }
    
    /**
    * PEAR DB Compat
    */
    function Quote($s)
    {
        return $this->qstr($s,false);
    }

    
    /**
    * PEAR DB Compat - do not use internally. 
    */
    function ErrorNative()
    {
        return $this->ErrorNo();
    }

    
   /**
    * PEAR DB Compat - do not use internally. 
    */
    function nextId($seq_name)
    {
        return $this->GenID($seq_name);
    }

    /**
    *   Lock a row, will escalate and lock the table if row locking not supported
    *   will normally free the lock at the end of the transaction
    *
    *  @param $table    name of table to lock
    *  @param $where    where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock
    */
    function RowLock($table,$where)
    {
        return false;
    }
    
    function CommitLock($table)
    {
        return $this->CommitTrans();
    }
    
    function RollbackLock($table)
    {
        return $this->RollbackTrans();
    }
    
    /**
    * PEAR DB Compat - do not use internally. 
    *
    * The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical
    *   for easy porting :-)
    *
    * @param mode   The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM
    * @returns      The previous fetch mode
    */
    function SetFetchMode($mode)
    {	
        $old = $this->fetchMode;
        $this->fetchMode = $mode;
        
        if ($old === false) {
        global $ADODB_FETCH_MODE;
            return $ADODB_FETCH_MODE;
        }
        return $old;
    }
    

    /**
    * PEAR DB Compat - do not use internally. 
    */
    function &Query($sql, $inputarr=false)
    {
        $rs = &$this->Execute($sql, $inputarr);
        if (!$rs && defined('ADODB_PEAR')) return ADODB_PEAR_Error();
        return $rs;
    }

    
    /**
    * PEAR DB Compat - do not use internally
    */
    function &LimitQuery($sql, $offset, $count)
    {
        $rs = &$this->SelectLimit($sql, $count, $offset); /*  swap  */
        if (!$rs && defined('ADODB_PEAR')) return ADODB_PEAR_Error();
        return $rs;
    }

    
    /**
    * PEAR DB Compat - do not use internally
    */
    function Disconnect()
    {
        return $this->Close();
    }

    /* 
    Usage in oracle
        $stmt = $db->Prepare('select * from table where id =:myid and group=:group');
        $db->Parameter($stmt,$id,'myid');
        $db->Parameter($stmt,$group,'group',64);
        $db->Execute();
        
        @param $stmt Statement returned by Prepare() or PrepareSP().
        @param $var PHP variable to bind to
        @param $name Name of stored procedure variable name to bind to.
        @param [$isOutput] Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
        @param [$maxLen] Holds an maximum length of the variable.
        @param [$type] The data type of $var. Legal values depend on driver.

    */
    function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false)
    {
        return false;
    }
    
    /**
        Improved method of initiating a transaction. Used together with CompleteTrans().
        Advantages include:
        
        a. StartTrans/CompleteTrans is nestable, unlike BeginTrans/CommitTrans/RollbackTrans.
           Only the outermost block is treated as a transaction.<br />
        b. CompleteTrans auto-detects SQL errors, and will rollback on errors, commit otherwise.<br />
        c. All BeginTrans/CommitTrans/RollbackTrans inside a StartTrans/CompleteTrans block
           are disabled, making it backward compatible.
    */
    function StartTrans($errfn = 'ADODB_TransMonitor')
    {
        
        if ($this->transOff > 0) {
            $this->transOff += 1;
            return;
        }
        
        $this->_oldRaiseFn = $this->raiseErrorFn;
        $this->raiseErrorFn = $errfn;
        $this->_transOK = true;
        
        if ($this->debug && $this->transCnt > 0) ADOConnection::outp("Bad Transaction: StartTrans called within BeginTrans");
        $this->BeginTrans();
        $this->transOff = 1;
    }
    
    /**
        Used together with StartTrans() to end a transaction. Monitors connection
        for sql errors, and will commit or rollback as appropriate.
        
        @autoComplete if true, monitor sql errors and commit and rollback as appropriate, 
        and if set to false force rollback even if no SQL error detected.
        @returns true on commit, false on rollback.
    */
    function CompleteTrans($autoComplete = true)
    {
        if ($this->transOff > 1) {
            $this->transOff -= 1;
            return true;
        }
        $this->raiseErrorFn = $this->_oldRaiseFn;
        
        $this->transOff = 0;
        if ($this->_transOK && $autoComplete) $this->CommitTrans();
        else $this->RollbackTrans();
        
        return $this->_transOK;
    }
    
    /*
        At the end of a StartTrans/CompleteTrans block, perform a rollback.
    */
    function FailTrans()
    {
        if ($this->debug && $this->transOff == 0) {
            ADOConnection::outp("FailTrans outside StartTrans/CompleteTrans");
        }
        $this->_transOK = false;
    }
    /**
     * Execute SQL 
     *
     * @param sql		SQL statement to execute, or possibly an array holding prepared statement ($sql[0] will hold sql text)
     * @param [inputarr]	holds the input data to bind to. Null elements will be set to null.
     * @param [arg3]	reserved for john lim for future use
     * @return 		RecordSet or false
     */
    function &Execute($sql,$inputarr=false,$arg3=false) 
    {
        if ($this->fnExecute) {
            $fn = $this->fnExecute;
            $fn($this,$sql,$inputarr);
        }
        if (!$this->_bindInputArray && $inputarr) {
            $sqlarr = explode('?',$sql);
            $sql = '';
            $i = 0;
            foreach($inputarr as $v) {

                $sql .= $sqlarr[$i];
                /*  from Ron Baldwin <ron.baldwin@sourceprose.com> */
                /*  Only quote string types	 */
                if (gettype($v) == 'string')
                    $sql .= $this->qstr($v);
                else if ($v === null)
                    $sql .= 'NULL';
                else
                    $sql .= $v;
                $i += 1;
    
            }
            $sql .= $sqlarr[$i];
            if ($i+1 != sizeof($sqlarr))	
                ADOConnection::outp( "Input Array does not match ?: ".htmlspecialchars($sql));
            $inputarr = false;
        }
        /*  debug version of query */
        if ($this->debug) {
        global $HTTP_SERVER_VARS;
        
            $ss = '';
            if ($inputarr) {
                foreach ($inputarr as $kk => $vv)  {
                    if (is_string($vv) && strlen($vv)>64) $vv = substr($vv,0,64).'...';
                    $ss .= "($kk=>'$vv') ";
                }
                $ss = "[ $ss ]";
            }
            if (is_array($sql)) $sqlTxt = $sql[0];
            else $sqlTxt = $sql;
            
            /*  check if running from browser or command-line */
            $inBrowser = isset($HTTP_SERVER_VARS['HTTP_USER_AGENT']);
            
            if ($inBrowser)
                ADOConnection::outp( "<hr />\n($this->databaseType): ".htmlspecialchars($sqlTxt)." &nbsp; <code>$ss</code>\n<hr />\n",false);
            else
                ADOConnection::outp(  "=----\n($this->databaseType): ".($sqlTxt)." \n-----\n",false);
            flush();
            
            $this->_queryID = $this->_query($sql,$inputarr,$arg3);

            /* 
                Alexios Fakios notes that ErrorMsg() must be called before ErrorNo() for mssql
                because ErrorNo() calls Execute('SELECT @ERROR'), causing recure
            */
            if ($this->databaseType == 'mssql') { 
            /*  ErrorNo is a slow function call in mssql, and not reliable */
            /*  in PHP 4.0.6 */
                if($emsg = $this->ErrorMsg()) {
                    $err = $this->ErrorNo();
                    if ($err) {
                        ADOConnection::outp($err.': '.$emsg);
                        flush();
                    }
                }
            } else 
                if (!$this->_queryID) {
                    $e = $this->ErrorNo();
                    $m = $this->ErrorMsg();
                    ADOConnection::outp($e .': '. $m );
                    flush();
                }
        } else {
            /*  non-debug version of query */
            
            $this->_queryID =@$this->_query($sql,$inputarr,$arg3);
            
        }
        /*  error handling if query fails */
        if ($this->_queryID === false) {
            $fn = $this->raiseErrorFn;
            if ($fn) {
                $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr,$this);
            }
            return false;
        } else if ($this->_queryID === true) {
        /*  return simplified empty recordset for inserts/updates/deletes with lower overhead */
            $rs = new ADORecordSet_empty();
            return $rs;
        }
        
        /*  return real recordset from select statement */
        $rsclass = "ADORecordSet_".$this->databaseType;
        $rs = new $rsclass($this->_queryID,$this->fetchMode); /*  &new not supported by older PHP versions */
        $rs->connection = &$this; /*  Pablo suggestion */
        $rs->Init();
        if (is_array($sql)) $rs->sql = $sql[0];
        else $rs->sql = $sql;
        
        if ($rs->_numOfRows <= 0) {
        global $ADODB_COUNTRECS;
        
            if ($ADODB_COUNTRECS) {
                if (!$rs->EOF){ 
                    $rs = &$this->_rs2rs($rs,-1,-1,!is_array($sql));
                    $rs->_queryID = $this->_queryID;
                } else
                    $rs->_numOfRows = 0;
            }
        }
        return $rs;
    }

    function CreateSequence($seqname='adodbseq',$startID=1)
    {
        if (empty($this->_genSeqSQL)) return false;
        return $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
    }
    
    function DropSequence($seqname)
    {
        if (empty($this->_dropSeqSQL)) return false;
        return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
    }

    /**
     * Generates a sequence id and stores it in $this->genID;
     * GenID is only available if $this->hasGenID = true;
     *
     * @param seqname       name of sequence to use
     * @param startID       if sequence does not exist, start at this ID
     * @return      0 if not supported, otherwise a sequence id
     */

    function GenID($seqname='adodbseq',$startID=1)
    {
        if (!$this->hasGenID) {
            return 0; /*  formerly returns false pre 1.60 */
        }
        
        $getnext = sprintf($this->_genIDSQL,$seqname);
        $rs = @$this->Execute($getnext);
        if (!$rs) {
            $createseq = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
            $rs = $this->Execute($getnext);
        }
        if ($rs && !$rs->EOF) $this->genID = reset($rs->fields);
        else $this->genID = 0; /*  false */
    
        if ($rs) $rs->Close();

        return $this->genID;
    }	

    /**
     * @return  the last inserted ID. Not all databases support this.
     */ 
        function Insert_ID()
        {
                if ($this->hasInsertID) return $this->_insertid();
                if ($this->debug) ADOConnection::outp( '<p>Insert_ID error</p>');
                return false;
        }
    
    
    /**
     * Portable Insert ID. Pablo Roca <pabloroca@mvps.org>
     *
     * @return  the last inserted ID. All databases support this. But aware possible
     * problems in multiuser environments. Heavy test this before deploying.
     */ 
        function PO_Insert_ID($table="", $id="") 
        {
           if ($this->hasInsertID){
               return $this->Insert_ID();
           } else {
               return $this->GetOne("SELECT MAX($id) FROM $table");
           }
        }	
    
        
     /**
     * @return  # rows affected by UPDATE/DELETE
     */ 
     function Affected_Rows()
     {
          if ($this->hasAffectedRows) {
                 $val = $this->_affectedrows();
                 return ($val < 0) ? false : $val;
          }
                  
          if ($this->debug) ADOConnection::outp( '<p>Affected_Rows error</p>',false);
          return false;
     }
    
    
    /**
     * @return  the last error message
     */
    function ErrorMsg()
    {
        return '!! '.strtoupper($this->dataProvider.' '.$this->databaseType).': '.$this->_errorMsg;
    }
    
    
    /**
     * @return the last error number. Normally 0 means no error.
     */
    function ErrorNo() 
    {
        return ($this->_errorMsg) ? -1 : 0;
    }
    
    function MetaError($err=false)
    {
        include_once(ADODB_DIR."/adodb-error.inc.php");
        if ($err === false) $err = $this->ErrorNo();
        return adodb_error($this->dataProvider,$this->databaseType,$err);
    }
    
    function MetaErrorMsg($errno)
    {
        include_once(ADODB_DIR."/adodb-error.inc.php");
        return adodb_errormsg($errno);
    }
    
    /**
     * @returns an array with the primary key columns in it.
     */
    function MetaPrimaryKeys($table, $owner=false)
    {
    /*  owner not used in base class - see oci8 */
        $p = array();
        $objs = $this->MetaColumns($table);
        if ($objs) {
            foreach($objs as $v) {
                if (!empty($v->primary_key))
                    $p[] = $v->name;
            }
        }
        if (sizeof($p)) return $p;
        return false;
    }
    
    
    /**
     * Choose a database to connect to. Many databases do not support this.
     *
     * @param dbName    is the name of the database to select
     * @return      true or false
     */
    function SelectDB($dbName) 
    {return false;}
    
    
    /**
    * Will select, getting rows from $offset (1-based), for $nrows. 
    * This simulates the MySQL "select * from table limit $offset,$nrows" , and
    * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
    * MySQL and PostgreSQL parameter ordering is the opposite of the other.
    * eg. 
    *  SelectLimit('select * from table',3); will return rows 1 to 3 (1-based)
    *  SelectLimit('select * from table',3,2); will return rows 3 to 5 (1-based)
    *
    * Uses SELECT TOP for Microsoft databases (when $this->hasTop is set)
    * BUG: Currently SelectLimit fails with $sql with LIMIT or TOP clause already set
    *
    * @param sql
    * @param [offset]   is the row to start calculations from (1-based)
    * @param [nrows]        is the number of rows to get
    * @param [inputarr]	array of bind variables
    * @param [arg3]     is a private parameter only used by jlim
    * @param [secs2cache]       is a private parameter only used by jlim
    * @return       the recordset ($rs->databaseType == 'array')
     */
    function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$arg3=false,$secs2cache=0)
    {
        if ($this->hasTop && $nrows > 0) {
        /*  suggested by Reinhard Balling. Access requires top after distinct  */
         /*  Informix requires first before distinct - F Riosa */
            $ismssql = (strpos($this->databaseType,'mssql') !== false);
            if ($ismssql) $isaccess = false;
            else $isaccess = (strpos($this->databaseType,'access') !== false);
            
            if ($offset <= 0) {
                
                    /*  access includes ties in result */
                    if ($isaccess) {
                        $sql = preg_replace(
                        '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nrows.' ',$sql);

                        if ($secs2cache>0) return $this->CacheExecute($secs2cache, $sql,$inputarr,$arg3);
                        else return $this->Execute($sql,$inputarr,$arg3);
                    } else if ($ismssql){
                        $sql = preg_replace(
                        '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nrows.' ',$sql);
                    } else {
                        $sql = preg_replace(
                        '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.$nrows.' ',$sql);
                    }
            } else {
                $nn = $nrows + $offset;
                if ($isaccess || $ismssql) {
                    $sql = preg_replace(
                    '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql);
                } else {
                    $sql = preg_replace(
                    '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql);
                }
            }
        }
        
        /*  if $offset>0, we want to skip rows, and $ADODB_COUNTRECS is set, we buffer  rows */
        /*  0 to offset-1 which will be discarded anyway. So we disable $ADODB_COUNTRECS. */
        global $ADODB_COUNTRECS;
        
        $savec = $ADODB_COUNTRECS;
        $ADODB_COUNTRECS = false;
            
        if ($offset>0){
            if ($secs2cache>0) $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr,$arg3);
            else $rs = &$this->Execute($sql,$inputarr,$arg3);
        } else {
            if ($secs2cache>0) $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr,$arg3);
            else $rs = &$this->Execute($sql,$inputarr,$arg3);
        }
        $ADODB_COUNTRECS = $savec;
        if ($rs && !$rs->EOF) {
            return $this->_rs2rs($rs,$nrows,$offset);
        }
        /* print_r($rs); */
        return $rs;
    }
    
    
    /**
    * Convert database recordset to an array recordset
    * input recordset's cursor should be at beginning, and
    * old $rs will be closed.
    *
    * @param rs         the recordset to copy
    * @param [nrows]        number of rows to retrieve (optional)
    * @param [offset]       offset by number of rows (optional)
    * @return           the new recordset
    */
    function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true)
    {
        if (! $rs) return false;
        
        $dbtype = $rs->databaseType;
        if (!$dbtype) {
            $rs = &$rs;  /*  required to prevent crashing in 4.2.1, but does not happen in 4.3.1 -- why ? */
            return $rs;
        }
        if (($dbtype == 'array' || $dbtype == 'csv') && $nrows == -1 && $offset == -1) {
            $rs->MoveFirst();
            $rs = &$rs; /*  required to prevent crashing in 4.2.1, but does not happen in 4.3.1-- why ? */
            return $rs;
        }
        
        for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) {
            $flds[] = $rs->FetchField($i);
        }
        $arr = $rs->GetArrayLimit($nrows,$offset);
        /* print_r($arr); */
        if ($close) $rs->Close();
        
        $arrayClass = $this->arrayClass;
        
        $rs2 = new $arrayClass();
        $rs2->connection = &$this;
        $rs2->sql = $rs->sql;
        $rs2->dataProvider = $this->dataProvider;
        $rs2->InitArrayFields($arr,$flds);
        return $rs2;
    }
    
    
    /**
    * Return first element of first row of sql statement. Recordset is disposed
    * for you.
    *
    * @param sql            SQL statement
    * @param [inputarr]     input bind array
    */
    function GetOne($sql,$inputarr=false)
    {
    global $ADODB_COUNTRECS;
        $crecs = $ADODB_COUNTRECS;
        $ADODB_COUNTRECS = false;
        
        $ret = false;
        $rs = &$this->Execute($sql,$inputarr);
        if ($rs) {
            if (!$rs->EOF) $ret = reset($rs->fields);
            $rs->Close();
        } 
        $ADODB_COUNTRECS = $crecs;
        return $ret;
    }
    
    function CacheGetOne($secs2cache,$sql=false,$inputarr=false)
    {
        $ret = false;
        $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr);
        if ($rs) {
            if (!$rs->EOF) $ret = reset($rs->fields);
            $rs->Close();
        } 
        
        return $ret;
    }
    
    function GetCol($sql, $inputarr = false, $trim = false)
    {
          $rv = false;
          $rs = &$this->Execute($sql, $inputarr);
          if ($rs) {
               if ($trim) {
                while (!$rs->EOF) {
                    $rv[] = trim(reset($rs->fields));
                    $rs->MoveNext();
                   }
            } else {
                while (!$rs->EOF) {
                    $rv[] = reset($rs->fields);
                    $rs->MoveNext();
                   }
            }
               $rs->Close();
          }
          return $rv;
    }
    
    function CacheGetCol($secs, $sql, $inputarr = false,$trim=false)
    {
          $rv = false;
          $rs = &$this->CacheExecute($secs, $sql, $inputarr);
          if ($rs) {
            if ($trim) {
                while (!$rs->EOF) {
                    $rv[] = trim(reset($rs->fields));
                    $rs->MoveNext();
                   }
            } else {
                while (!$rs->EOF) {
                    $rv[] = reset($rs->fields);
                    $rs->MoveNext();
                   }
            }
               $rs->Close();
          }
          return $rv;
    }
 
    /*
        Calculate the offset of a date for a particular database and generate
            appropriate SQL. Useful for calculating future/past dates and storing
            in a database.
            
        If dayFraction=1.5 means 1.5 days from now, 1.0/24 for 1 hour.
    */
    function OffsetDate($dayFraction,$date=false)
    {
        if (!$date) $date = $this->sysDate;
        return  '('.$date.'+'.$dayFraction.')';
    }
    
    
    /**
    * Return all rows. Compat with PEAR DB
    *
    * @param sql            SQL statement
    * @param [inputarr]     input bind array
    */
    function GetAll($sql,$inputarr=false)
    {
    global $ADODB_COUNTRECS;
        
        $savec = $ADODB_COUNTRECS;
        $ADODB_COUNTRECS = false;
        $rs = $this->Execute($sql,$inputarr);
        $ADODB_COUNTRECS = $savec;
        
        if (!$rs) 
            if (defined('ADODB_PEAR')) return ADODB_PEAR_Error();
            else return false;
        $arr = $rs->GetArray();
        $rs->Close();
        return $arr;
    }
    
    function CacheGetAll($secs2cache,$sql=false,$inputarr=false)
    {
    global $ADODB_COUNTRECS;
        
        $savec = $ADODB_COUNTRECS;
        $ADODB_COUNTRECS = false;
        $rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
        $ADODB_COUNTRECS = $savec;
        
        if (!$rs) 
            if (defined('ADODB_PEAR')) return ADODB_PEAR_Error();
            else return false;
        
        $arr = $rs->GetArray();
        $rs->Close();
        return $arr;
    }
    
    
    
    /**
    * Return one row of sql statement. Recordset is disposed for you.
    *
    * @param sql            SQL statement
    * @param [inputarr]     input bind array
    */
    function GetRow($sql,$inputarr=false)
    {
    global $ADODB_COUNTRECS;
        $crecs = $ADODB_COUNTRECS;
        $ADODB_COUNTRECS = false;
        
        $rs = $this->Execute($sql,$inputarr);
        
        $ADODB_COUNTRECS = $crecs;
        if ($rs) {
            $arr = false;
            if (!$rs->EOF) $arr = $rs->fields;
            $rs->Close();
            return $arr;
        }
        
        return false;
    }
    
    function CacheGetRow($secs2cache,$sql=false,$inputarr=false)
    {
        $rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
        if ($rs) {
            $arr = false;
            if (!$rs->EOF) $arr = $rs->fields;
            $rs->Close();
            return $arr;
        }
        return false;
    }
    
    /**
    * Insert or replace a single record. Note: this is not the same as MySQL's replace. 
    *  ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL.
    *
    * $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname');
    *
    * $table        table name
    * $fieldArray   associative array of data (you must quote strings yourself).
    * $keyCol       the primary key field name or if compound key, array of field names
    * autoQuote     set to true to use a hueristic to quote strings. Works with nulls and numbers
    *                   but does not work with dates nor SQL functions.
    * has_autoinc   the primary key is an auto-inc field, so skip in insert.
    *
    * Currently blob replace not supported
    *
    * returns 0 = fail, 1 = update, 2 = insert 
    */
    
    function Replace($table, $fieldArray, $keyCol, $autoQuote=false, $has_autoinc=false)
    {
        if (count($fieldArray) == 0) return 0;
        $first = true;
        $uSet = '';
        
        if (!is_array($keyCol)) {
            $keyCol = array($keyCol);
        }
        foreach($fieldArray as $k => $v) {
            if ($autoQuote && !is_numeric($v) and $v[0] != "'" and strcasecmp($v,'null')!=0) {
                $v = $this->qstr($v);
                $fieldArray[$k] = $v;
            }
            if (in_array($k,$keyCol)) continue; /*  skip UPDATE if is key */
            
            if ($first) {
                $first = false;
                $uSet = "$k=$v";
            } else
                $uSet .= ",$k=$v";
        }
         
        $first = true;
        foreach ($keyCol as $v) {
            if ($first) {
                $first = false;
                $where = "$v=$fieldArray[$v]";
            } else {
                $where .= " and $v=$fieldArray[$v]";
            }
        }
        
        if ($uSet) {
            $update = "UPDATE $table SET $uSet WHERE $where";
        
            $rs = $this->Execute($update);
            if ($rs) {
                if ($this->poorAffectedRows) {
                /*
                 The Select count(*) wipes out any errors that the update would have returned. 
                http://phplens.com/lens/lensforum/msgs.php?id=5696
                */
                    if ($this->ErrorNo()<>0) return 0;
                    
                # affected_rows == 0 if update field values identical to old values
                # for mysql - which is silly. 
            
                    $cnt = $this->GetOne("select count(*) from $table where $where");
                    if ($cnt > 0) return 1; /*  record already exists */
                } else
                     if (($this->Affected_Rows()>0)) return 1;
            }
                
        }
    /* 	print "<p>Error=".$this->ErrorNo().'<p>'; */
        $first = true;
        foreach($fieldArray as $k => $v) {
            if ($has_autoinc && in_array($k,$keyCol)) continue; /*  skip autoinc col */
            
            if ($first) {
                $first = false;			
                $iCols = "$k";
                $iVals = "$v";
            } else {
                $iCols .= ",$k";
                $iVals .= ",$v";
            }				
        }
        $insert = "INSERT INTO $table ($iCols) VALUES ($iVals)"; 
        $rs = $this->Execute($insert);
        return ($rs) ? 2 : 0;
    }
    
    
    /**
    * Will select, getting rows from $offset (1-based), for $nrows. 
    * This simulates the MySQL "select * from table limit $offset,$nrows" , and
    * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
    * MySQL and PostgreSQL parameter ordering is the opposite of the other.
    * eg. 
    *  CacheSelectLimit(15,'select * from table',3); will return rows 1 to 3 (1-based)
    *  CacheSelectLimit(15,'select * from table',3,2); will return rows 3 to 5 (1-based)
    *
    * BUG: Currently CacheSelectLimit fails with $sql with LIMIT or TOP clause already set
    *
    * @param [secs2cache]   seconds to cache data, set to 0 to force query. This is optional
    * @param sql
    * @param [offset]   is the row to start calculations from (1-based)
    * @param [nrows]    is the number of rows to get
    * @param [inputarr]	array of bind variables
    * @param [arg3]     is a private parameter only used by jlim
    * @return       the recordset ($rs->databaseType == 'array')
     */
    function &CacheSelectLimit($secs2cache,$sql,$nrows=-1,$offset=-1,$inputarr=false, $arg3=false)
    {	
        if (!is_numeric($secs2cache)) {
            if ($sql === false) $sql = -1;
            if ($offset == -1) $offset = false;
                                      /*  sql,	nrows, offset,inputarr,arg3 */
            return $this->SelectLimit($secs2cache,$sql,$nrows,$offset,$inputarr,$this->cacheSecs);
        } else {
            if ($sql === false) ADOConnection::outp( "Warning: \$sql missing from CacheSelectLimit()");
            return $this->SelectLimit($sql,$nrows,$offset,$inputarr,$arg3,$secs2cache);
        }
    }
    
    /**
    * Flush cached recordsets that match a particular $sql statement. 
    * If $sql == false, then we purge all files in the cache.
     */
    function CacheFlush($sql=false,$inputarr=false)
    {
    global $ADODB_CACHE_DIR;
    
        if (strlen($ADODB_CACHE_DIR) > 1 && !$sql) {
            if (strpos(strtoupper(PHP_OS),'WIN') !== false) {
                $cmd = 'del /s '.str_replace('/','\\',$ADODB_CACHE_DIR).'\adodb_*.cache';
            } else {
                $cmd = 'rm -rf '.$ADODB_CACHE_DIR.'/??/adodb_*.cache'; 
                /*  old version 'rm -f `find '.$ADODB_CACHE_DIR.' -name adodb_*.cache`'; */
            }
            if ($this->debug) {
                ADOConnection::outp( "CacheFlush: $cmd<br /><pre>\n", system($cmd),"</pre>");
            } else {
                exec($cmd);
            }
            return;
        } 
        $f = $this->_gencachename($sql.serialize($inputarr),false);
        adodb_write_file($f,''); /*  is adodb_write_file needed? */
        @unlink($f);
    }
    
    /**
    * Private function to generate filename for caching.
    * Filename is generated based on:
    *
    *  - sql statement
    *  - database type (oci8, ibase, ifx, etc)
    *  - database name
    *  - userid
    *
    * We create 256 sub-directories in the cache directory ($ADODB_CACHE_DIR). 
    * Assuming that we can have 50,000 files per directory with good performance, 
    * then we can scale to 12.8 million unique cached recordsets. Wow!
     */
    function _gencachename($sql,$createdir)
    {
    global $ADODB_CACHE_DIR;
        
        $m = md5($sql.$this->databaseType.$this->database.$this->user);
        $dir = $ADODB_CACHE_DIR.'/'.substr($m,0,2);
        if ($createdir && !file_exists($dir)) {
            $oldu = umask(0);
            if (!mkdir($dir,0771)) 
                if ($this->debug) ADOConnection::outp( "Unable to mkdir $dir for $sql");
            umask($oldu);
        }
        return $dir.'/adodb_'.$m.'.cache';
    }
    
    
    /**
     * Execute SQL, caching recordsets.
     *
     * @param [secs2cache]	seconds to cache data, set to 0 to force query. 
     *                  This is an optional parameter.
     * @param sql       SQL statement to execute
     * @param [inputarr]    holds the input data  to bind to
     * @param [arg3]    reserved for john lim for future use
     * @return      RecordSet or false
     */
    function &CacheExecute($secs2cache,$sql=false,$inputarr=false,$arg3=false)
    {
        if (!is_numeric($secs2cache)) {
            $arg3 = $inputarr;
            $inputarr = $sql;
            $sql = $secs2cache;
            $secs2cache = $this->cacheSecs;
        }
        include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
        
        $md5file = $this->_gencachename($sql.serialize($inputarr),true);
        $err = '';
        
        if ($secs2cache > 0){
            $rs = &csv2rs($md5file,$err,$secs2cache);
            $this->numCacheHits += 1;
        } else {
            $err='Timeout 1';
            $rs = false;
            $this->numCacheMisses += 1;
        }
        if (!$rs) {
        /*  no cached rs found */
            if ($this->debug) {
                if (get_magic_quotes_runtime()) {
                    ADOConnection::outp("Please disable magic_quotes_runtime - it corrupts cache files :(");
                }
                ADOConnection::outp( " $md5file cache failure: $err (see sql below)");
            }
            $rs = &$this->Execute($sql,$inputarr,$arg3);
            if ($rs) {
                $eof = $rs->EOF;
                $rs = &$this->_rs2rs($rs); /*  read entire recordset into memory immediately */
                $txt = _rs2serialize($rs,false,$sql); /*  serialize */
        
                if (!adodb_write_file($md5file,$txt,$this->debug)) {
                    if ($fn = $this->raiseErrorFn) {
                        $fn($this->databaseType,'CacheExecute',-32000,"Cache write error",$md5file,$sql,$this);
                    }
                    if ($this->debug) ADOConnection::outp( " Cache write error");
                }
                if ($rs->EOF && !$eof) {
                    $rs->MoveFirst();
                    /* $rs = &csv2rs($md5file,$err);		 */
                    $rs->connection = &$this; /*  Pablo suggestion */
                }  
                
            } else
                @unlink($md5file);
        } else {
            if ($this->fnCacheExecute) {
                $fn = $this->fnCacheExecute;
                $fn($this, $secs2cache, $sql, $inputarr);
            }
        /*  ok, set cached object found */
            $rs->connection = &$this; /*  Pablo suggestion */
            if ($this->debug){ 
            global $HTTP_SERVER_VARS;
                    
                $inBrowser = isset($HTTP_SERVER_VARS['HTTP_USER_AGENT']);
                $ttl = $rs->timeCreated + $secs2cache - time();
                $s = is_array($sql) ? $sql[0] : $sql;
                if ($inBrowser) $s = '<i>'.htmlspecialchars($s).'</i>';
                
                ADOConnection::outp( " $md5file reloaded, ttl=$ttl [ $s ]");
            }
        }
        return $rs;
    }
    
    
    /**
     * Generates an Update Query based on an existing recordset.
     * $arrFields is an associative array of fields with the value
     * that should be assigned.
     *
     * Note: This function should only be used on a recordset
     *      that is run against a single table and sql should only 
     *      be a simple select stmt with no groupby/orderby/limit
     *
     * "Jonathan Younger" <jyounger@unilab.com>
       */
    function GetUpdateSQL(&$rs, $arrFields,$forceUpdate=false,$magicq=false)
    {
        include_once(ADODB_DIR.'/adodb-lib.inc.php');
        return _adodb_getupdatesql($this,$rs,$arrFields,$forceUpdate,$magicq);
    }


    /**
     * Generates an Insert Query based on an existing recordset.
     * $arrFields is an associative array of fields with the value
     * that should be assigned.
     *
     * Note: This function should only be used on a recordset
     *      that is run against a single table.
       */
    function GetInsertSQL(&$rs, $arrFields,$magicq=false)
    {	
        include_once(ADODB_DIR.'/adodb-lib.inc.php');
        return _adodb_getinsertsql($this,$rs,$arrFields,$magicq);
    }
    

    /**
    * Update a blob column, given a where clause. There are more sophisticated
    * blob handling functions that we could have implemented, but all require
    * a very complex API. Instead we have chosen something that is extremely
    * simple to understand and use. 
    *
    * Note: $blobtype supports 'BLOB' and 'CLOB', default is BLOB of course.
    *
    * Usage to update a $blobvalue which has a primary key blob_id=1 into a 
    * field blobtable.blobcolumn:
    *
    *   UpdateBlob('blobtable', 'blobcolumn', $blobvalue, 'blob_id=1');
    *
    * Insert example:
    *
    *   $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
    *   $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
    */
    
    function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
    {
        return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
    }

    /**
    * Usage:
    *   UpdateBlob('TABLE', 'COLUMN', '/path/to/file', 'ID=1');
    *
    *   $blobtype supports 'BLOB' and 'CLOB'
    *
    *   $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
    *   $conn->UpdateBlob('blobtable','blobcol',$blobpath,'id=1');
    */
    function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB')
    {
        $fd = fopen($path,'rb');
        if ($fd === false) return false;
        $val = fread($fd,filesize($path));
        fclose($fd);
        return $this->UpdateBlob($table,$column,$val,$where,$blobtype);
    }
    
    function BlobDecode($blob)
    {
        return $blob;
    }
    
    function BlobEncode($blob)
    {
        return $blob;
    }
    
    /**
    * Usage:
    *   UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB');
    *
    *   $conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)');
    *   $conn->UpdateClob('clobtable','clobcol',$clob,'id=1');
    */
    function UpdateClob($table,$column,$val,$where)
    {
        return $this->UpdateBlob($table,$column,$val,$where,'CLOB');
    }
    
    
    /**
     *  $meta   contains the desired type, which could be...
     *  C for character. You will have to define the precision yourself.
     *  X for teXt. For unlimited character lengths.
     *  B for Binary
     *  F for floating point, with no need to define scale and precision
     *  N for decimal numbers, you will have to define the (scale, precision) yourself
     *  D for date
     *  T for timestamp
     *  L for logical/Boolean
     *  I for integer
     *  R for autoincrement counter/integer
     *  and if you want to use double-byte, add a 2 to the end, like C2 or X2.
     * 
     *
     * @return the actual type of the data or false if no such type available
    */
     function ActualType($meta)
    {
        switch($meta) {
        case 'C':
        case 'X':
            return 'VARCHAR';
        case 'B':
            
        case 'D':
        case 'T':
        case 'L':
        
        case 'R':
            
        case 'I':
        case 'N':
            return false;
        }
    }

    /*
    * Maximum size of C field
    */
    function CharMax()
    {
        return 255; /*  make it conservative if not defined */
    }
    
    
    /*
    * Maximum size of X field
    */
    function TextMax()
    {
        return 4000; /*  make it conservative if not defined */
    }
    
    /**
     * Close Connection
     */
    function Close() 
    {
        return $this->_close();
        
        /*  "Simon Lee" <simon@mediaroad.com> reports that persistent connections need  */
        /*  to be closed too! */
        /* if ($this->_isPersistentConnection != true) return $this->_close(); */
        /* else return true;*/
    }
    
    /**
     * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans().
     *
     * @return true if succeeded or false if database does not support transactions
     */
    function BeginTrans() {return false;}
    
    
    /**
     * If database does not support transactions, always return true as data always commited
     *
     * @param $ok  set to false to rollback transaction, true to commit
     *
     * @return true/false.
     */
    function CommitTrans($ok=true) 
    { return true;}
    
    
    /**
     * If database does not support transactions, rollbacks always fail, so return false
     *
     * @return true/false.
     */
    function RollbackTrans() 
    { return false;}


    /**
     * return the databases that the driver can connect to. 
     * Some databases will return an empty array.
     *
     * @return an array of database names.
     */
        function MetaDatabases() 
        {return false;}
        
    /**
     * @return  array of tables for current database.
     */ 
    function MetaTables() 
    {
    global $ADODB_FETCH_MODE;
    
        if ($this->metaTablesSQL) {
            /*  complicated state saving by the need for backward compat */
            $save = $ADODB_FETCH_MODE; 
            $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 
            
            if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
            
            $rs = $this->Execute($this->metaTablesSQL);
            if (isset($savem)) $this->SetFetchMode($savem);
            $ADODB_FETCH_MODE = $save; 
            
            if ($rs === false) return false;
            $arr = $rs->GetArray();
            $arr2 = array();
            for ($i=0; $i < sizeof($arr); $i++) {
                $arr2[] = $arr[$i][0];
            }
            $rs->Close();
            return $arr2;
        }
        return false;
    }
    
    
    /**
     * List columns in a database as an array of ADOFieldObjects. 
     * See top of file for definition of object.
     *
     * @param table	table name to query
     * @param upper	uppercase table name (required by some databases)
     *
     * @return  array of ADOFieldObjects for current table.
     */ 
    function MetaColumns($table,$upper=true) 
    {
    global $ADODB_FETCH_MODE;
    
        if (!empty($this->metaColumnsSQL)) {
            $save = $ADODB_FETCH_MODE;
            $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
            if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
            $rs = $this->Execute(sprintf($this->metaColumnsSQL,($upper)?strtoupper($table):$table));
            if (isset($savem)) $this->SetFetchMode($savem);
            $ADODB_FETCH_MODE = $save;
            if ($rs === false) return false;

            $retarr = array();
            while (!$rs->EOF) { /* print_r($rs->fields); */
                $fld = new ADOFieldObject();
                $fld->name = $rs->fields[0];
                $fld->type = $rs->fields[1];
                $fld->max_length = $rs->fields[2];
                $retarr[strtoupper($fld->name)] = $fld;	
                
                $rs->MoveNext();
            }
            $rs->Close();
            return $retarr;	
        }
        return false;
    }
    
    /**
     * List columns names in a table as an array. 
     * @param table	table name to query
     *
     * @return  array of column names for current table.
     */ 
    function MetaColumnNames($table) 
    {
        $objarr = $this->MetaColumns($table);
        if (!is_array($objarr)) return false;
        
        $arr = array();
        foreach($objarr as $v) {
            $arr[] = $v->name;
        }
        return $arr;
    }
            
    /**
     * Different SQL databases used different methods to combine strings together.
     * This function provides a wrapper. 
     * 
     * param s  variable number of string parameters
     *
     * Usage: $db->Concat($str1,$str2);
     * 
     * @return concatenated string
     */
    function Concat()
    {	
        $arr = func_get_args();
        return implode($this->concat_operator, $arr);
    }
    
    
    /**
     * Converts a date "d" to a string that the database can understand.
     *
     * @param d	a date in Unix date time format.
     *
     * @return  date string in database date format
     */
    function DBDate($d)
    {
    
        if (empty($d) && $d !== 0) return 'null';

        if (is_string($d) && !is_numeric($d)) 
            if ($this->isoDates) return "'$d'";
            else $d = ADOConnection::UnixDate($d);
            
        return adodb_date($this->fmtDate,$d);
    }
    
    
    /**
     * Converts a timestamp "ts" to a string that the database can understand.
     *
     * @param ts    a timestamp in Unix date time format.
     *
     * @return  timestamp string in database timestamp format
     */
    function DBTimeStamp($ts)
    {
        if (empty($ts) && $ts !== 0) return 'null';

        if (is_string($ts) && !is_numeric($ts)) 
            if ($this->isoDates) return "'$ts'";
            else $ts = ADOConnection::UnixTimeStamp($ts);
            
        return adodb_date($this->fmtTimeStamp,$ts);
    }
    
    /**
     * Also in ADORecordSet.
     * @param $v is a date string in YYYY-MM-DD format
     *
     * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
     */
    function UnixDate($v)
    {
        if (!preg_match( "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})|", 
            ($v), $rr)) return false;

        if ($rr[1] <= TIMESTAMP_FIRST_YEAR) return 0;
        /*  h-m-s-MM-DD-YY */
        return @adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
    }
    

    /**
     * Also in ADORecordSet.
     * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
     *
     * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
     */
    function UnixTimeStamp($v)
    {
        if (!preg_match( 
            "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ -]?(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|", 
            ($v), $rr)) return false;
        if ($rr[1] <= TIMESTAMP_FIRST_YEAR && $rr[2]<= 1) return 0;
    
        /*  h-m-s-MM-DD-YY */
        if (!isset($rr[5])) return  adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
        return  @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1]);
    }
    
    /**
     * Also in ADORecordSet.
     *
     * Format database date based on user defined format.
     *
     * @param v is the character date in YYYY-MM-DD format, returned by database
     * @param fmt   is the format to apply to it, using date()
     *
     * @return a date formated as user desires
     */
     
    function UserDate($v,$fmt='Y-m-d')
    {
        $tt = $this->UnixDate($v);
        /*  $tt == -1 if pre TIMESTAMP_FIRST_YEAR */
        if (($tt === false || $tt == -1) && $v != false) return $v;
        else if ($tt == 0) return $this->emptyDate;
        else if ($tt == -1) { /*  pre-TIMESTAMP_FIRST_YEAR */
        }
        
        return adodb_date($fmt,$tt);
    
    }
    
    
    /**
     * Correctly quotes a string so that all strings are escaped. We prefix and append
     * to the string single-quotes.
     * An example is  $db->qstr("Don't bother",magic_quotes_runtime());
     * 
     * @param s         the string to quote
     * @param [magic_quotes]	if $s is GET/POST var, set to get_magic_quotes_gpc().
     *              This undoes the stupidity of magic quotes for GPC.
     *
     * @return  quoted string to be sent back to database
     */
    function qstr($s,$magic_quotes=false)
    {	
        if (!$magic_quotes) {
        
            if ($this->replaceQuote[0] == '\\'){
                /*  only since php 4.0.5 */
                $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s);
                /* $s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s)); */
            }
            return  "'".str_replace("'",$this->replaceQuote,$s)."'";
        }
        
        /*  undo magic quotes for " */
        $s = str_replace('\\"','"',$s);
        
        if ($this->replaceQuote == "\\'")  /*  ' already quoted, no need to change anything */
            return "'$s'";
        else {/*  change \' to '' for sybase/mssql */
            $s = str_replace('\\\\','\\',$s);
            return "'".str_replace("\\'",$this->replaceQuote,$s)."'";
        }
    }
    
    
    /**
    * Will select the supplied $page number from a recordset, given that it is paginated in pages of 
    * $nrows rows per page. It also saves two boolean values saying if the given page is the first 
    * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
    *
    * See readme.htm#ex8 for an example of usage.
    *
    * @param sql
    * @param nrows      is the number of rows per page to get
    * @param page       is the page number to get (1-based)
    * @param [inputarr]	array of bind variables
    * @param [arg3]     is a private parameter only used by jlim
    * @param [secs2cache]       is a private parameter only used by jlim
    * @return       the recordset ($rs->databaseType == 'array')
    *
    * NOTE: phpLens uses a different algorithm and does not use PageExecute().
    *
    */
    function &PageExecute($sql, $nrows, $page, $inputarr=false, $arg3=false, $secs2cache=0) 
    {
        include_once(ADODB_DIR.'/adodb-lib.inc.php');
        if ($this->pageExecuteCountRows) return _adodb_pageexecute_all_rows($this, $sql, $nrows, $page, $inputarr, $arg3, $secs2cache);
        return _adodb_pageexecute_no_last_page($this, $sql, $nrows, $page, $inputarr, $arg3, $secs2cache);

    }
    
        
    /**
    * Will select the supplied $page number from a recordset, given that it is paginated in pages of 
    * $nrows rows per page. It also saves two boolean values saying if the given page is the first 
    * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
    *
    * @param secs2cache	seconds to cache data, set to 0 to force query
    * @param sql
    * @param nrows      is the number of rows per page to get
    * @param page       is the page number to get (1-based)
    * @param [inputarr]	array of bind variables
    * @param [arg3]     is a private parameter only used by jlim
    * @return       the recordset ($rs->databaseType == 'array')
    */
    function &CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false, $arg3=false) 
    {
        /*switch($this->dataProvider) {
        case 'postgres':
        case 'mysql': 
            break;
        default: $secs2cache = 0; break;
        }*/
        return $this->PageExecute($sql,$nrows,$page,$inputarr,$arg3,$secs2cache);
    }

} /*  end class ADOConnection */

?>