<?php
/**
 * IBM DB2 Native Client driver.
 *
 * Originally DB2 drivers were dependent on an ODBC driver, and some installations
 * may still use that. To use an ODBC driver connection, use the odbc_db2
 * ADOdb driver. For Linux, you need the 'ibm_db2' PECL extension for PHP,
 * For Windows, you need to locate an appropriate version of the php_ibm_db2.dll,
 * as well as the IBM data server client software.
 * This is basically a full rewrite of the original driver, for information
 * about all the changes, see the update information on the ADOdb website
 * for version 5.21.0.
 *
 * @link https://pecl.php.net/package/ibm_db2 PECL Extension For DB2
 *
 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
 *
 * @package ADOdb
 * @link https://adodb.org Project's web site and documentation
 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
 *
 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
 * any later version. This means you can use it in proprietary products.
 * See the LICENSE.md file distributed with this source code for details.
 * @license BSD-3-Clause
 * @license LGPL-2.1-or-later
 *
 * @copyright 2000-2013 John Lim
 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
 * @author Mark Newnham
 */

// security - hide paths
if (!defined('ADODB_DIR')) die();

define("_ADODB_DB2_LAYER", 2 );


class ADODB_db2 extends ADOConnection {
    var $databaseType = "db2";
    var $fmtDate = "'Y-m-d'";
    var $concat_operator = '||';

    var $sysTime = 'CURRENT TIME';
    var $sysDate = 'CURRENT DATE';
    var $sysTimeStamp = 'CURRENT TIMESTAMP';

    var $fmtTimeStamp = "'Y-m-d H:i:s'";
    var $replaceQuote = "''"; // string to use to replace quotes
    var $dataProvider = "db2";
    var $hasAffectedRows = true;

    var $binmode = DB2_BINARY;

    /*
    * setting this to true will make array elements in FETCH_ASSOC
    * mode case-sensitive breaking backward-compat
    */
    var $useFetchArray = false;
    var $_bindInputArray = true;
    var $_genIDSQL = "VALUES NEXTVAL FOR %s";

    /**
     * SQL to create a new sequence
     *
     * @var string
     */
    var $_genSeqSQL = "
    CREATE SEQUENCE %s START WITH %s
    NO MAXVALUE NO CYCLE INCREMENT BY 1 NO CACHE
    ";

    /**
     * SQL to drop a sequence
     *
     * @var string
     */
    var $_dropSeqSQL = "DROP SEQUENCE %s";

    var $_autocommit = true;
    var $_lastAffectedRows = 0;
    var $hasInsertID = true;
    var $hasGenID    = true;
    /*
     * Character used to wrap column and table names for escaping special
     * characters in column and table names as well as forcing upper and
     * lower case
     */
    public $nameQuote = '"';

    /*
     * Holds information about the stored procedure request
     * currently being built
     */
    private $storedProcedureParameters = false;


    /**
     * Supports older DB2 versions without record counts
     *
     * @var boolean
     */
    public bool $useLegacyRecordCounting = false;

    /**
     * Supports older DB2 versions without 'OFFSET' support
     *
     * @var boolean
     */
    public bool $useLegacyRowOffsets = false;

    function __construct() {}

    protected function _insertID($table = '', $column = '')
    {
        return ADOConnection::GetOne('VALUES IDENTITY_VAL_LOCAL()');
    }

    public function _connect($argDSN, $argUsername, $argPassword, $argDatabasename)
    {
        return $this->doDB2Connect($argDSN, $argUsername, $argPassword, $argDatabasename);
    }

    public function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
    {
        return $this->doDB2Connect($argDSN, $argUsername, $argPassword, $argDatabasename,true);
    }

    private function doDB2Connect($argDSN, $argUsername, $argPassword, $argDatabasename, $persistent=false)
    {

        if (!function_exists('db2_connect')) {
            ADOConnection::outp("DB2 extension not installed.");
            return null;
        }

        $connectionParameters = $this->unpackParameters(
            $argDSN,
            $argUsername,
            $argPassword,
            $argDatabasename
        );

        if ($connectionParameters == null)
        {
            /*
             * Error thrown
             */
            return null;
        }

        $argDSN 		         = $connectionParameters['dsn'];
        $argUsername 	         = $connectionParameters['uid'];
        $argPassword 	         = $connectionParameters['pwd'];
        $argDatabasename         = $connectionParameters['database'];
        $useCataloguedConnection = $connectionParameters['catalogue'];

        if ($this->debug){
            if (strcmp($argDSN,'*LOCAL') == 0)
            {
                $connectMessage = '*LOCAL connection';
            }
            else if ($useCataloguedConnection)
            {
                $connectMessage = "Catalogued connection using parameters: ";
                $connectMessage .= "DB=$argDatabasename / ";
                $connectMessage .= "UID=$argUsername / ";
                $connectMessage .= "PWD=$argPassword";
            }
            else
            {
                $connectMessage = "Uncatalogued connection using DSN: $argDSN";
            }
            ADOConnection::outp($connectMessage);
        }

        /*
         * This needs to be set before the connect().
         */
        ini_set('ibm_db2.binmode', $this->binmode);

        if ($persistent)
            $db2Function = 'db2_pconnect';
        else
            $db2Function = 'db2_connect';

        /*
        * We need to flatten out the connectionParameters
        */

        $db2Options = array();
        if ($this->connectionParameters)
        {
            foreach($this->connectionParameters as $p)
                foreach($p as $k=>$v)
                    $db2Options[$k] = $v;
        }

        if ($useCataloguedConnection)
        {
            $this->_connectionID = $db2Function($argDatabasename,
                                                $argUsername,
                                                $argPassword,
                                                $db2Options);
        }
        else

            $this->_connectionID = $db2Function($argDSN,
                                                '',
                                                '',
                                                $db2Options);


        $this->_errorMsg = @db2_conn_errormsg();

        if ($this->_connectionID && $this->connectStmt)
            $this->execute($this->connectStmt);

        if ($this->_connectionID && $argDatabasename)
            $this->execute("SET SCHEMA=$argDatabasename");

        return $this->_connectionID != false;

    }

