<?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 http://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"; 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 $_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; 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 ($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, null, null, $db2Options); $this->_errorMsg = @db2_conn_errormsg(); if ($this->_connectionID && $this->connectStmt) $this->execute($this->connectStmt); 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 ); /* * 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('.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 * * @param string $fmt * @param bool $col * * @return string */ function sqlDate($fmt, $col=false) { if (!$col) $col = $this->sysDate; /* 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)"; $s .= "char(year($col))"; break; case 'M': if ($len==1) return "monthname($col)"; $s .= "substr(monthname($col),1,3)"; break; case 'm': if ($len==1) return "month($col)"; $s .= "right(digits(month($col)),2)"; break; case 'D': case 'd': if ($len==1) return "day($col)"; $s .= "right(digits(day($col)),2)"; break; case 'H': case 'h': if ($len==1) return "hour($col)"; if ($col != $this->sysDate) $s .= "right(digits(hour($col)),2)"; else $s .= "''"; break; case 'i': case 'I': if ($len==1) return "minute($col)"; if ($col != $this->sysDate) $s .= "right(digits(minute($col)),2)"; else $s .= "''"; break; case 'S': case 's': if ($len==1) return "second($col)"; if ($col != $this->sysDate) $s .= "right(digits(second($col)),2)"; else $s .= "''"; break; default: if ($ch == '\\') { $i++; $ch = substr($fmt,$i,1); } $s .= $this->qstr($ch); } } return $s; } function serverInfo() { $sql = "SELECT service_level, fixpack_num FROM TABLE(sysproc.env_get_inst_info()) AS INSTANCEINFO"; $row = $this->GetRow($sql); if ($row) { $info['version'] = $row[0].':'.$row[1]; $info['fixpack'] = $row[1]; $info['description'] = ''; } else { return ADOConnection::serverInfo(); } return $info; } function createSequence($seqname='adodbseq',$start=1) { if (empty($this->_genSeqSQL)) return false; $ok = $this->execute(sprintf($this->_genSeqSQL,$seqname,$start)); if (!$ok) return false; return true; } function dropSequence($seqname='adodbseq') { if (empty($this->_dropSeqSQL)) return false; return $this->execute(sprintf($this->_dropSeqSQL,$seqname)); } function selectLimit($sql,$nrows=-1,$offset=-1,$inputArr=false,$secs2cache=0) { $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 { $nrows += $offset; $sql .= " FETCH FIRST $nrows ROWS ONLY "; } /* * DB2 has no native support for mid table offset */ $rs = ADOConnection::selectLimit($sql,$nrows,$offset,$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->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) { 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); $savem = $ADODB_FETCH_MODE; $ADODB_FETCH_MODE = ADODB_FETCH_NUM; $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; } 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; $ADODB_FETCH_MODE = ADODB_FETCH_NUM; /* * 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' */ $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(); $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; /* 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 ((integer)$t) { case 1: case 12: case 0: case -95: case -96: return 'C'; case -97: case -1: //text return 'X'; case -4: //image return 'B'; case 9: case 91: return 'D'; case 10: case 11: case 92: case 93: return 'T'; case 4: case 5: case -6: return 'I'; case -11: // uniqidentifier return 'R'; case -7: //bit return 'L'; default: return 'N'; } } public function metaColumns($table, $normalize=true) { global $ADODB_FETCH_MODE; $savem = $ADODB_FETCH_MODE; $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); if (!$rs) 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 = 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; /* * 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); 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 ($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",array($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; function __construct($id,$mode=false) { if ($mode === false) { global $ADODB_FETCH_MODE; $mode = $ADODB_FETCH_MODE; } $this->fetchMode = $mode; $this->_queryID = $id; } // 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; $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; } 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() { $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; }<} }