3 V4.94 23 Jan 2007 (c) 2006 John Lim (jlim#natsoft.com.my). All rights reserved.
5 This is a version of the ADODB driver for DB2. It uses the 'ibm_db2' PECL extension
6 for PHP (http://pecl.php.net/package/ibm_db2), which in turn requires DB2 V8.2.2 or
9 Originally tested with PHP 5.1.1 and Apache 2.0.55 on Windows XP SP2.
10 More recently tested with PHP 5.1.2 and Apache 2.0.55 on Windows XP SP2.
12 This file was ported from "adodb-odbc.inc.php" by Larry Menard, "larry.menard#rogers.com".
13 I ripped out what I believed to be a lot of redundant or obsolete code, but there are
14 probably still some remnants of the ODBC support in this file; I'm relying on reviewers
15 of this code to point out any other things that can be removed.
18 // security - hide paths
19 if (!defined('ADODB_DIR')) die();
21 define("_ADODB_DB2_LAYER", 2 );
23 /*--------------------------------------------------------------------------------------
24 --------------------------------------------------------------------------------------*/
27 class ADODB_db2
extends ADOConnection
{
28 var $databaseType = "db2";
29 var $fmtDate = "'Y-m-d'";
30 var $concat_operator = '||';
32 var $sysTime = 'CURRENT TIME';
33 var $sysDate = 'CURRENT DATE';
34 var $sysTimeStamp = 'CURRENT TIMESTAMP';
36 var $fmtTimeStamp = "'Y-m-d-H:i:s'";
37 var $replaceQuote = "''"; // string to use to replace quotes
38 var $dataProvider = "db2";
39 var $hasAffectedRows = true;
41 var $binmode = DB2_BINARY
;
43 var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive
44 // breaking backward-compat
45 var $_bindInputArray = false;
46 var $_genIDSQL = "VALUES NEXTVAL FOR %s";
47 var $_genSeqSQL = "CREATE SEQUENCE %s START WITH 1 NO MAXVALUE NO CYCLE";
48 var $_dropSeqSQL = "DROP SEQUENCE %s";
49 var $_autocommit = true;
50 var $_haserrorfunctions = true;
51 var $_lastAffectedRows = 0;
52 var $uCaseTables = true; // for meta* functions, uppercase table names
53 var $hasInsertID = true;
57 return ADOConnection
::GetOne('VALUES IDENTITY_VAL_LOCAL()');
62 $this->_haserrorfunctions
= ADODB_PHPVER
>= 0x4050;
65 // returns true or false
66 function _connect($argDSN, $argUsername, $argPassword, $argDatabasename)
70 if (!function_exists('db2_connect')) {
71 ADOConnection
::outp("Warning: The old ODBC based DB2 driver has been renamed 'odbc_db2'. This ADOdb driver calls PHP's native db2 extension.");
74 // This needs to be set before the connect().
75 // Replaces the odbc_binmode() call that was in Execute()
76 ini_set('ibm_db2.binmode', $this->binmode
);
78 if ($argDatabasename) {
79 $this->_connectionID
= db2_connect($argDatabasename,$argUsername,$argPassword);
81 $this->_connectionID
= db2_connect($argDSN,$argUsername,$argPassword);
83 if (isset($php_errormsg)) $php_errormsg = '';
85 // For db2_connect(), there is an optional 4th arg. If present, it must be
86 // an array of valid options. So far, we don't use them.
88 $this->_errorMsg
= @db2_conn_errormsg
();
90 if (isset($this->connectStmt
)) $this->Execute($this->connectStmt
);
92 return $this->_connectionID
!= false;
95 // returns true or false
96 function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
100 if (!function_exists('db2_connect')) return null;
102 // This needs to be set before the connect().
103 // Replaces the odbc_binmode() call that was in Execute()
104 ini_set('ibm_db2.binmode', $this->binmode
);
106 if (isset($php_errormsg)) $php_errormsg = '';
107 $this->_errorMsg
= isset($php_errormsg) ?
$php_errormsg : '';
109 if ($argDatabasename) {
110 $this->_connectionID
= db2_pconnect($argDatabasename,$argUsername,$argPassword);
112 $this->_connectionID
= db2_pconnect($argDSN,$argUsername,$argPassword);
114 if (isset($php_errormsg)) $php_errormsg = '';
116 $this->_errorMsg
= @db2_conn_errormsg
();
117 if ($this->_connectionID
&& $this->autoRollback
) @db2_rollback
($this->_connectionID
);
118 if (isset($this->connectStmt
)) $this->Execute($this->connectStmt
);
120 return $this->_connectionID
!= false;
123 // format and return date string in database timestamp format
124 function DBTimeStamp($ts)
126 if (empty($ts) && $ts !== 0) return 'null';
127 if (is_string($ts)) $ts = ADORecordSet
::UnixTimeStamp($ts);
128 return 'TO_DATE('.adodb_date($this->fmtTimeStamp
,$ts).",'YYYY-MM-DD HH24:MI:SS')";
131 // Format date column in sql string given an input format that understands Y M D
132 function SQLDate($fmt, $col=false)
134 // use right() and replace() ?
135 if (!$col) $col = $this->sysDate
;
137 /* use TO_CHAR() if $fmt is TO_CHAR() allowed fmt */
138 if ($fmt== 'Y-m-d H:i:s')
139 return 'TO_CHAR('.$col.", 'YYYY-MM-DD HH24:MI:SS')";
144 for ($i=0; $i < $len; $i++
) {
145 if ($s) $s .= $this->concat_operator
;
150 if ($len==1) return "year($col)";
151 $s .= "char(year($col))";
154 if ($len==1) return "monthname($col)";
155 $s .= "substr(monthname($col),1,3)";
158 if ($len==1) return "month($col)";
159 $s .= "right(digits(month($col)),2)";
163 if ($len==1) return "day($col)";
164 $s .= "right(digits(day($col)),2)";
168 if ($len==1) return "hour($col)";
169 if ($col != $this->sysDate
) $s .= "right(digits(hour($col)),2)";
174 if ($len==1) return "minute($col)";
175 if ($col != $this->sysDate
)
176 $s .= "right(digits(minute($col)),2)";
181 if ($len==1) return "second($col)";
182 if ($col != $this->sysDate
)
183 $s .= "right(digits(second($col)),2)";
189 $ch = substr($fmt,$i,1);
191 $s .= $this->qstr($ch);
198 function ServerInfo()
201 if (!empty($this->host
) && ADODB_PHPVER
>= 0x4300) {
202 $dsn = strtoupper($this->host
);
206 if (!function_exists('db2_data_source')) return false;
210 $rez = @db2_data_source
($this->_connectionID
,
211 $first ? SQL_FETCH_FIRST
: SQL_FETCH_NEXT
);
213 if (!is_array($rez)) break;
214 if (strtoupper($rez['server']) == $dsn) {
219 if (!$found) return ADOConnection
::ServerInfo();
220 if (!isset($rez['version'])) $rez['version'] = '';
223 return ADOConnection
::ServerInfo();
228 function CreateSequence($seqname='adodbseq',$start=1)
230 if (empty($this->_genSeqSQL
)) return false;
231 $ok = $this->Execute(sprintf($this->_genSeqSQL
,$seqname));
232 if (!$ok) return false;
236 function DropSequence($seqname)
238 if (empty($this->_dropSeqSQL
)) return false;
239 return $this->Execute(sprintf($this->_dropSeqSQL
,$seqname));
243 This algorithm is not very efficient, but works even if table locking
246 Will return false if unable to generate an ID after $MAXLOOPS attempts.
248 function GenID($seq='adodbseq',$start=1)
250 // if you have to modify the parameter below, your database is overloaded,
251 // or you need to implement generation of id's yourself!
252 $num = $this->GetOne("VALUES NEXTVAL FOR $seq");
259 if ($this->_haserrorfunctions
) {
260 if ($this->_errorMsg
!== false) return $this->_errorMsg
;
261 if (empty($this->_connectionID
)) return @db2_conn_errormsg
();
262 return @db2_conn_errormsg
($this->_connectionID
);
263 } else return ADOConnection
::ErrorMsg();
269 if ($this->_haserrorfunctions
) {
270 if ($this->_errorCode
!== false) {
271 // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
272 return (strlen($this->_errorCode
)<=2) ?
0 : $this->_errorCode
;
275 if (empty($this->_connectionID
)) $e = @db2_conn_error
();
276 else $e = @db2_conn_error
($this->_connectionID
);
278 // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
279 // so we check and patch
280 if (strlen($e)<=2) return 0;
282 } else return ADOConnection
::ErrorNo();
287 function BeginTrans()
289 if (!$this->hasTransactions
) return false;
290 if ($this->transOff
) return true;
291 $this->transCnt +
= 1;
292 $this->_autocommit
= false;
293 return db2_autocommit($this->_connectionID
,false);
296 function CommitTrans($ok=true)
298 if ($this->transOff
) return true;
299 if (!$ok) return $this->RollbackTrans();
300 if ($this->transCnt
) $this->transCnt
-= 1;
301 $this->_autocommit
= true;
302 $ret = db2_commit($this->_connectionID
);
303 db2_autocommit($this->_connectionID
,true);
307 function RollbackTrans()
309 if ($this->transOff
) return true;
310 if ($this->transCnt
) $this->transCnt
-= 1;
311 $this->_autocommit
= true;
312 $ret = db2_rollback($this->_connectionID
);
313 db2_autocommit($this->_connectionID
,true);
317 function MetaPrimaryKeys($table)
319 global $ADODB_FETCH_MODE;
321 if ($this->uCaseTables
) $table = strtoupper($table);
323 $this->_findschema($table,$schema);
325 $savem = $ADODB_FETCH_MODE;
326 $ADODB_FETCH_MODE = ADODB_FETCH_NUM
;
327 $qid = @db2_primarykeys
($this->_connectionID
,'',$schema,$table);
330 $ADODB_FETCH_MODE = $savem;
333 $rs = new ADORecordSet_db2($qid);
334 $ADODB_FETCH_MODE = $savem;
336 if (!$rs) return false;
338 $arr =& $rs->GetArray();
341 for ($i=0; $i < sizeof($arr); $i++
) {
342 if ($arr[$i][3]) $arr2[] = $arr[$i][3];
347 function MetaForeignKeys($table, $owner = FALSE, $upper = FALSE, $asociative = FALSE )
349 global $ADODB_FETCH_MODE;
351 if ($this->uCaseTables
) $table = strtoupper($table);
353 $this->_findschema($table,$schema);
355 $savem = $ADODB_FETCH_MODE;
356 $ADODB_FETCH_MODE = ADODB_FETCH_NUM
;
357 $qid = @db2_foreign_keys
($this->_connectionID
,'',$schema,$table);
359 $ADODB_FETCH_MODE = $savem;
362 $rs = new ADORecordSet_db2($qid);
364 $ADODB_FETCH_MODE = $savem;
376 if (!$rs) return false;
378 $foreign_keys = array();
380 if (strtoupper(trim($rs->fields
[2])) == $table && (!$schema ||
strtoupper($rs->fields
[1]) == $schema)) {
381 if (!is_array($foreign_keys[$rs->fields
[5].'.'.$rs->fields
[6]]))
382 $foreign_keys[$rs->fields
[5].'.'.$rs->fields
[6]] = array();
383 $foreign_keys[$rs->fields
[5].'.'.$rs->fields
[6]][$rs->fields
[7]] = $rs->fields
[3];
393 function &MetaTables($ttype=false,$schema=false)
395 global $ADODB_FETCH_MODE;
397 $savem = $ADODB_FETCH_MODE;
398 $ADODB_FETCH_MODE = ADODB_FETCH_NUM
;
399 $qid = db2_tables($this->_connectionID
);
401 $rs = new ADORecordSet_db2($qid);
403 $ADODB_FETCH_MODE = $savem;
409 $arr =& $rs->GetArray();
415 $isview = strncmp($ttype,'V',1) === 0;
417 for ($i=0; $i < sizeof($arr); $i++
) {
418 if (!$arr[$i][2]) continue;
420 $schemaval = ($schema) ?
$arr[$i][1].'.' : '';
423 if (strncmp($type,'V',1) === 0) $arr2[] = $schemaval.$arr[$i][2];
424 } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $schemaval.$arr[$i][2];
425 } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $schemaval.$arr[$i][2];
431 See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2datetime_data_type_changes.asp
432 / SQL data type codes /
433 #define SQL_UNKNOWN_TYPE 0
435 #define SQL_NUMERIC 2
436 #define SQL_DECIMAL 3
437 #define SQL_INTEGER 4
438 #define SQL_SMALLINT 5
442 #if (DB2VER >= 0x0300)
443 #define SQL_DATETIME 9
445 #define SQL_VARCHAR 12
448 / One-parameter shortcuts for date/time data types /
449 #if (DB2VER >= 0x0300)
450 #define SQL_TYPE_DATE 91
451 #define SQL_TYPE_TIME 92
452 #define SQL_TYPE_TIMESTAMP 93
454 #define SQL_UNICODE (-95)
455 #define SQL_UNICODE_VARCHAR (-96)
456 #define SQL_UNICODE_LONGVARCHAR (-97)
458 function DB2Types($t)
460 switch ((integer)$t) {
488 case -11: // uniqidentifier
498 function &MetaColumns($table)
500 global $ADODB_FETCH_MODE;
503 if ($this->uCaseTables
) $table = strtoupper($table);
505 $this->_findschema($table,$schema);
507 $savem = $ADODB_FETCH_MODE;
508 $ADODB_FETCH_MODE = ADODB_FETCH_NUM
;
511 $qid = db2_columns($this->_connectionID
, "", $schema, $table, $colname);
512 if (empty($qid)) return $false;
514 $rs =& new ADORecordSet_db2($qid);
515 $ADODB_FETCH_MODE = $savem;
517 if (!$rs) return $false;
538 if (strtoupper(trim($rs->fields
[2])) == $table && (!$schema ||
strtoupper($rs->fields
[1]) == $schema)) {
539 $fld = new ADOFieldObject();
540 $fld->name
= $rs->fields
[3];
541 $fld->type
= $this->DB2Types($rs->fields
[4]);
543 // ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp
544 // access uses precision to store length for char/varchar
545 if ($fld->type
== 'C' or $fld->type
== 'X') {
546 if ($rs->fields
[4] <= -95) // UNICODE
547 $fld->max_length
= $rs->fields
[7]/2;
549 $fld->max_length
= $rs->fields
[7];
551 $fld->max_length
= $rs->fields
[7];
552 $fld->not_null
= !empty($rs->fields
[10]);
553 $fld->scale
= $rs->fields
[8];
554 $fld->primary_key
= false;
555 $retarr[strtoupper($fld->name
)] = $fld;
556 } else if (sizeof($retarr)>0)
561 if (empty($retarr)) $retarr = false;
563 $qid = db2_primary_keys($this->_connectionID
, "", $schema, $table);
564 if (empty($qid)) return $false;
566 $rs =& new ADORecordSet_db2($qid);
567 $ADODB_FETCH_MODE = $savem;
569 if (!$rs) return $retarr;
582 if (strtoupper(trim($rs->fields
[2])) == $table && (!$schema ||
strtoupper($rs->fields
[1]) == $schema)) {
583 $retarr[strtoupper($rs->fields
[3])]->primary_key
= true;
584 } else if (sizeof($retarr)>0)
590 if (empty($retarr)) $retarr = false;
594 function Prepare($sql)
596 if (! $this->_bindInputArray
) return $sql; // no binding
597 $stmt = db2_prepare($this->_connectionID
,$sql);
599 // we don't know whether db2 driver is parsing prepared stmts, so just return sql
602 return array($sql,$stmt,false);
605 /* returns queryID or false */
606 function _query($sql,$inputarr=false)
608 GLOBAL $php_errormsg;
609 if (isset($php_errormsg)) $php_errormsg = '';
613 if (is_array($sql)) {
616 $stmtid = db2_prepare($this->_connectionID
,$sql);
618 if ($stmtid == false) {
619 $this->_errorMsg
= isset($php_errormsg) ?
$php_errormsg : '';
624 if (! db2_execute($stmtid,$inputarr)) {
625 if ($this->_haserrorfunctions
) {
626 $this->_errorMsg
= db2_stmt_errormsg();
627 $this->_errorCode
= db2_stmt_error();
632 } else if (is_array($sql)) {
634 if (!db2_execute($stmtid)) {
635 if ($this->_haserrorfunctions
) {
636 $this->_errorMsg
= db2_stmt_errormsg();
637 $this->_errorCode
= db2_stmt_error();
642 $stmtid = @db2_exec
($this->_connectionID
,$sql);
644 $this->_lastAffectedRows
= 0;
646 if (@db2_num_fields
($stmtid) == 0) {
647 $this->_lastAffectedRows
= db2_num_rows($stmtid);
650 $this->_lastAffectedRows
= 0;
653 if ($this->_haserrorfunctions
) {
654 $this->_errorMsg
= '';
655 $this->_errorCode
= 0;
657 $this->_errorMsg
= isset($php_errormsg) ?
$php_errormsg : '';
659 if ($this->_haserrorfunctions
) {
660 $this->_errorMsg
= db2_stmt_errormsg();
661 $this->_errorCode
= db2_stmt_error();
663 $this->_errorMsg
= isset($php_errormsg) ?
$php_errormsg : '';
670 Insert a null into the blob field of the table first.
671 Then use UpdateBlob to store the blob.
675 $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
676 $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
678 function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
680 return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
683 // returns true or false
686 $ret = @db2_close
($this->_connectionID
);
687 $this->_connectionID
= false;
691 function _affectedrows()
693 return $this->_lastAffectedRows
;
698 /*--------------------------------------------------------------------------------------
699 Class Name: Recordset
700 --------------------------------------------------------------------------------------*/
702 class ADORecordSet_db2
extends ADORecordSet
{
705 var $databaseType = "db2";
706 var $dataProvider = "db2";
709 function ADORecordSet_db2($id,$mode=false)
711 if ($mode === false) {
712 global $ADODB_FETCH_MODE;
713 $mode = $ADODB_FETCH_MODE;
715 $this->fetchMode
= $mode;
717 $this->_queryID
= $id;
721 // returns the field object
722 function &FetchField($offset = -1)
724 $o= new ADOFieldObject();
725 $o->name
= @db2_field_name
($this->_queryID
,$offset);
726 $o->type
= @db2_field_type
($this->_queryID
,$offset);
727 $o->max_length
= db2_field_width($this->_queryID
,$offset);
728 if (ADODB_ASSOC_CASE
== 0) $o->name
= strtolower($o->name
);
729 else if (ADODB_ASSOC_CASE
== 1) $o->name
= strtoupper($o->name
);
733 /* Use associative array to get fields array */
734 function Fields($colname)
736 if ($this->fetchMode
& ADODB_FETCH_ASSOC
) return $this->fields
[$colname];
738 $this->bind
= array();
739 for ($i=0; $i < $this->_numOfFields
; $i++
) {
740 $o = $this->FetchField($i);
741 $this->bind
[strtoupper($o->name
)] = $i;
745 return $this->fields
[$this->bind
[strtoupper($colname)]];
751 global $ADODB_COUNTRECS;
752 $this->_numOfRows
= ($ADODB_COUNTRECS) ? @db2_num_rows
($this->_queryID
) : -1;
753 $this->_numOfFields
= @db2_num_fields
($this->_queryID
);
754 // some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0
755 if ($this->_numOfRows
== 0) $this->_numOfRows
= -1;
763 // speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated
764 function &GetArrayLimit($nrows,$offset=-1)
767 $rs =& $this->GetArray($nrows);
770 $savem = $this->fetchMode
;
771 $this->fetchMode
= ADODB_FETCH_NUM
;
772 $this->Move($offset);
773 $this->fetchMode
= $savem;
775 if ($this->fetchMode
& ADODB_FETCH_ASSOC
) {
776 $this->fields
=& $this->GetRowAssoc(ADODB_ASSOC_CASE
);
781 while (!$this->EOF
&& $nrows != $cnt) {
782 $results[$cnt++
] = $this->fields
;
792 if ($this->_numOfRows
!= 0 && !$this->EOF
) {
793 $this->_currentRow++
;
795 $this->fields
= @db2_fetch_array
($this->_queryID
);
797 if ($this->fetchMode
& ADODB_FETCH_ASSOC
) {
798 $this->fields
=& $this->GetRowAssoc(ADODB_ASSOC_CASE
);
803 $this->fields
= false;
811 $this->fields
= db2_fetch_array($this->_queryID
);
813 if ($this->fetchMode
& ADODB_FETCH_ASSOC
) {
814 $this->fields
=& $this->GetRowAssoc(ADODB_ASSOC_CASE
);
818 $this->fields
= false;
824 return @db2_free_result
($this->_queryID
);