    /**
     * Validates and preprocesses the passed parameters for consistency
     *
     * @param	string	$argDSN				Either DSN or database
     * @param	string	$argUsername		User name or null
     * @param	string	$argPassword		Password or null
     * @param	string	$argDatabasename	Either DSN or database
     *
     * @return mixed  array if correct, null if not
     */
    private function unpackParameters($argDSN, $argUsername, $argPassword, $argDatabasename)
    {


        $connectionParameters = array(
            'dsn'=>'',
            'uid'=>'',
            'pwd'=>'',
            'database'=>'',
            'catalogue'=>true
        );

        /*
        * Shortcut for *LOCAL
        */
        if (strcmp($argDSN,'*LOCAL') == 0)
        {
            $connectionParameters['dsn']      = $argDSN;
            $connectionParameters['database'] = $argDatabasename;
            $connectionParameters['catalogue'] = false;

            return $connectionParameters;
        }

        /*
         * Uou can either connect to a catalogued connection
         * with a database name e.g. 'SAMPLE'
         * or an uncatalogued connection with a DSN like connection
         * DATABASE=database;HOSTNAME=hostname;PORT=port;PROTOCOL=TCPIP;UID=username;PWD=password;
         */

        if (!$argDSN && !$argDatabasename)
        {
            $errorMessage = 'Supply either catalogued or uncatalogued connection parameters';
            $this->_errorMsg = $errorMessage;
            if ($this->debug)
                ADOConnection::outp($errorMessage);
            return null;
        }

        $useCataloguedConnection = true;
        $schemaName 			 = '';

        if ($argDSN && $argDatabasename)
        {
            /*
             * If a catalogued connection if provided,
             * as well as user and password
             * that will take priority
             */
            if ($argUsername && $argPassword && !$this->isDsn($argDatabasename))
            {
                if ($this->debug){
                    $errorMessage = 'Warning: Because you provided user,';
                    $errorMessage.= 'password and database, DSN connection ';
                    $errorMessage.= 'parameters were discarded';
                    ADOConnection::outp($errorMessage);

                }
                $argDSN = '';
            }
            else if ($this->isDsn($argDSN) && $this->isDsn($argDatabasename))
            {
                $errorMessage = 'Supply uncatalogued connection parameters ';
                $errorMessage.= 'in either the database or DSN arguments, ';
                $errorMessage.= 'but not both';

                if ($this->debug)
                    ADOConnection::outp($errorMessage);
                return null;
            }
        }

        if (!$this->isDsn($argDSN) && $this->isDsn($argDatabasename))
        {
            /*
             * Switch them around for next test
             */
            $temp           = $argDSN;
            $argDsn         = $argDatabasename;
            $argDatabasenME = $temp;
        }

        if ($this->isDsn($argDSN))
        {

            if (!preg_match('/uid=/i',$argDSN)
            ||  !preg_match('/pwd=/i',$argDSN))
            {
                $errorMessage = 'For uncatalogued connections, provide ';
                $errorMessage.= 'both UID and PWD in the connection string';

                if ($this->debug)
                    ADOConnection::outp($errorMessage);
                return null;
            }

            if (preg_match('/database=/i',$argDSN))
            {
                if ($argDatabasename)
                {
                    $argDatabasename = '';
                    if ($this->debug)
                    {
                        $errorMessage = 'Warning: Because you provided ';
                        $errorMessage.= 'database information in the DSN ';
                        $errorMessage.= 'parameters, the supplied database ';
                        $errorMessage.= 'name was discarded';
                        ADOConnection::outp($errorMessage);
                    }
                }
                $useCataloguedConnection = false;

            }
            elseif ($argDatabasename)
            {
                $this->database = $argDatabasename;
                $argDSN .= ';database=' . $argDatabasename;
                $argDatabasename = '';
                $useCataloguedConnection = false;

            }
            else
            {
                $errorMessage = 'Uncatalogued connection parameters ';
                $errorMessage.= 'must contain a database= argument';

                if ($this->debug)
                    ADOConnection::outp($errorMessage);
                return null;
            }
        }

        if ($argDSN && !$argDatabasename && $useCataloguedConnection)
        {
            $argDatabasename = $argDSN;
            $argDSN          = '';
        }


        if ($useCataloguedConnection
        && (!$argDatabasename
        || !$argUsername
        || !$argPassword))
        {

            $errorMessage = 'For catalogued connections, provide ';
            $errorMessage.= 'database, username and password';
            $this->_errorMsg = $errorMessage;
            if ($this->debug)
                ADOConnection::outp($errorMessage);
            return null;

        }

        if ($argDatabasename)
            $this->database = $argDatabasename;
        elseif (!$this->database)
            $this->database = $this->getDatabasenameFromDsn($argDSN);


        $connectionParameters = array('dsn'=>$argDSN,
                                      'uid'=>$argUsername,
                                      'pwd'=>$argPassword,
                                      'database'=>$argDatabasename,
                                      'catalogue'=>$useCataloguedConnection
                                      );

        return $connectionParameters;

    }

    /**
      * Does the provided string look like a DSN
      *
      * @param	string	$dsnString
      *
      * @return bool
      */
    private function isDsn($dsnString){
        $dsnArray = preg_split('/[;=]+/',$dsnString);
        if (count($dsnArray) > 2)
            return true;
        return false;
    }


    /**
      * Gets the database name from the DSN
      *
      * @param	string	$dsnString
      *
      * @return string
      */
    private function getDatabasenameFromDsn($dsnString){

        $dsnArray = preg_split('/[;=]+/',$dsnString);
        $dbIndex  = array_search('database',$dsnArray);

        return $dsnArray[$dbIndex + 1];
    }


    /**
    * format and return date string in database timestamp format
    *
    * @param	mixed	$ts		either a string or a unixtime
    * @param	bool	$isField	discarded
    *
    * @return string
    */
    function dbTimeStamp($ts,$isField=false)
    {
        if (empty($ts) && $ts !== 0) return 'null';
        if (is_string($ts)) $ts = ADORecordSet::unixTimeStamp($ts);
        return 'TO_DATE('.date($this->fmtTimeStamp,$ts).",'YYYY-MM-DD HH24:MI:SS')";
    }

    /**
    * Format date column in sql string given an input format that understands Y M D
    *
    * @param	string	$fmt The format string e.g. 'm/d/Y'
    * @param	string	$col optionally either a column name,an ISO date string or a
    *                        ate geerating DB2 function like CURRENT DATE
    *
    * @return string
    */
    public function sqlDate($fmt, $col=false)
    {
        $usesSysDate = false;
        $trailer = '';
        $dCol    = '';
        $tCol    = '';
        if (!$col) {
            /*
             * If no column is provided, we use the system date or time
             */

            $dCol = $this->sysDate;
            $tCol = $this->sysTime;

            /*
            * Anything not from a column needs this
            */
            $trailer = 'FROM SYSIBM.SYSDUMMY1';

        } else if (preg_match('/(\d{4})-(\d{2})-(\d{2})/',$col)) {
            /*
             * If the column is an ISO date string, we use it as is
             */
            $trailer = 'FROM SYSIBM.SYSDUMMY1';
            if (substr($col,0,1) != "'") {
                $col = $this->qstr($col);
            }
        }

        /* use TO_CHAR() if $fmt is TO_CHAR() allowed fmt */
        if ($fmt== 'Y-m-d H:i:s')
            return 'TO_CHAR('.$col.", 'YYYY-MM-DD HH24:MI:SS')";

        $s = '';

        $len = strlen($fmt);
        for ($i=0; $i < $len; $i++) {
            if ($s) {
                $s .= $this->concat_operator;
            }

            $ch = $fmt[$i];

            switch($ch) {
            case 'Y':
            case 'y':

                if ($len==1) {
                    return "YEAR($col$dCol) $trailer";
                }

                /*
                * the CHAR creates a CHAR(20) so trim it
                */
                $s .= "RTRIM(CHAR(YEAR($col$dCol)))";

                break;

            case 'M':

                /*
                * Returns "January" we want "Jan"
                */
                $cmd = "SUBSTR(MONTHNAME($col$dCol),1,3)";

                if ($len==1) {
                     return "$cmd $trailer";
                }
                $s .= $cmd;

                break;

            case 'm':

                $cmd = "RIGHT(DIGITS(MONTH($col$dCol)),2)";
                if ($len==1) {
                    return "$cmd $trailer";
                }

                $s .= $cmd;
                break;

            case 'D':
            case 'd':

                $cmd = "RIGHT(DIGITS(DAY($col$dCol)),2)";

                if ($len==1) {
                    return "$cmd $trailer";
                }

                $s .= $cmd;
                break;

            case 'H':
            case 'h':

                $cmd = "RIGHT(DIGITS(HOUR($col$tCol)),2)";
                if ($len==1) {
                    return "$cmd $trailer";
                }

                $s .= $cmd;

                break;

            case 'i':
            case 'I':

                $cmd = "RIGHT(DIGITS(MINUTE($col$tCol)),2)";
                if ($len==1) {
                    return "$cmd $trailer";
                }

                $s .= $cmd;

                break;

            case 'S':
            case 's':
                $cmd = "RIGHT(DIGITS(SECOND($col$tCol)),2)";
                if ($len==1) {
                    return "$cmd $trailer";
                }

                $s .= $cmd;
                break;

            default:

                if ($ch == '\\') {
                    $i++;
                    $ch = substr($fmt, $i, 1);
                }

                $s .= $this->qstr($ch);
            }
        }

        $s .= " $trailer";

        return $s;
    }

