diff options
| -rw-r--r-- | datadict/datadict-db2.inc.php | 76 | ||||
| -rw-r--r-- | drivers/adodb-db2.inc.php | 1829 |
2 files changed, 1562 insertions, 343 deletions
diff --git a/datadict/datadict-db2.inc.php b/datadict/datadict-db2.inc.php index 69c40986..93bf79b7 100644 --- a/datadict/datadict-db2.inc.php +++ b/datadict/datadict-db2.inc.php @@ -1,5 +1,4 @@ <?php - /** @version v5.21.0-dev ??-???-2016 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved. @@ -18,6 +17,7 @@ class ADODB2_db2 extends ADODB_DataDict { var $databaseType = 'db2'; var $seqField = false; + var $dropCol = 'ALTER TABLE %s DROP COLUMN %s'; function ActualType($meta) { @@ -60,20 +60,66 @@ class ADODB2_db2 extends ADODB_DataDict { return $suffix; } - function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') + function alterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') { - if ($this->debug) ADOConnection::outp("AlterColumnSQL not supported"); - return array(); + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey,$idxs) = $this->_GenFields($flds); + // genfields can return FALSE at times + if ($lines == null) $lines = array(); + $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' '; + + $dataTypeWords = array('SET','DATA','TYPE'); + + foreach($lines as $v) + { + /* + * We must now post-process the line to insert the 'SET DATA TYPE' + * text into the alter statement + */ + $e = explode(' ',$v); + + array_splice($e,1,0,$dataTypeWords); + + $v = implode(' ',$e); + + $sql[] = $alter . $v; + } + if (is_array($idxs)) + { + foreach($idxs as $idx => $idxdef) { + $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']); + $sql = array_merge($sql, $sql_idxs); + } + + } + return $sql; } - function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') + + function dropColumnSql($tabname, $flds, $tableflds='',$tableoptions='') { - if ($this->debug) ADOConnection::outp("DropColumnSQL not supported"); - return array(); + + + $tabname = $this->connection->getMetaCasedValue($tabname); + $flds = $this->connection->getMetaCasedValue($flds); + + if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_NATIVE ) + { + /* + * METACASE_NATIVE + */ + $tabname = $this->connection->nameQuote . $tabname . $this->connection->nameQuote; + $flds = $this->connection->nameQuote . $flds . $this->connection->nameQuote; + } + $sql = sprintf($this->dropCol,$tabname,$flds); + return (array)$sql; + } + - function ChangeTableSQL($tablename, $flds, $tableoptions = false, $dropOldFlds=false) + function changeTableSQL($tablename, $flds, $tableoptions = false, $dropOldFields=false) { /** @@ -85,17 +131,25 @@ class ADODB2_db2 extends ADODB_DataDict { $validTypes = array("CHAR","VARC"); $invalidTypes = array("BIGI","BLOB","CLOB","DATE", "DECI","DOUB", "INTE", "REAL","SMAL", "TIME"); // check table exists - $cols = $this->MetaColumns($tablename); + + + $cols = $this->metaColumns($tablename); if ( empty($cols)) { - return $this->CreateTableSQL($tablename, $flds, $tableoptions); + return $this->createTableSQL($tablename, $flds, $tableoptions); } // already exists, alter table instead list($lines,$pkey) = $this->_GenFields($flds); - $alter = 'ALTER TABLE ' . $this->TableName($tablename); + $alter = 'ALTER TABLE ' . $this->tableName($tablename); $sql = array(); foreach ( $lines as $id => $v ) { + /* + * If the metaCasing was NATIVE the col returned with nameQuotes + * around the field. We need to remove this for the metaColumn + * match + */ + $id = str_replace($this->connection->nameQuote,'',$id); if ( isset($cols[$id]) && is_object($cols[$id]) ) { /** If the first field of $v is the fieldname, and diff --git a/drivers/adodb-db2.inc.php b/drivers/adodb-db2.inc.php index a56385d2..4b1bbb54 100644 --- a/drivers/adodb-db2.inc.php +++ b/drivers/adodb-db2.inc.php @@ -3,18 +3,25 @@ @version v5.21.0-dev ??-???-2016 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved. @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community + 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. - This is a version of the ADODB driver for DB2. It uses the 'ibm_db2' PECL extension - for PHP (http://pecl.php.net/package/ibm_db2), which in turn requires DB2 V8.2.2 or - higher. + Set tabs to 4 for best viewing. - Originally tested with PHP 5.1.1 and Apache 2.0.55 on Windows XP SP2. - More recently tested with PHP 5.1.2 and Apache 2.0.55 on Windows XP SP2. - - This file was ported from "adodb-odbc.inc.php" by Larry Menard, "larry.menard#rogers.com". - I ripped out what I believed to be a lot of redundant or obsolete code, but there are - probably still some remnants of the ODBC support in this file; I'm relying on reviewers - of this code to point out any other things that can be removed. +* Driver for use with IBM DB2 Native Client +* +* 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 http://pecl.php.net/package/ibm_db2 Pecl Extension For DB2 +* @author Mark Newnham */ // security - hide paths @@ -22,11 +29,8 @@ if (!defined('ADODB_DIR')) die(); define("_ADODB_DB2_LAYER", 2 ); -/*-------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------*/ - - - +/*-------------------------------------------------------------------- +----------------------------------------------------------------------*/ class ADODB_db2 extends ADOConnection { @@ -45,101 +49,375 @@ class ADODB_db2 extends ADOConnection { var $binmode = DB2_BINARY; - var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive - // breaking backward-compat - var $_bindInputArray = false; + /* + * 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"; - var $_genSeqSQL = "CREATE SEQUENCE %s START WITH %s NO MAXVALUE NO CYCLE"; + var $_genSeqSQL = " + CREATE SEQUENCE %s START WITH %s + NO MAXVALUE NO CYCLE INCREMENT BY 1 NO CACHE + "; var $_dropSeqSQL = "DROP SEQUENCE %s"; var $_autocommit = true; - var $_haserrorfunctions = true; var $_lastAffectedRows = 0; - var $uCaseTables = true; // for meta* functions, uppercase table names 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 = '"'; + + /* + * Executed after successful connection + */ + public $connectStmt = ''; + + /* + * Holds the current database name + */ + private $databaseName = ''; + /* + * Holds information about the stored procedure request + * currently being built + */ + private $storedProcedureParameters = false; + + + function __construct() {} function _insertid() { return ADOConnection::GetOne('VALUES IDENTITY_VAL_LOCAL()'); } - function __construct() + public function _connect($argDSN, $argUsername, $argPassword, $argDatabasename) { - $this->_haserrorfunctions = ADODB_PHPVER >= 0x4050; + return $this->doDB2Connect($argDSN, $argUsername, $argPassword, $argDatabasename); } - - // returns true or false - function _connect($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) { + global $php_errormsg; + if (!function_exists('db2_connect')) { - ADOConnection::outp("Warning: The old ODBC based DB2 driver has been renamed 'odbc_db2'. This ADOdb driver calls PHP's native db2 extension which is not installed."); + ADOConnection::outp("DB2 extension not installed."); + return null; + } + + $connectionParameters = $this->unpackParameters($argDSN, + $argUsername, + $argPassword, + $argDatabasename); + + if ($connectionParameters == null) + { + /* + * Error thrown + */ return null; } - // This needs to be set before the connect(). - // Replaces the odbc_binmode() call that was in Execute() + + $argDSN = $connectionParameters['dsn']; + $argUsername = $connectionParameters['uid']; + $argPassword = $connectionParameters['pwd']; + $argDatabasename = $connectionParameters['database']; + $useCataloguedConnection = $connectionParameters['catalogue']; + + if ($this->debug){ + 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 ($argDatabasename && empty($argDSN)) { + if ($persistent) + $db2Function = 'db2_pconnect'; + else + $db2Function = 'db2_connect'; + + /* + * We need to flatten out the connectionParameters + */ - if (stripos($argDatabasename,'UID=') && stripos($argDatabasename,'PWD=')) $this->_connectionID = db2_connect($argDatabasename,null,null); - else $this->_connectionID = db2_connect($argDatabasename,$argUsername,$argPassword); - } else { - if ($argDatabasename) $schema = $argDatabasename; - if (stripos($argDSN,'UID=') && stripos($argDSN,'PWD=')) $this->_connectionID = db2_connect($argDSN,null,null); - else $this->_connectionID = db2_connect($argDSN,$argUsername,$argPassword); + $db2Options = array(); + if ($this->connectionParameters) + { + foreach($this->connectionParameters as $p) + foreach($p as $k=>$v) + $db2Options[$k] = $v; } - - // For db2_connect(), there is an optional 4th arg. If present, it must be - // an array of valid options. So far, we don't use them. + + if ($useCataloguedConnection) + $this->_connectionID = $db2Function($argDatabasename, + $argUsername, + $argPassword, + $db2Options); + else + $this->_connectionID = $db2Function($argDSN, + null, + null, + $db2Options); + + $php_errormsg = ''; $this->_errorMsg = @db2_conn_errormsg(); - if (isset($this->connectStmt)) $this->Execute($this->connectStmt); + + if ($this->_connectionID && $this->connectStmt) + $this->execute($this->connectStmt); - if ($this->_connectionID && isset($schema)) $this->Execute("SET SCHEMA=$schema"); return $this->_connectionID != false; - } - - // returns true or false - function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename) + + } + + /** + * 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) { - if (!function_exists('db2_connect')) return null; - - // This needs to be set before the connect(). - // Replaces the odbc_binmode() call that was in Execute() - ini_set('ibm_db2.binmode', $this->binmode); - - $this->_errorMsg = ''; - - if ($argDatabasename && empty($argDSN)) { - - if (stripos($argDatabasename,'UID=') && stripos($argDatabasename,'PWD=')) $this->_connectionID = db2_pconnect($argDatabasename,null,null); - else $this->_connectionID = db2_pconnect($argDatabasename,$argUsername,$argPassword); - } else { - if ($argDatabasename) $schema = $argDatabasename; - if (stripos($argDSN,'UID=') && stripos($argDSN,'PWD=')) $this->_connectionID = db2_pconnect($argDSN,null,null); - else $this->_connectionID = db2_pconnect($argDSN,$argUsername,$argPassword); + + global $php_errormsg; + + $connectionParameters = array('dsn'=>'', + 'uid'=>'', + 'pwd'=>'', + 'database'=>'', + 'catalogue'=>true + ); + + /* + * 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'; + $php_errormsg = $errorMessage; + 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'; + $php_errormsg = $errorMessage; + 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->databaseName = $argDatabasename; + $argDSN .= ';database=' . $argDatabasename; + $argDatabasename = ''; + $useCataloguedConnection = false; + + } + else + { + $errorMessage = 'Uncatalogued connection parameters '; + $errorMessage.= 'must contain a database= argument'; + $php_errormsg = $errorMessage; + if ($this->debug) + ADOConnection::outp($errorMessage); + return null; + } } + + if ($argDSN && !$argDatabasename && $useCataloguedConnection) + { + $argDatabasename = $argDSN; + $argDSN = ''; + } + - $this->_errorMsg = @db2_conn_errormsg(); - if ($this->_connectionID && $this->autoRollback) @db2_rollback($this->_connectionID); - if (isset($this->connectStmt)) $this->Execute($this->connectStmt); + 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->databaseName = $argDatabasename; + elseif (!$this->databaseName) + $this->databaseName = $this->getDatabasenameFromDsn($argDSN); + + + $connectionParameters = array('dsn'=>$argDSN, + 'uid'=>$argUsername, + 'pwd'=>$argPassword, + 'database'=>$argDatabasename, + 'catalogue'=>$useCataloguedConnection + ); + + return $connectionParameters; + + } - if ($this->_connectionID && isset($schema)) $this->Execute("SET SCHEMA=$schema"); - return $this->_connectionID != false; + /** + * 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; } + - // format and return date string in database timestamp format - function DBTimeStamp($ts, $isfld = 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); + if (is_string($ts)) $ts = ADORecordSet::unixTimeStamp($ts); return 'TO_DATE('.adodb_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 - function SQLDate($fmt, $col=false) + /** + * Format date column in sql string given an input format that understands Y M D + * + * @param string $fmt + * @param bool $col + * + * @return string + */ + function sqlDate($fmt, $col=false) { - // use right() and replace() ? if (!$col) $col = $this->sysDate; /* use TO_CHAR() if $fmt is TO_CHAR() allowed fmt */ @@ -203,10 +481,12 @@ class ADODB_db2 extends ADOConnection { } - function ServerInfo() + function serverInfo() { - $row = $this->GetRow("SELECT service_level, fixpack_num FROM TABLE(sysproc.env_get_inst_info()) - as INSTANCEINFO"); + $sql = "SELECT service_level, fixpack_num + FROM TABLE(sysproc.env_get_inst_info()) + AS INSTANCEINFO"; + $row = $this->GetRow($sql); if ($row) { @@ -214,108 +494,119 @@ class ADODB_db2 extends ADOConnection { $info['fixpack'] = $row[1]; $info['description'] = ''; } else { - return ADOConnection::ServerInfo(); + return ADOConnection::serverInfo(); } return $info; } - function CreateSequence($seqname='adodbseq',$start=1) + function createSequence($seqname='adodbseq',$start=1) { - if (empty($this->_genSeqSQL)) return false; - $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$start)); - if (!$ok) return false; + if (empty($this->_genSeqSQL)) + return false; + + $ok = $this->execute(sprintf($this->_genSeqSQL,$seqname,$start)); + if (!$ok) + return false; return true; } - function DropSequence($seqname = 'adodbseq') + function dropSequence($seqname='adodbseq') { if (empty($this->_dropSeqSQL)) return false; - return $this->Execute(sprintf($this->_dropSeqSQL,$seqname)); + return $this->execute(sprintf($this->_dropSeqSQL,$seqname)); } - function SelectLimit($sql, $nrows = -1, $offset = -1, $inputArr = false, $secs2cache = 0) + function selectLimit($sql,$nrows=-1,$offset=-1,$inputArr=false,$secs2cache=0) { - $nrows = (int) $nrows; - $offset = (int) $offset; - if ($offset <= 0) { - // could also use " OPTIMIZE FOR $nrows ROWS " - if ($nrows >= 0) $sql .= " FETCH FIRST $nrows ROWS ONLY "; - $rs = $this->Execute($sql,$inputArr); - } else { + $nrows = (integer) $nrows; + + if ($offset <= 0) + { + if ($nrows >= 0) + $sql .= " FETCH FIRST $nrows ROWS ONLY "; + + $rs = $this->execute($sql,$inputArr); + + } + else + { if ($offset > 0 && $nrows < 0); - else { + + else + { $nrows += $offset; $sql .= " FETCH FIRST $nrows ROWS ONLY "; } - $rs = ADOConnection::SelectLimit($sql,-1,$offset,$inputArr); + + /* + * DB2 has no native support for mid table offset + */ + $rs = ADOConnection::selectLimit($sql,$nrows,$offset,$inputArr); + } - + return $rs; } - /* - This algorithm is not very efficient, but works even if table locking - is not available. - - Will return false if unable to generate an ID after $MAXLOOPS attempts. - */ - function GenID($seq='adodbseq',$start=1) + + function errorMsg() { - // if you have to modify the parameter below, your database is overloaded, - // or you need to implement generation of id's yourself! - $num = $this->GetOne("VALUES NEXTVAL FOR $seq"); - return $num; + if ($this->_errorMsg !== false) + return $this->_errorMsg; + + if (empty($this->_connectionID)) + return @db2_conn_errormsg(); + + return @db2_conn_errormsg($this->_connectionID); } - - function ErrorMsg() + function errorNo() { - if ($this->_haserrorfunctions) { - if ($this->_errorMsg !== false) return $this->_errorMsg; - if (empty($this->_connectionID)) return @db2_conn_errormsg(); - return @db2_conn_errormsg($this->_connectionID); - } else return ADOConnection::ErrorMsg(); - } - function ErrorNo() - { + if ($this->_errorCode !== false) + return $this->_errorCode; + - if ($this->_haserrorfunctions) { - if ($this->_errorCode !== false) { - // bug in 4.0.6, error number can be corrupted string (should be 6 digits) - return (strlen($this->_errorCode)<=2) ? 0 : $this->_errorCode; - } - - if (empty($this->_connectionID)) $e = @db2_conn_error(); - else $e = @db2_conn_error($this->_connectionID); - - // bug in 4.0.6, error number can be corrupted string (should be 6 digits) - // so we check and patch - if (strlen($e)<=2) return 0; - return $e; - } else return ADOConnection::ErrorNo(); + if (empty($this->_connectionID)) + $e = @db2_conn_error(); + + else + $e = @db2_conn_error($this->_connectionID); + + return $e; } - function BeginTrans() + function beginTrans() { - if (!$this->hasTransactions) return false; - if ($this->transOff) return true; + 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; + 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); + $ret = @db2_commit($this->_connectionID); + @db2_autocommit($this->_connectionID,true); return $ret; } @@ -324,123 +615,400 @@ class ADODB_db2 extends ADOConnection { if ($this->transOff) return true; if ($this->transCnt) $this->transCnt -= 1; $this->_autocommit = true; - $ret = db2_rollback($this->_connectionID); - db2_autocommit($this->_connectionID,true); + $ret = @db2_rollback($this->_connectionID); + @db2_autocommit($this->_connectionID,true); return $ret; } - function MetaPrimaryKeys($table, $owner = false) + /** + * 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) { - global $ADODB_FETCH_MODE; + + $primaryKeys = array(); + + global $ADODB_FETCH_MODE; - if ($this->uCaseTables) $table = strtoupper($table); $schema = ''; $this->_findschema($table,$schema); + + $table = $this->getTableCasedValue($table); - $savem = $ADODB_FETCH_MODE; + $savem = $ADODB_FETCH_MODE; $ADODB_FETCH_MODE = ADODB_FETCH_NUM; - $qid = @db2_primarykeys($this->_connectionID,'',$schema,$table); + $this->setFetchMode(ADODB_FETCH_NUM); - if (!$qid) { - $ADODB_FETCH_MODE = $savem; - return false; - } - $rs = new ADORecordSet_db2($qid); - $ADODB_FETCH_MODE = $savem; - if (!$rs) return false; + $sql = "SELECT * + FROM syscat.indexes + WHERE tabname='$table'"; + + $rows = $this->getAll($sql); + + $this->setFetchMode($savem); + $ADODB_FETCH_MODE = $savem; - $arr = $rs->GetArray(); - $rs->Close(); - $arr2 = array(); - for ($i=0; $i < sizeof($arr); $i++) { - if ($arr[$i][3]) $arr2[] = $arr[$i][3]; + 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 $arr2; + return $primaryKeys; } - function MetaForeignKeys($table, $owner = FALSE, $upper = FALSE, $asociative = FALSE ) + /** + * returns assoc array where keys are tables, and values are foreign keys + * + * @param string $table + * @param string $owner [optional][discarded] + * @param bool $upper [optional][discarded] + * @param bool $associative[optional][discarded] + * + * @return mixed[] Array of foreign key information + */ + public function metaForeignKeys($table, $owner = FALSE, $upper = FALSE, $asociative = FALSE ) { - global $ADODB_FETCH_MODE; + + global $ADODB_FETCH_MODE; - if ($this->uCaseTables) $table = strtoupper($table); $schema = ''; $this->_findschema($table,$schema); $savem = $ADODB_FETCH_MODE; $ADODB_FETCH_MODE = ADODB_FETCH_NUM; - $qid = @db2_foreign_keys($this->_connectionID,'',$schema,$table); - if (!$qid) { - $ADODB_FETCH_MODE = $savem; + + $this->setFetchMode(ADODB_FETCH_NUM); + + $sql = "SELECT SUBSTR(tabname,1,20) table_name, + SUBSTR(constname,1,20) fk_name, + SUBSTR(REFTABNAME,1,12) parent_table, + SUBSTR(refkeyname,1,20) pk_orig_table, + fk_colnames + FROM syscat.references + WHERE tabname = '$table'"; + + $results = $this->getAll($sql); + + $ADODB_FETCH_MODE = $savem; + $this->setFetchMode($savem); + + if (empty($results)) return false; + + $foreignKeys = array(); + + foreach ($results as $r) + { + $parentTable = trim($this->getMetaCasedValue($r[2])); + $keyName = trim($this->getMetaCasedValue($r[1])); + $foreignKeys[$parentTable] = $keyName; } - $rs = new ADORecordSet_db2($qid); + + return $foreignKeys; + } - $ADODB_FETCH_MODE = $savem; + /** + * 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; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + /* - $rs->fields indices - 0 PKTABLE_CAT - 1 PKTABLE_SCHEM - 2 PKTABLE_NAME - 3 PKCOLUMN_NAME - 4 FKTABLE_CAT - 5 FKTABLE_SCHEM - 6 FKTABLE_NAME - 7 FKCOLUMN_NAME + * 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' */ - if (!$rs) return false; - - $foreign_keys = array(); - while (!$rs->EOF) { - if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) { - if (!is_array($foreign_keys[$rs->fields[5].'.'.$rs->fields[6]])) - $foreign_keys[$rs->fields[5].'.'.$rs->fields[6]] = array(); - $foreign_keys[$rs->fields[5].'.'.$rs->fields[6]][$rs->fields[7]] = $rs->fields[3]; - } - $rs->MoveNext(); + $ttype = strtoupper($ttype); + if ($ttype) { + /* + * @todo We could do valid type checking or array type + */ + if ($ttype == 'V') + $ttype = 'VIEW'; + if ($ttype == 'T') + $ttype = 'TABLE'; } + + if (!$schema) + $schema = '%'; + + if (!$mask) + $mask = '%'; + + $qid = @db2_tables($this->_connectionID,NULL,$schema,$mask,$ttype); + $rs = new ADORecordSet_db2($qid); + + $ADODB_FETCH_MODE = $savem; + + if (!$rs) + return false; + + $arr = $rs->getArray(); + $rs->Close(); - return $foreign_key; - } + + $tableList = array(); + /* + * Array items + * --------------------------------- + * 0 TABLE_CAT The catalog that contains the table. + * The value is NULL if this table does not have catalogs. + * 1 TABLE_SCHEM Name of the schema that contains the table. + * 2 TABLE_NAME Name of the table. + * 3 TABLE_TYPE Table type identifier for the table. + * 4 REMARKS Description of the table. + */ + + for ($i=0; $i < sizeof($arr); $i++) + { + + $tableRow = $arr[$i]; + $tableName = $tableRow[2]; + $tableType = $tableRow[3]; + + if (!$tableName) + continue; + + if ($ttype == '' && (strcmp($tableType,'TABLE') <> 0 && strcmp($tableType,'VIEW') <> 0)) + continue; + + /* + * Set metacasing if required + */ + $tableName = $this->getMetaCasedValue($tableName); + + /* + * If we requested a schema, we prepend the schema + name to the table name + */ + if (strcmp($schema,'%') <> 0) + $tableName = $schema . '.' . $tableName; + + $tableList[] = $tableName; + + } + return $tableList; + } + + /** + * 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; - function MetaTables($ttype = false, $schema = false, $mask = 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; + + $savem = $ADODB_FETCH_MODE; $ADODB_FETCH_MODE = ADODB_FETCH_NUM; - $qid = db2_tables($this->_connectionID); - - $rs = new ADORecordSet_db2($qid); + $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 (!$rs) { - $false = false; - return $false; - } - - $arr = $rs->GetArray(); - $rs->Close(); - $arr2 = array(); + + if (empty($rows)) + return false; - if ($ttype) { - $isview = strncmp($ttype,'V',1) === 0; + 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; + } + } - for ($i=0; $i < sizeof($arr); $i++) { - if (!$arr[$i][2]) continue; - $type = $arr[$i][3]; - $owner = $arr[$i][1]; - $schemaval = ($schema) ? $arr[$i][1].'.' : ''; - if ($ttype) { - if ($isview) { - if (strncmp($type,'V',1) === 0) $arr2[] = $schemaval.$arr[$i][2]; - } else if (strncmp($owner,'SYS',3) !== 0) $arr2[] = $schemaval.$arr[$i][2]; - } else if (strncmp($owner,'SYS',3) !== 0) $arr2[] = $schemaval.$arr[$i][2]; + + 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 $arr2; + + return $metaProcedures; + + } + + /** + * Lists databases. Because instances are independant, we only know about + * the current database name + * + * @return string[] + */ + public function metaDatabases(){ + + $dbName = $this->getMetaCasedValue($this->databaseName); + + return (array)$dbName; + } + + + /* See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2datetime_data_type_changes.asp @@ -510,26 +1078,32 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2 } } - function MetaColumns($table, $normalize=true) + public function metaColumns($table, $normalize=true) { - global $ADODB_FETCH_MODE; - - $false = false; - if ($this->uCaseTables) $table = strtoupper($table); - $schema = ''; - $this->_findschema($table,$schema); - + global $ADODB_FETCH_MODE; + $savem = $ADODB_FETCH_MODE; - $ADODB_FETCH_MODE = ADODB_FETCH_NUM; - $colname = "%"; - $qid = db2_columns($this->_connectionID, "", $schema, $table, $colname); - if (empty($qid)) return $false; + $schema = '%'; + $this->_findschema($table,$schema); + $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); - $ADODB_FETCH_MODE = $savem; - if (!$rs) return $false; + if (!$rs) + return false; + $rs->_fetch(); $retarr = array(); @@ -548,15 +1122,25 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2 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 (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) { - $fld = new ADOFieldObject(); + 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; @@ -564,24 +1148,42 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2 $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 = false; - $retarr[strtoupper($fld->name)] = $fld; - } else if (sizeof($retarr)>0) + + $fld->not_null = !empty($rs->fields[10]); + $fld->scale = $rs->fields[8]; + $fld->primary_key = false; + + //$columnName = $this->getMetaCasedValue($fld->name); + $columnName = strtoupper($fld->name); + $retarr[$columnName] = $fld; + + } + else if (sizeof($retarr)>0) break; + $rs->MoveNext(); + } + $rs->Close(); - if (empty($retarr)) $retarr = false; + if (empty($retarr)) + $retarr = false; - $qid = db2_primary_keys($this->_connectionID, "", $schema, $table); - if (empty($qid)) return $false; + /* + * Now we find out if the column is part of a primary key + */ + + $qid = @db2_primary_keys($this->_connectionID, "", $schema, $table); + if (empty($qid)) + return false; $rs = new ADORecordSet_db2($qid); - $ADODB_FETCH_MODE = $savem; - if (!$rs) return $retarr; + if (!$rs) + { + $ADODB_FETCH_MODE = $savem; + return $retarr; + } $rs->_fetch(); /* @@ -594,23 +1196,371 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2 5 PK_NAME */ while (!$rs->EOF) { - if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) { + 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) + } + else if (sizeof($retarr)>0) break; + $rs->MoveNext(); } $rs->Close(); - if (empty($retarr)) $retarr = false; + $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 compatibilty. 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(); - function Prepare($sql) + 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); + + $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; @@ -618,66 +1568,158 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2 return array($sql,$stmt,false); } - /* returns queryID or false */ - function _query($sql,$inputarr=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) { - $last_php_error = $this->resetLastError(); - $this->_errorMsg = ''; + + GLOBAL $php_errormsg; + + if (isset($php_errormsg)) + $php_errormsg = ''; + $this->_error = ''; - if ($inputarr) { - if (is_array($sql)) { + $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); + } + + $db2Options = array('db2_attr_case'=>DB2_CASE_LOWER); + $setOption = @db2_set_option($this->_connectionID,$db2Options,1); + + if ($inputarr) + { + if (is_array($sql)) + { $stmtid = $sql[1]; - } else { - $stmtid = db2_prepare($this->_connectionID,$sql); + } + else + { + $stmtid = @db2_prepare($this->_connectionID,$sql); - if ($stmtid == false) { - $this->_errorMsg = $this->getChangedErrorMsg($last_php_error); + if ($stmtid == false) + { + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; return false; } } - if (! db2_execute($stmtid,$inputarr)) { - if ($this->_haserrorfunctions) { - $this->_errorMsg = db2_stmt_errormsg(); - $this->_errorCode = db2_stmt_error(); - } + 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)) { - $stmtid = $sql[1]; - if (!db2_execute($stmtid)) { - if ($this->_haserrorfunctions) { - $this->_errorMsg = db2_stmt_errormsg(); - $this->_errorCode = db2_stmt_error(); + } + 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; } - } else - $stmtid = @db2_exec($this->_connectionID,$sql); + + return $stmtid; + } + else + { + $stmtid = @db2_exec($this->_connectionID,$sql); + } $this->_lastAffectedRows = 0; - if ($stmtid) { - if (@db2_num_fields($stmtid) == 0) { + if ($stmtid) + { + if (@db2_num_fields($stmtid) == 0) + { $this->_lastAffectedRows = db2_num_rows($stmtid); $stmtid = true; - } else { + } + else + { $this->_lastAffectedRows = 0; } - if ($this->_haserrorfunctions) { - $this->_errorMsg = ''; - $this->_errorCode = 0; - } else { - $this->_errorMsg = $this->getChangedErrorMsg($last_php_error); - } - } else { - if ($this->_haserrorfunctions) { - $this->_errorMsg = db2_stmt_errormsg(); - $this->_errorCode = db2_stmt_error(); - } else { - $this->_errorMsg = $this->getChangedErrorMsg($last_php_error); - } + $this->_errorMsg = ''; + $this->_errorCode = 0; + + } + else + { + + $this->_errorMsg = @db2_stmt_errormsg(); + $this->_errorCode = @db2_stmt_error(); + } return $stmtid; } @@ -688,12 +1730,12 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2 Usage: - $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); + $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') + function updateBlob($table,$column,$val,$where,$blobtype='BLOB') { - return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false; + return $this->execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false; } // returns true or false @@ -708,6 +1750,87 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2 { 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; + } } @@ -735,21 +1858,30 @@ class ADORecordSet_db2 extends ADORecordSet { // returns the field object - function FetchField($offset = -1) + 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); + $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) + function fields($colname) { - if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->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++) { @@ -764,11 +1896,15 @@ class ADORecordSet_db2 extends ADORecordSet { function _initrs() { - global $ADODB_COUNTRECS; + global $ADODB_COUNTRECS; $this->_numOfRows = ($ADODB_COUNTRECS) ? @db2_num_rows($this->_queryID) : -1; + $this->_numOfFields = @db2_num_fields($this->_queryID); + // some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0 - if ($this->_numOfRows == 0) $this->_numOfRows = -1; + + if ($this->_numOfRows == 0) + $this->_numOfRows = -1; } function _seek($row) @@ -776,21 +1912,15 @@ class ADORecordSet_db2 extends ADORecordSet { return false; } - // speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated - function GetArrayLimit($nrows,$offset=-1) + function getArrayLimit($nrows,$offset=0) { if ($offset <= 0) { $rs = $this->GetArray($nrows); return $rs; } - $savem = $this->fetchMode; - $this->fetchMode = ADODB_FETCH_NUM; + $this->Move($offset); - $this->fetchMode = $savem; - if ($this->fetchMode & ADODB_FETCH_ASSOC) { - $this->fields = $this->GetRowAssoc(); - } $results = array(); $cnt = 0; @@ -798,46 +1928,81 @@ class ADORecordSet_db2 extends ADORecordSet { $results[$cnt++] = $this->fields; $this->MoveNext(); } - + return $results; } - - function MoveNext() + function moveNext() { - if ($this->_numOfRows != 0 && !$this->EOF) { - $this->_currentRow++; - + if ($this->EOF || $this->_numOfRows == 0) + return false; + + $this->_currentRow++; + + $this->processCoreFetch(); + return $this->processMoveRecord(); + + } + + final 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); - if ($this->fields) { - if ($this->fetchMode & ADODB_FETCH_ASSOC) { - $this->fields = $this->GetRowAssoc(); - } - return true; - } + break; } - $this->fields = false; - $this->EOF = true; - return false; - } + } - function _fetch() + final private function processMoveRecord() + { + if (!$this->fields){ + $this->EOF = true; + return false; + } + + return true; + } + + function _fetch() { - - $this->fields = db2_fetch_array($this->_queryID); - if ($this->fields) { - if ($this->fetchMode & ADODB_FETCH_ASSOC) { - $this->fields = $this->GetRowAssoc(); - } + $this->processCoreFetch(); + if ($this->fields) return true; - } + $this->fields = false; return false; } function _close() { - return @db2_free_result($this->_queryID); + $ok = @db2_free_result($this->_queryID); + if (!$ok) + { + $this->_errorMsg = @db2_stmt_errormsg($this->_queryId); + $this->_errorCode = @db2_stmt_error(); + + if ($this->debug) + ADOConnection::outp($this->_errorMsg); + return false; + } + } -} +}
\ No newline at end of file |