    /**
     * Return information about the database server
     *
     * @return array
     */
    public function serverInfo()
    {
        global $ADODB_FETCH_MODE;
        $savem = $ADODB_FETCH_MODE;

        $this->setFetchMode(ADODB_FETCH_NUM);

        $sql = "SELECT service_level, fixpack_num
                  FROM TABLE(sysproc.env_get_inst_info())
                    AS INSTANCEINFO";
        $row = $this->GetRow($sql);

        $this->setFetchMode($savem);

        if ($row) {
            $info['version'] = $row[0].':'.$row[1];
            $info['fixpack'] = $row[1];
            $info['description'] = '';
        } else {
            return ADOConnection::serverInfo();
        }

        return $info;
    }

    /**
     * Select a limited number of rows.
     *
     * @param string     $sql        The SQL to execute
     * @param int        $nrows      Number of rows to get
     * @param int        $offset     Row to start calculations from (1-based)
     * @param array|bool $inputArr   Array of bind variables
     * @param int        $secs2cache Private parameter only used by jlim
     *
     * @return ADORecordSet The recordset ($rs->databaseType == 'array')
     */
    function selectLimit($sql, $nrows=-1, $offset=-1, $inputArr=false, $secs2cache=0)
    {
        $nrows = (int) $nrows;

        if ($this->useLegacyRowOffsets) {
            /*
            * Disables support for OFFSET
            */
            if ($offset <= 0) {
                if ($nrows >= 0)
                    $sql .=  " FETCH FIRST $nrows ROWS ONLY ";

                $rs = $this->execute($sql, $inputArr);

            } else {

                if ($offset > 0 && $nrows < 0) {

                } else {
                    $nrows += $offset;
                    $sql .=  " FETCH FIRST $nrows ROWS ONLY ";
                }

                /*
                * Disabled DB2 support for mid table offset
                */
                $rs = ADOConnection::selectLimit($sql, $nrows, $offset, $inputArr);

            }
        } else {

            $rowString    = '';
            $offsetString = '';

            if ($nrows > 0) {
                $rowString = sprintf('FETCH FIRST %d ROWS ONLY', $nrows);
            }

            if ($offset > 0) {
                $offsetString = sprintf('OFFSET %d ROWS', $offset);
            }

            $sql = sprintf('%s %s %s', $sql, $offsetString, $rowString);
            if ($secs2cache) {
                $rs = $this->cacheExecute($secs2cache, $sql, $inputArr);
            } else {
                $rs = $this->execute($sql, $inputArr);
            }
        }

        return $rs;
    }


    function errorMsg()
    {
        if ($this->_errorMsg !== false)
            return $this->_errorMsg;

        if (empty($this->_connectionID))
            return @db2_conn_errormsg();

        return @db2_conn_errormsg($this->_connectionID);
    }

    function errorNo()
    {

        if ($this->_errorCode !== false)
            return $this->_errorCode;


        if (empty($this->_connectionID))
            $e = @db2_conn_error();

        else
            $e = @db2_conn_error($this->_connectionID);

        return $e;
    }



    function beginTrans()
    {
        if (!$this->hasTransactions)
            return false;
        if ($this->transOff)
            return true;

        $this->transCnt += 1;

        $this->_autocommit = false;

        return db2_autocommit($this->_connectionID,false);
    }

    function CommitTrans($ok=true)
    {
        if ($this->transOff)
            return true;

        if (!$ok)
            return $this->RollbackTrans();

        if ($this->transCnt)
            $this->transCnt -= 1;

        $this->_autocommit = true;
        $ret = @db2_commit($this->_connectionID);
        @db2_autocommit($this->_connectionID,true);
        return $ret;
    }

    function RollbackTrans()
    {
        if ($this->transOff) return true;
        if ($this->transCnt) $this->transCnt -= 1;
        $this->_autocommit = true;
        $ret = @db2_rollback($this->_connectionID);
        @db2_autocommit($this->_connectionID,true);
        return $ret;
    }

    /**
     * Return a list of Primary Keys for a specified table
     *
     * We don't use db2_statistics as the function does not seem to play
     * well with mixed case table names
     *
     * @param string   $table
     * @param bool     $primary    (optional) only return primary keys
     * @param bool     $owner      (optional) not used in this driver
     *
     * @return string[]    Array of indexes
     */
    public function metaPrimaryKeys($table,$owner=false)
    {

        $primaryKeys = array();

        global $ADODB_FETCH_MODE;

        $schema = '';
        $this->_findschema($table,$schema);

        $table = $this->metaTables('T','',$table);
        if ($table == false)
            return false;

        $table = $table[0];

        $savem 			  = $ADODB_FETCH_MODE;
        $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
        $this->setFetchMode(ADODB_FETCH_NUM);


        $sql = "SELECT *
                  FROM SYSCAT.INDEXES
                 WHERE TABNAME='$table'";

        $rows = $this->getAll($sql);

        $this->setFetchMode($savem);
        $ADODB_FETCH_MODE = $savem;

        if (empty($rows))
            return false;

        foreach ($rows as $r)
        {
            if ($r[7] != 'P')
                continue;

            $cols = explode('+',$r[6]);
            foreach ($cols as $colIndex=>$col)
            {
                if ($colIndex == 0)
                    continue;
                $columnName = $this->getMetaCasedValue($col);
                $primaryKeys[] = $columnName;
            }
            break;
        }
        return $primaryKeys;
    }

    /**
     * Returns a list of Foreign Keys associated with a specific table.
     *
     * @param string $table
     * @param string $owner       discarded
     * @param bool   $upper       discarded
     * @param bool   $associative discarded
     *
     * @return string[]|false An array where keys are tables, and values are foreign keys;
     *                        false if no foreign keys could be found.
     */
    public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
    {

        global $ADODB_FETCH_MODE;

        $schema = '';
        $this->_findschema($table,$schema);

        $metaTables = $this->metaTables('T','',$table);

        if ($metaTables == false) {
            return false;
        }

        $table = $metaTables[0];

        $baseFetchMode = $ADODB_FETCH_MODE;

        $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;

        $this->setFetchMode(ADODB_FETCH_ASSOC);
        $sql = "SELECT *
                 FROM syscat.references
                WHERE tabname = '$table'";

        $results = $this->getAll($sql);

        $this->setFetchMode($baseFetchMode);

        if (empty($results))
            return false;

        $foreignKeys = array();

        foreach ($results as $r)
        {

            /*

            [CONSTNAME] => SQL250829011849680
            [TABSCHEMA] => DB2INST1
            [TABNAME] => TESTTABLE_2
            [OWNER] => DB2INST1
            [OWNERTYPE] => U
            [REFKEYNAME] => SQL250829011847480
            [REFTABSCHEMA] => DB2INST1
            [REFTABNAME] => TESTTABLE_1
            [COLCOUNT] => 1
            [DELETERULE] => C
            [UPDATERULE] => A
            [CREATE_TIME] => 2025-08-29 01:18:49.717630
            [FK_COLNAMES] =>  TT_ID
            [PK_COLNAMES] =>  ID
            [DEFINER] => DB2INST1
        )
            */
            $referenceTable = trim($r['REFTABNAME']);

            if (!array_key_exists($referenceTable,$foreignKeys)) {
                $foreignKeys[$referenceTable] = array();
            }
            $pkColnames = array_filter(preg_split('/ +/', $r['PK_COLNAMES']));
            $fkColnames = array_filter(preg_split('/ +/', $r['FK_COLNAMES']));

            foreach ($pkColnames as $i=>$pkColname) {

                $pkColname = trim($pkColname);
                $fkColname = trim($fkColnames[$i]);
                if ($baseFetchMode == ADODB_FETCH_ASSOC) {
                    $foreignKeys[$referenceTable][$pkColname] = $fkColname;
                } else {
                    $foreignKeys[$referenceTable][] = sprintf('%s=%s',$pkColname,$fkColname);
                }

            }

        }
        return $foreignKeys;
    }

    /**
     * Returns a list of tables
     *
     * @param string	$ttype (optional)
     * @param	string	$schema	(optional)
     * @param	string	$mask	(optional)
     *
     * @return array
     */
    public function metaTables($ttype=false,$schema=false,$mask=false)
    {

        global $ADODB_FETCH_MODE;

        $savem = $ADODB_FETCH_MODE;
        $this->SetFetchMode(ADODB_FETCH_ASSOC);

        /*
        * Values for TABLE_TYPE
        * ---------------------------
        * ALIAS, HIERARCHY TABLE, INOPERATIVE VIEW, NICKNAME,
        * MATERIALIZED QUERY TABLE, SYSTEM TABLE, TABLE,
        * TYPED TABLE, TYPED VIEW, and VIEW
        *
        * If $ttype passed as '', match 'TABLE' and 'VIEW'
        * If $ttype passed as 'T' it is assumed to be 'TABLE'
        * if $ttype passed as 'V' it is assumed to be 'VIEW'
        */

        $sqlFilters = array();
        $sqlFilter = '';
        $returnedColumn = 'TRIM(TABNAME)';

        if ($ttype) {

            /*
            * We only support 'T' for TABLE and 'V' for VIEW
            * All types are returned if $ttype is passed as ''
            */
            $ttype = strtoupper(substr($ttype,0,1));

            if ($ttype == 'V') {
                $sqlFilters[] = "TYPE='V'";
            } else if ($ttype == 'T') {
                $sqlFilters[] = "TYPE='T'";
            }
        }

        if ($schema && $schema != '%') {

            $schema = strtoupper($schema);
            $returnedColumn = $this->concat('TRIM(TABSCHEMA)',"'.'",'TRIM(TABNAME)');

            if (strpos($schema,'%') !== false) {
                $sqlFilters[] = "UPPER(TABSCHEMA) LIKE '$schema'";
            } else {
                $sqlFilters[] = "UPPER(TABSCHEMA)='" . strtoupper($schema) . "'";
            }
        }

        if ($mask) {
            /*
            * Mask is SQL LIKE format. We match on upper case
            * unless the match is quoted in which case we match
            * exactly
            */

            if (in_array(substr($mask,0,1),array('"','[')))
            {
                if (strpos($mask,'%') !== false)
                {
                    $sqlFilters[] = "TABNAME LIKE '$mask'";
                } else {
                    $sqlFilters[] = "TABNAME='$mask'";
                }
            } else {
                $mask = strtoupper($mask);
                if (strpos($mask,'%') !== false) {
                    $sqlFilters[] = "UPPER(TABNAME) LIKE '$mask'";
                } else {
                    $sqlFilters[] = "UPPER(TABNAME)='$mask'";
                }
            }
        }

        if (count($sqlFilters) > 0) {
            $sqlFilter = 'WHERE ' . implode(' AND ',$sqlFilters);
        }

        $sql = "SELECT $returnedColumn FROM SYSCAT.TABLES $sqlFilter ORDER BY TABSCHEMA, TABNAME";


        $metaTables = $this->getCol($sql);

        $this->SetFetchMode($savem);

        if (count($metaTables) == 0)
            return false;

        return $metaTables;
    }

    /**
      * Return a list of indexes for a specified table
      *
      * We don't use db2_statistics as the function does not seem to play
      * well with mixed case table names
      *
      * @param string   $table
      * @param bool     $primary    (optional) only return primary keys
      * @param bool     $owner      (optional) not used in this driver
      *
      * @return string[]    Array of indexes
      */
    public function metaIndexes($table, $primary = false, $owner = false) {

        global $ADODB_FETCH_MODE;

         /* Array(
         *   [name_of_index] => Array(
         *     [unique] => true or false
         *     [columns] => Array(
         *       [0] => firstcol
         *       [1] => nextcol
         *       [2] => etc........
         *     )
         *   )
         * )
         */
        $indices 		= array();
        $primaryKeyName = '';

        $table = $this->getTableCasedValue($table);


        $savem 			  = $ADODB_FETCH_MODE;
        $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
        $this->setFetchMode(ADODB_FETCH_NUM);

        $sql = "SELECT *
                  FROM syscat.indexes
                 WHERE tabname='$table'";

        $rows = $this->getAll($sql);

        $this->setFetchMode($savem);
        $ADODB_FETCH_MODE = $savem;

        if (empty($rows))
            return false;

        foreach ($rows as $r)
        {

            $primaryIndex = $r[7] == 'P'?1:0;
            if (!$primary)
                /*
                 * Primary key not requested, ignore that one
                 */
                if ($r[7] == 'P')
                    continue;

            $indexName = $this->getMetaCasedValue($r[1]);
            if (!isset($indices[$indexName]))
            {
                $unique = ($r[7] == 'U')?1:0;
                $indices[$indexName] = array('unique'=>$unique,
                                             'primary'=>$primaryIndex,
                                             'columns'=>array()
                                        );
            }
            $cols = explode('+',$r[6]);
            foreach ($cols as $colIndex=>$col)
            {
                if ($colIndex == 0)
                    continue;
                $columnName = $this->getMetaCasedValue($col);
                $indices[$indexName]['columns'][] = $columnName;
            }

        }

        return $indices;

    }

    /**
     * List procedures or functions in an array.
     *
     * We interrogate syscat.routines instead of calling the PHP
     * function procedures because ADOdb requires the type of procedure
     * this is not available in the php function
     *
     * @param	string $procedureNamePattern (optional)
     * @param	string $catalog				 (optional)
     * @param	string $schemaPattern		 (optional)

     * @return array of procedures on current database.
     *
     */
    public function metaProcedures($procedureNamePattern = null, $catalog  = null, $schemaPattern  = null) {


        global $ADODB_FETCH_MODE;

        $metaProcedures = array();
        $procedureSQL   = '';
        $catalogSQL     = '';
        $schemaSQL      = '';

        $savem 			  = $ADODB_FETCH_MODE;
        $ADODB_FETCH_MODE = ADODB_FETCH_NUM;

        if ($procedureNamePattern)
            $procedureSQL = "AND ROUTINENAME LIKE " . strtoupper($this->qstr($procedureNamePattern));

        if ($catalog)
            $catalogSQL = "AND OWNER=" . strtoupper($this->qstr($catalog));

        if ($schemaPattern)
            $schemaSQL = "AND ROUTINESCHEMA LIKE {$this->qstr($schemaPattern)}";


        $fields = "
        ROUTINENAME,
        CASE ROUTINETYPE
             WHEN 'P' THEN 'PROCEDURE'
             WHEN 'F' THEN 'FUNCTION'
             ELSE 'METHOD'
             END AS ROUTINETYPE_NAME,
        ROUTINESCHEMA,
        REMARKS";

        $SQL = "SELECT $fields
                  FROM syscat.routines
                 WHERE OWNER IS NOT NULL
                  $procedureSQL
                  $catalogSQL
                  $schemaSQL
                ORDER BY ROUTINENAME
                ";

        $result = $this->execute($SQL);

        $ADODB_FETCH_MODE = $savem;

        if (!$result)
            return false;

        while ($r = $result->fetchRow()){
            $procedureName = $this->getMetaCasedValue($r[0]);
            $schemaName    = $this->getMetaCasedValue($r[2]);
            $metaProcedures[$procedureName] = array('type'=> $r[1],
                                                   'catalog' => '',
                                                   'schema'  => $schemaName,
                                                   'remarks' => $r[3]
                                                    );
        }

        return $metaProcedures;

    }

    /**
      * Lists databases. Because instances are independent, we only know about
      * the current database name
      *
      * @return string[]
      */
    public function metaDatabases(){

        $dbName = $this->getMetaCasedValue($this->database);

        return (array)$dbName;

    }




/*
See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2datetime_data_type_changes.asp
/ SQL data type codes /
#define	SQL_UNKNOWN_TYPE	0
#define SQL_CHAR			1
#define SQL_NUMERIC		 2
#define SQL_DECIMAL		 3
#define SQL_INTEGER		 4
#define SQL_SMALLINT		5
#define SQL_FLOAT		   6
#define SQL_REAL			7
#define SQL_DOUBLE		  8
#if (DB2VER >= 0x0300)
#define SQL_DATETIME		9
#endif
#define SQL_VARCHAR		12


/ One-parameter shortcuts for date/time data types /
#if (DB2VER >= 0x0300)
#define SQL_TYPE_DATE	  91
#define SQL_TYPE_TIME	  92
#define SQL_TYPE_TIMESTAMP 93

#define SQL_UNICODE                             (-95)
#define SQL_UNICODE_VARCHAR                     (-96)
#define SQL_UNICODE_LONGVARCHAR                 (-97)
*/
    function DB2Types($t)
    {
        switch ((int)$t) {
        case 1:
        case 12:
        case 0:
        case -95:
        case -96:
            return 'C';
        case -97:
        case -1: //text
            return 'X';
        case -4: //image
        case -98: //blob
            return 'B';

        case 9:
        case 91:
            return 'D';

        case 10:
        case 11:
        case 92:
        case 93:
            return 'T';

        case 4: //BIGINT
            return 'I';
        case -6:
            return 'I8';

        case -11: // uniqidentifier
            return 'R';
        case 5: //Logical I2
        case -7: //bit
            return 'L';

        case 3:
            return "N";
        case -5:
            return "I4";

        default:
            
            return ADODB_DEFAULT_METATYPE;
        }
    }

    public function metaColumns($table, $ignored=true)
    {
        global $ADODB_FETCH_MODE;

        $schema = '%';
        $this->_findschema($table, $schema);


        $table = $this->getTableCasedValue($table);

        $findMatchingTable = $this->metaTables('T', '', $table);


        if ($findMatchingTable === false)
            return false;

        $table = $findMatchingTable[0];

        $savem = $ADODB_FETCH_MODE;
        $this->setFetchMode(ADODB_FETCH_NUM);
        $ADODB_FETCH_MODE = ADODB_FETCH_NUM;

        /*
        * The returned table name has the correct casing
        */

        //$table = $this->getTableCasedValue($table);
        $colname = "%";
        $qid = db2_columns($this->_connectionID, null, $schema, $table, $colname);
        if (empty($qid))
        {
            if ($this->debug)
            {
                $errorMessage = @db2_conn_errormsg($this->_connectionID);
                ADOConnection::outp($errorMessage);
            }
            return false;
        }

        $rs = new ADORecordSet_db2($qid);

        if (!$rs) {
            print "NO RECORDSET\n";
            $this->setFetchMode($savem);
            return false;
        
        }
        $rs->_fetch();

        $retarr = array();

        /*
        $rs->fields indices
        0 TABLE_QUALIFIER
        1 TABLE_SCHEM
        2 TABLE_NAME
        3 COLUMN_NAME
        4 DATA_TYPE
        5 TYPE_NAME
        6 PRECISION
        7 LENGTH
        8 SCALE
        9 RADIX
        10 NULLABLE
        11 REMARKS
        12 Column Default
        13 SQL Data Type
        14 SQL DateTime SubType
        15 Max length in Octets
        16 Ordinal Position
        17 Is NULLABLE
        */
        while (!$rs->EOF)
        {

           
            if ($rs->fields[2] == $table)
            {

                $fld       = new ADOFieldObject();
                $fld->name = $rs->fields[3];
                $fld->type = $this->DB2Types($rs->fields[4]);

                // ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp
                // access uses precision to store length for char/varchar

                if ($fld->type == 'C' or $fld->type == 'X') {
                    if ($rs->fields[4] <= -95) // UNICODE
                        $fld->max_length = $rs->fields[7]/2;
                    else
                        $fld->max_length = $rs->fields[7];
                } else
                    $fld->max_length = $rs->fields[7];

                $fld->not_null         = !empty($rs->fields[10]);
                $fld->scale            = $rs->fields[8];
                $fld->primary_key      = 0;

                $fld->actualType = $rs->fields[4];
                //$columnName = $this->getMetaCasedValue($fld->name);
                $columnName = strtoupper($fld->name);
                $retarr[$columnName] = $fld;

            }
            else if (sizeof($retarr)>0)
                break;

            $rs->MoveNext();

        }

        $sql = "SELECT COLNAME
                  FROM SYSCAT.COLUMNS
                 WHERE TABNAME='$table'
                   AND IDENTITY = 'Y'";
    
        $identityColumns = $this->getCol($sql);

        foreach ($identityColumns as $identityColumn) {
            $identityColumn = strtoupper($identityColumn);
            if (isset($retarr[$identityColumn])) {
                $retarr[$identityColumn]->auto_increment = true;
            }
        }

        $this->setFetchMode($savem);

        $rs->Close();
        if (empty($retarr))
            $retarr = false;


        $this->setFetchMode(ADODB_FETCH_NUM	);

        /*
         * Now we find out if the column is part of a primary key
         */

        $primaryKeys = $this->metaPrimaryKeys($table);
        if (!$primaryKeys) {
            return $retarr;
        }


        foreach ($primaryKeys as $pk) {
            $pk = strtoupper($pk);
            if (isset($retarr[$pk])) {
                $retarr[$pk]->primary_key = true;
            }
        }

        return $retarr;



        $qid = @db2_primary_keys($this->_connectionID, "", $schema, $table);
        if (empty($qid))
            return false;

        $rs = new ADORecordSet_db2($qid);

        if (!$rs)
        {
            $ADODB_FETCH_MODE = $savem;
            return $retarr;
        }
        $rs->_fetch();

        /*
        $rs->fields indices
        0 TABLE_CAT
        1 TABLE_SCHEM
        2 TABLE_NAME
        3 COLUMN_NAME
        4 KEY_SEQ
        5 PK_NAME
        */
        while (!$rs->EOF) {

            if (strtoupper(trim($rs->fields[2])) == $table
            && (!$schema || strtoupper($rs->fields[1]) == $schema))
            {
                $retarr[strtoupper($rs->fields[3])]->primary_key = true;
            }
            else if (sizeof($retarr)>0)
                break;

            $rs->MoveNext();
        }
        $rs->Close();

        $ADODB_FETCH_MODE = $savem;

        if (empty($retarr))
            return false;

        /*
        * If the fetch mode is numeric, return as numeric array
        */
        if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM)
            $retarr = array_values($retarr);

        return $retarr;
    }

    /**
      * In this version if prepareSp, we just check to make sure
      * that the name of the stored procedure is correct
      * If true, we returns an array
      * else false
      *
      * @param	string	$procedureName
      * @param	mixed   $parameters (not used in db2 connections)
      * @return mixed[]
      */
    function prepareSp($procedureName,$parameters=false) {

        global $ADODB_FETCH_MODE;

        $this->storedProcedureParameters = array('name'=>'',
                                                 'resource'=>false,
                                                 'in'=>array(),
                                                 'out'=>array(),
                                                 'index'=>array(),
                                                 'parameters'=>array(),
                                                 'keyvalue' => array());

        //$procedureName = strtoupper($procedureName);
        //$procedureName = $this->getTableCasedValue($procedureName);

        $savem = $ADODB_FETCH_MODE;
        $ADODB_FETCH_MODE = ADODB_FETCH_NUM;

        $qid = db2_procedures($this->_connectionID, NULL , '%' , $procedureName );

        $ADODB_FETCH_MODE = $savem;

        if (!$qid)
        {
            if ($this->debug)
                ADOConnection::outp(sprintf('No Procedure of name %s available',$procedureName));
            return false;
        }



        $this->storedProcedureParameters['name'] = $procedureName;
        /*
         * Now we know we have a valid procedure name, lets see if it requires
         * parameters
         */
        $savem = $ADODB_FETCH_MODE;
        $ADODB_FETCH_MODE = ADODB_FETCH_NUM;

        $qid = db2_procedure_columns($this->_connectionID, NULL , '%' , $procedureName , NULL );

        $ADODB_FETCH_MODE = $savem;

        if (!$qid)
        {
            if ($this->debug)
                ADOConnection::outp(sprintf('No columns of name %s available',$procedureName));
            return false;
        }
        $rs = new ADORecordSet_db2($qid);
        if (!$rs)
            return false;

        $preparedStatement = 'CALL %s(%s)';
        $parameterMarkers = array();
        while (!$rs->EOF)
        {
            $parameterName = $rs->fields[3];
            if ($parameterName == '')
            {
                $rs->moveNext();
                continue;
            }
            $parameterType = $rs->fields[4];
            $ordinalPosition = $rs->fields[17];
            switch($parameterType)
            {
            case DB2_PARAM_IN:
            case DB2_PARAM_INOUT:
                $this->storedProcedureParameters['in'][$parameterName] = '';
                break;
            case DB2_PARAM_INOUT:
            case DB2_PARAM_OUT:
                $this->storedProcedureParameters['out'][$parameterName] = '';
                break;
            }
            $this->storedProcedureParameters['index'][$parameterName] = $ordinalPosition;
            $this->storedProcedureParameters['parameters'][$ordinalPosition] = $rs->fields;
            $rs->moveNext();

        }
        $parameterCount = count($this->storedProcedureParameters['index']);
        $parameterMarkers = array_fill(0,$parameterCount,'?');

        /*
         * We now know how many parameters to bind to the stored procedure
         */
        $parameterList = implode(',',$parameterMarkers);

        $sql = sprintf($preparedStatement,$procedureName,$parameterList);

        $spResource = @db2_prepare($this->_connectionID,$sql);

        if (!$spResource)
        {
            $errorMessage = @db2_conn_errormsg($this->_connectionID);
            $this->_errorMsg = $errorMessage;

            if ($this->debug)
                ADOConnection::outp($errorMessage);

            return false;
        }

        $this->storedProcedureParameters['resource'] = $spResource;

        if ($this->debug)
        {

            ADOConnection::outp('The following parameters will be used in the SP call');
            ADOConnection::outp(print_r($this->storedProcedureParameters));
        }
        /*
         * We now have a stored parameter resource
         * to bind to. The spResource and sql that is returned are
         * not usable, its for dummy compatibility. Everything
         * will be handled by the storedProcedureParameters
         * array
         */
        return array($sql,$spResource);

    }

    private function storedProcedureParameter(&$stmt,
                                              &$var,
                                              $name,
                                              $isOutput=false,
                                              $maxLen=4000,
                                              $type=false)
    {


        $name = strtoupper($name);

        /*
         * Must exist in the list of parameter names for the type
         */
        if ($isOutput
        && !isset( $this->storedProcedureParameters['out'][$name]))
        {
            $errorMessage = sprintf('%s is not a valid OUT parameter name',$name);

            $this->_errorMsg = $errorMessage;
            if ($this->debug)
                ADOConnection::outp($errorMessage);
            return false;
        }

        if (!$isOutput
        && !isset( $this->storedProcedureParameters['in'][$name]))
        {
            $errorMessage = sprintf('%s is not a valid IN parameter name',$name);

            $this->_errorMsg = $errorMessage;
            if ($this->debug)
                ADOConnection::outp($errorMessage);
            return false;
        }

        /*
         * We will use these values to bind to when we execute
         * the query
         */
        $this->storedProcedureParameters['keyvalue'][$name] = &$var;

        return true;

    }

    /**
    * Executes a prepared stored procedure.
    *
    * The function uses the previously accumulated information and
    * resources in the $storedProcedureParameters array
    *
    * @return mixed	The statement id if successful, or false
    */
    private function executeStoredProcedure()
    {

        /*
         * Get the previously built resource
         */
        $stmtid = $this->storedProcedureParameters['resource'];

        /*
         * Bind our variables to the DB2 procedure
         */
        foreach ($this->storedProcedureParameters['keyvalue'] as $spName=>$spValue){

            /*
             * Get the ordinal position, required for binding
             */
            $ordinalPosition = $this->storedProcedureParameters['index'][$spName];

            /*
             * Get the db2 column dictionary for the parameter
             */
            $columnDictionary = $this->storedProcedureParameters['parameters'][$ordinalPosition];
            $parameterType    = $columnDictionary[4];
            $dataType         = $columnDictionary[5];
            $precision        = $columnDictionary[10];
            $scale        	  = $columnDictionary[9];

            $ok = @db2_bind_param ($this->storedProcedureParameters['resource'],
                                  $ordinalPosition ,
                                  $spName,
                                  $parameterType,
                                  $dataType,
                                  $precision,
                                  $scale
                                  );

            if (!$ok)
            {
                $this->_errorMsg  = @db2_stmt_errormsg();
                $this->_errorCode = @db2_stmt_error();

                if ($this->debug)
                    ADOConnection::outp($this->_errorMsg);
                return false;
            }

            if ($this->debug)
                ADOConnection::outp("Correctly Bound parameter $spName to procedure");

            /*
             * Build a variable in the current environment that matches
             * the parameter name
             */
            ${$spName} = $spValue;

        }

        /*
         * All bound, execute
         */

        if (!@db2_execute($stmtid))
        {
            $this->_errorMsg = @db2_stmt_errormsg();
            $this->_errorCode = @db2_stmt_error();

            if ($this->debug)
                ADOConnection::outp($this->_errorMsg);
            return false;
        }

        /*
         * We now take the changed parameters back into the
         * stored procedures array where we can query them later
         * Remember that $spValue was passed in by reference, so we
         * can access the value in the variable that was originally
         * passed to inParameter or outParameter
         */
        foreach ($this->storedProcedureParameters['keyvalue'] as $spName=>$spValue)
        {
            /*
             * We make it available to the environment
             */
            $spValue = ${$spName};
            $this->storedProcedureParameters['keyvalue'][$spName] = $spValue;
        }

        return $stmtid;
    }

    /**
     *
     * Accepts an input or output parameter to bind to either a stored
     * or prepared statements. For DB2, this should not be called as an
     * API. always wrap with inParameter and outParameter
     *
     * @param mixed[] $stmt 		Statement returned by Prepare() or PrepareSP().
     * @param mixed   $var 		PHP variable to bind to. Can set to null (for isNull support).
     * @param string  $name 		Name of stored procedure variable name to bind to.
     * @param int	 $isOutput 	optional) Indicates direction of parameter
     * 							0/false=IN  1=OUT  2= IN/OUT
     *							This is ignored for Stored Procedures
     * @param int	$maxLen		(optional)Holds an maximum length of the variable.
     *							This is ignored for Stored Procedures
     * @param int	$type 		(optional) The data type of $var.
     *							This is ignored for Stored Procedures
     *
     * @return bool				Success of the operation
     */
    public function parameter(&$stmt, &$var, $name, $isOutput=false, $maxLen=4000, $type=false)
    {

        /*
         * If the $stmt is the name of a stored procedure we are
         * setting up, we will process it one way, otherwise
         * we assume we are setting up a prepared statement
        */
        if (is_array($stmt))
        {
            if ($this->debug)
                ADOConnection::outp("Adding parameter to stored procedure");
            if ($stmt[1] == $this->storedProcedureParameters['resource'])
                return $this->storedProcedureParameter($stmt[1],
                                                        $var,
                                                        $name,
                                                        $isOutput,
                                                        $maxLen,
                                                        $type);

        }

        /*
         * We are going to add a parameter to a prepared statement
         */
        if ($this->debug)
            ADOConnection::outp("Adding parameter to prepared statement");
    }


    /**
     * Prepares a prepared SQL statement, not used for stored procedures
     *
     * @param string	$sql
     *
     * @return mixed
     */
    function prepare($sql)
    {

        if (! $this->_bindInputArray) return $sql; // no binding

        $stmt = @db2_prepare($this->_connectionID,$sql);
        if (!$stmt) {
            // we don't know whether db2 driver is parsing prepared stmts, so just return sql
            return $sql;
        }
        return array($sql,$stmt,false);
    }

    /**
     * Executes a query
     *
     * @param	mixed $sql
     * @param	mixed $inputarr	An optional array of parameters
     *
     * @return mixed				either the queryID or false
     */
    function _query($sql, $inputarr = false)
    {
        $db2Options = array();
        /*
         * Use DB2 Internal case handling for best speed
         */
        switch(ADODB_ASSOC_CASE)
        {
        case ADODB_ASSOC_CASE_UPPER:
            $db2Options = array('db2_attr_case'=>DB2_CASE_UPPER);
            $setOption = @db2_set_option($this->_connectionID,$db2Options,1);
            break;

         case ADODB_ASSOC_CASE_LOWER:
            $db2Options = array('db2_attr_case'=>DB2_CASE_LOWER);
            $setOption = @db2_set_option($this->_connectionID,$db2Options,1);
            break;

        default:
            $db2Options = array('db2_attr_case'=>DB2_CASE_NATURAL);
            $setOption = @db2_set_option($this->_connectionID,$db2Options,1);
        }

        if (!$this->useLegacyRecordCounting) {
            $db2Options = array('rowcount'=>DB2_ROWCOUNT_PREFETCH_ON);
            $setOption = @db2_set_option($this->_connectionID,$db2Options,1);
        }

        if ($inputarr)
        {
            if (is_array($sql))
            {
                $stmtid = $sql[1];
            }
            else
            {
                $stmtid = @db2_prepare($this->_connectionID,$sql);

                if ($stmtid == false)
                {
                    $this->_errorMsg  = @db2_stmt_errormsg();
                    $this->_errorCode = @db2_stmt_error();

                    if ($this->debug)
                        ADOConnection::outp($this->_errorMsg);

                    return false;
                }
            }

            if (! @db2_execute($stmtid,$inputarr))
            {
                $this->_errorMsg = @db2_stmt_errormsg();
                $this->_errorCode = @db2_stmt_error();
                if ($this->debug)
                    ADOConnection::outp($this->_errorMsg);
                return false;
            }

        }
        else if (is_array($sql))
        {

            /*
             * Either a prepared statement or a stored procedure
             */

            if (is_array($this->storedProcedureParameters)
                && is_resource($this->storedProcedureParameters['resource']
            ))
                /*
                 * This is all handled in the separate method for
                 * readability
                 */
                return $this->executeStoredProcedure();

            /*
             * First, we prepare the statement
             */
            $stmtid = @db2_prepare($this->_connectionID,$sql[0]);
            if (!$stmtid){
                $this->_errorMsg = @db2_stmt_errormsg();
                $this->_errorCode = @db2_stmt_error();
                if ($this->debug)
                    ADOConnection::outp("Prepare failed: " . $this->_errorMsg);

                return false;
            }
            /*
             * We next bind some input parameters
             */
            $ordinal = 1;
            foreach ($sql[1] as $psVar=>$psVal){
                ${$psVar} = $psVal;
                $ok = @db2_bind_param($stmtid, $ordinal, $psVar, DB2_PARAM_IN);
                if (!$ok)
                {
                    $this->_errorMsg = @db2_stmt_errormsg();
                    $this->_errorCode = @db2_stmt_error();
                    if ($this->debug)
                        ADOConnection::outp("Bind failed: " . $this->_errorMsg);
                    return false;
                }
            }

            if (!@db2_execute($stmtid))
            {
                $this->_errorMsg = @db2_stmt_errormsg();
                $this->_errorCode = @db2_stmt_error();
                if ($this->debug)
                    ADOConnection::outp($this->_errorMsg);
                return false;
            }

            return $stmtid;
        }
        else
        {

            $stmtid = @db2_exec($this->_connectionID,$sql);
        }
        $this->_lastAffectedRows = 0;
        if ($stmtid)
        {
            if (@db2_num_fields($stmtid) == 0)
            {
                $this->_lastAffectedRows = db2_num_rows($stmtid);
                $stmtid = true;
            }
            else
            {
                $this->_lastAffectedRows = 0;
            }

            $this->_errorMsg = '';
            $this->_errorCode = 0;

        }
        else
        {

            $this->_errorMsg = @db2_stmt_errormsg();
            $this->_errorCode = @db2_stmt_error();

        }
        return $stmtid;
    }

    /*
        Insert a null into the blob field of the table first.
        Then use UpdateBlob to store the blob.

        Usage:

        $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",$val) != false;
    }

    // returns true or false
    function _close()
    {
        $ret = @db2_close($this->_connectionID);
        $this->_connectionID = false;
        return $ret;
    }

    function _affectedrows()
    {
        return $this->_lastAffectedRows;
    }

    /**
     * Gets a meta cased parameter
     *
     * Receives an input variable to be processed per the metaCasing
     * rule, and returns the same value, processed
     *
     * @param string $value
     *
     * @return string
     */
    final public function getMetaCasedValue($value)
    {
        global $ADODB_ASSOC_CASE;

        switch($ADODB_ASSOC_CASE)
        {
        case ADODB_ASSOC_CASE_LOWER:
            $value = strtolower($value);
            break;
        case ADODB_ASSOC_CASE_UPPER:
            $value = strtoupper($value);
            break;
        }
        return $value;
    }


    const TABLECASE_LOWER    =  0;
    const TABLECASE_UPPER    =  1;
    const TABLECASE_DEFAULT  =  2;

    /**
     * Controls the casing of the table provided to the meta functions
     */
    private $tableCase = 2;

    /**
     * Sets the table case parameter
     *
     * @param int $caseOption
     * @return null
     */
    final public function setTableCasing($caseOption)
    {
        $this->tableCase = $caseOption;
    }

    /**
     * Gets the table casing parameter
     *
     * @return int $caseOption
     */
    final public function getTableCasing()
    {
        return $this->tableCase;
    }

    /**
     * Gets a table cased parameter
     *
     * Receives an input variable to be processed per the tableCasing
     * rule, and returns the same value, processed
     *
     * @param string $value
     *
     * @return string
     */
    final public function getTableCasedValue($value)
    {
        switch($this->tableCase)
        {
        case self::TABLECASE_LOWER:
            $value = strtolower($value);
            break;
        case self::TABLECASE_UPPER:
            $value = strtoupper($value);
            break;
        }
        return $value;
    }

}

/*--------------------------------------------------------------------------------------
     Class Name: Recordset
--------------------------------------------------------------------------------------*/

class ADORecordSet_db2 extends ADORecordSet {

    var $bind = false;
    var $databaseType = "db2";
    var $dataProvider = "db2";
    var $useFetchArray;


    // returns the field object
    function fetchField($offset = 0)
    {
        $o			   = new ADOFieldObject();
        $o->name 	   = @db2_field_name($this->_queryID,$offset);
        $o->type 	   = @db2_field_type($this->_queryID,$offset);
        $o->max_length = @db2_field_width($this->_queryID,$offset);

        /*
        if (ADODB_ASSOC_CASE == 0)
            $o->name = strtolower($o->name);
        else if (ADODB_ASSOC_CASE == 1)
            $o->name = strtoupper($o->name);
        */
        return $o;
    }

    /* Use associative array to get fields array */
    function fields($colname)
    {

        if ($this->fetchMode & ADODB_FETCH_ASSOC) {
            return $this->fields[$colname];
        }

        if (!$this->bind) {
            $this->bind = array();
            for ($i=0; $i < $this->_numOfFields; $i++) {
                $o = $this->FetchField($i);
                $this->bind[strtoupper($o->name)] = $i;
            }
        }

         return $this->fields[$this->bind[strtoupper($colname)]];
    }


    function _initrs()
    {
        global $ADODB_COUNTRECS;
        /*
        * If legacy rowcount is activated, this will return -1;
        */
        $this->_numOfRows = ($ADODB_COUNTRECS) ? @db2_num_rows($this->_queryID) : -1;

        $this->_numOfFields = @db2_num_fields($this->_queryID);

        if ($this->_numOfRows == 0)
            $this->_numOfRows = -1;
    }

    function _seek($row)
    {
        return false;
    }

    function getArrayLimit($nrows,$offset=0)
    {
        if ($offset <= 0) {
            $rs = $this->GetArray($nrows);
            return $rs;
        }

        $this->Move($offset);


        $results = array();
        $cnt = 0;
        while (!$this->EOF && $nrows != $cnt) {
            $results[$cnt++] = $this->fields;
            $this->MoveNext();
        }

        return $results;
    }

    function moveNext()
    {
        if ($this->EOF || $this->_numOfRows == 0)
            return false;

        $this->_currentRow++;

        $this->processCoreFetch();
        return $this->processMoveRecord();

    }

    private function processCoreFetch()
    {
        switch ($this->fetchMode){
        case ADODB_FETCH_ASSOC:

            /*
             * Associative array
             */
            $this->fields = @db2_fetch_assoc($this->_queryID);
            break;

        case ADODB_FETCH_BOTH:
            /*
             * Fetch both numeric and Associative array
             */
            $this->fields = @db2_fetch_both($this->_queryID);
            break;
        default:
            /*
             * Numeric array
             */
            $this->fields = @db2_fetch_array($this->_queryID);
            break;
        }
    }

    private function processMoveRecord()
    {
        if (!$this->fields){
            $this->EOF = true;
            return false;
        }

        return true;
    }

    function _fetch()
    {
        $this->processCoreFetch();
        if ($this->fields)
            return true;

        $this->fields = false;
        return false;
    }

    function _close()
    {
        if (!is_resource($this->_queryID)) {
            /*
            * Already closed
            */
            return false;
        }
        $ok = @db2_free_result($this->_queryID);
        if (!$ok)
        {
            $this->connection->_errorMsg  = @db2_stmt_errormsg($this->_queryID);
            $this->connection->_errorCode = @db2_stmt_error();

            if ($this->debug)
                ADOConnection::outp($this->connection->_errorMsg);
            return false;
        }
    }

}
