ChangeLog
[Packages/TYPO3.CMS.git] / typo3 / sysext / adodb / adodb / drivers / adodb-db2.inc.php
1 <?php
2 /*
3 V4.90 8 June 2006 (c) 2006 John Lim (jlim#natsoft.com.my). All rights reserved.
4
5 This is a version of the ADODB driver for DB2. It uses the 'ibm_db2' PECL extension for PHP
6 (http://pecl.php.net/package/ibm_db2), which in turn requires DB2 V8.2.2.
7
8 Tested with PHP 5.1.1 and Apache 2.0.55 on Windows XP SP2.
9
10 This file was ported from "adodb-odbc.inc.php" by Larry Menard, "larry.menard@rogers.com".
11 I ripped out what I believed to be a lot of redundant or obsolete code, but there are
12 probably still some remnants of the ODBC support in this file; I'm relying on reviewers
13 of this code to point out any other things that can be removed.
14 */
15
16 // security - hide paths
17 if (!defined('ADODB_DIR')) die();
18
19 define("_ADODB_DB2_LAYER", 2 );
20
21 /*--------------------------------------------------------------------------------------
22 --------------------------------------------------------------------------------------*/
23
24
25 class ADODB_db2 extends ADOConnection {
26 var $databaseType = "db2";
27 var $fmtDate = "'Y-m-d'";
28 var $concat_operator = '||';
29
30 var $sysTime = 'CURRENT TIME';
31 var $sysDate = 'CURRENT DATE';
32 var $sysTimeStamp = 'CURRENT TIMESTAMP';
33
34 var $fmtTimeStamp = "'Y-m-d-H.i.s'";
35 #var $fmtTimeStamp = "'Y-m-d, h:i:sA'";
36 var $replaceQuote = "''"; // string to use to replace quotes
37 var $dataProvider = "db2";
38 var $hasAffectedRows = true;
39
40 var $binmode = DB2_BINARY;
41
42 var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive
43 // breaking backward-compat
44 var $_bindInputArray = false;
45 var $_genSeqSQL = "create table %s (id integer)";
46 var $_autocommit = true;
47 var $_haserrorfunctions = true;
48 var $_lastAffectedRows = 0;
49 var $uCaseTables = true; // for meta* functions, uppercase table names
50 var $hasInsertID = true;
51
52 function _insertid()
53 {
54 return ADOConnection::GetOne('VALUES IDENTITY_VAL_LOCAL()');
55 }
56
57 function ADODB_db2()
58 {
59 $this->_haserrorfunctions = ADODB_PHPVER >= 0x4050;
60 }
61
62 // returns true or false
63 function _connect($argDSN, $argUsername, $argPassword, $argDatabasename)
64 {
65 global $php_errormsg;
66
67 if (!function_exists('db2_connect')) {
68 ADOConnection::outp("Warning: The old ODBC based DB2 driver has been renamed 'odbc_db2'. This ADOdb driver calls PHP's native db2 extension.");
69 return null;
70 }
71 // This needs to be set before the connect().
72 // Replaces the odbc_binmode() call that was in Execute()
73 ini_set('ibm_db2.binmode', $this->binmode);
74
75 if ($argDatabasename) {
76 $this->_connectionID = db2_connect($argDatabasename,$argUsername,$argPassword);
77 } else {
78 $this->_connectionID = db2_connect($argDSN,$argUsername,$argPassword);
79 }
80 if (isset($php_errormsg)) $php_errormsg = '';
81
82 // For db2_connect(), there is an optional 4th arg. If present, it must be
83 // an array of valid options. So far, we don't use them.
84
85 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : '';
86 if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
87
88 return $this->_connectionID != false;
89 }
90
91 // returns true or false
92 function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
93 {
94 global $php_errormsg;
95
96 if (!function_exists('db2_connect')) return null;
97
98 // This needs to be set before the connect().
99 // Replaces the odbc_binmode() call that was in Execute()
100 ini_set('ibm_db2.binmode', $this->binmode);
101
102 if (isset($php_errormsg)) $php_errormsg = '';
103 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : '';
104
105 if ($argDatabasename) {
106 $this->_connectionID = db2_pconnect($argDatabasename,$argUsername,$argPassword);
107 } else {
108 $this->_connectionID = db2_pconnect($argDSN,$argUsername,$argPassword);
109 }
110 if (isset($php_errormsg)) $php_errormsg = '';
111
112 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : '';
113 if ($this->_connectionID && $this->autoRollback) @db2_rollback($this->_connectionID);
114 if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
115
116 return $this->_connectionID != false;
117 }
118
119
120 // Format date column in sql string given an input format that understands Y M D
121 function SQLDate($fmt, $col=false)
122 {
123 // use right() and replace() ?
124 if (!$col) $col = $this->sysDate;
125 $s = '';
126
127 $len = strlen($fmt);
128 for ($i=0; $i < $len; $i++) {
129 if ($s) $s .= $this->concat_operator;
130 $ch = $fmt[$i];
131 switch($ch) {
132 case 'Y':
133 case 'y':
134 $s .= "char(year($col))";
135 break;
136 case 'M':
137 $s .= "substr(monthname($col),1,3)";
138 break;
139 case 'm':
140 $s .= "right(digits(month($col)),2)";
141 break;
142 case 'D':
143 case 'd':
144 $s .= "right(digits(day($col)),2)";
145 break;
146 case 'H':
147 case 'h':
148 if ($col != $this->sysDate) $s .= "right(digits(hour($col)),2)";
149 else $s .= "''";
150 break;
151 case 'i':
152 case 'I':
153 if ($col != $this->sysDate)
154 $s .= "right(digits(minute($col)),2)";
155 else $s .= "''";
156 break;
157 case 'S':
158 case 's':
159 if ($col != $this->sysDate)
160 $s .= "right(digits(second($col)),2)";
161 else $s .= "''";
162 break;
163 default:
164 if ($ch == '\\') {
165 $i++;
166 $ch = substr($fmt,$i,1);
167 }
168 $s .= $this->qstr($ch);
169 }
170 }
171 return $s;
172 }
173
174
175 function ServerInfo()
176 {
177
178 if (!empty($this->host) && ADODB_PHPVER >= 0x4300) {
179 $dsn = strtoupper($this->host);
180 $first = true;
181 $found = false;
182
183 if (!function_exists('db2_data_source')) return false;
184
185 while(true) {
186
187 $rez = @db2_data_source($this->_connectionID,
188 $first ? SQL_FETCH_FIRST : SQL_FETCH_NEXT);
189 $first = false;
190 if (!is_array($rez)) break;
191 if (strtoupper($rez['server']) == $dsn) {
192 $found = true;
193 break;
194 }
195 }
196 if (!$found) return ADOConnection::ServerInfo();
197 if (!isset($rez['version'])) $rez['version'] = '';
198 return $rez;
199 } else {
200 return ADOConnection::ServerInfo();
201 }
202 }
203
204
205 function CreateSequence($seqname='adodbseq',$start=1)
206 {
207 if (empty($this->_genSeqSQL)) return false;
208 $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname));
209 if (!$ok) return false;
210 $start -= 1;
211 return $this->Execute("insert into $seqname values($start)");
212 }
213
214 var $_dropSeqSQL = 'drop table %s';
215 function DropSequence($seqname)
216 {
217 if (empty($this->_dropSeqSQL)) return false;
218 return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
219 }
220
221 /*
222 This algorithm is not very efficient, but works even if table locking
223 is not available.
224
225 Will return false if unable to generate an ID after $MAXLOOPS attempts.
226 */
227 function GenID($seq='adodbseq',$start=1)
228 {
229 // if you have to modify the parameter below, your database is overloaded,
230 // or you need to implement generation of id's yourself!
231 $MAXLOOPS = 100;
232 while (--$MAXLOOPS>=0) {
233 $num = $this->GetOne("select id from $seq");
234 if ($num === false) {
235 $this->Execute(sprintf($this->_genSeqSQL ,$seq));
236 $start -= 1;
237 $num = '0';
238 $ok = $this->Execute("insert into $seq values($start)");
239 if (!$ok) return false;
240 }
241 $this->Execute("update $seq set id=id+1 where id=$num");
242
243 if ($this->affected_rows() > 0) {
244 $num += 1;
245 $this->genID = $num;
246 return $num;
247 }
248 }
249 if ($fn = $this->raiseErrorFn) {
250 $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num);
251 }
252 return false;
253 }
254
255
256 function ErrorMsg()
257 {
258 if ($this->_haserrorfunctions) {
259 if ($this->_errorMsg !== false) return $this->_errorMsg;
260 if (empty($this->_connectionID)) return @db2_errormsg();
261 return @db2_errormsg($this->_connectionID);
262 } else return ADOConnection::ErrorMsg();
263 }
264
265 function ErrorNo()
266 {
267
268 if ($this->_haserrorfunctions) {
269 if ($this->_errorCode !== false) {
270 // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
271 return (strlen($this->_errorCode)<=2) ? 0 : $this->_errorCode;
272 }
273
274 if (empty($this->_connectionID)) $e = @db2_error();
275 else $e = @db2_error($this->_connectionID);
276
277 // bug in 4.0.6, error number can be corrupted string (should be 6 digits)
278 // so we check and patch
279 if (strlen($e)<=2) return 0;
280 return $e;
281 } else return ADOConnection::ErrorNo();
282 }
283
284
285
286 function BeginTrans()
287 {
288 if (!$this->hasTransactions) return false;
289 if ($this->transOff) return true;
290 $this->transCnt += 1;
291 $this->_autocommit = false;
292 return db2_autocommit($this->_connectionID,false);
293 }
294
295 function CommitTrans($ok=true)
296 {
297 if ($this->transOff) return true;
298 if (!$ok) return $this->RollbackTrans();
299 if ($this->transCnt) $this->transCnt -= 1;
300 $this->_autocommit = true;
301 $ret = db2_commit($this->_connectionID);
302 db2_autocommit($this->_connectionID,true);
303 return $ret;
304 }
305
306 function RollbackTrans()
307 {
308 if ($this->transOff) return true;
309 if ($this->transCnt) $this->transCnt -= 1;
310 $this->_autocommit = true;
311 $ret = db2_rollback($this->_connectionID);
312 db2_autocommit($this->_connectionID,true);
313 return $ret;
314 }
315
316 function MetaPrimaryKeys($table)
317 {
318 global $ADODB_FETCH_MODE;
319
320 if ($this->uCaseTables) $table = strtoupper($table);
321 $schema = '';
322 $this->_findschema($table,$schema);
323
324 $savem = $ADODB_FETCH_MODE;
325 $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
326 $qid = @db2_primarykeys($this->_connectionID,'',$schema,$table);
327
328 if (!$qid) {
329 $ADODB_FETCH_MODE = $savem;
330 return false;
331 }
332 $rs = new ADORecordSet_db2($qid);
333 $ADODB_FETCH_MODE = $savem;
334
335 if (!$rs) return false;
336
337 $arr =& $rs->GetArray();
338 $rs->Close();
339 $arr2 = array();
340 for ($i=0; $i < sizeof($arr); $i++) {
341 if ($arr[$i][3]) $arr2[] = $arr[$i][3];
342 }
343 return $arr2;
344 }
345
346
347
348 function &MetaTables($ttype=false)
349 {
350 global $ADODB_FETCH_MODE;
351
352 $savem = $ADODB_FETCH_MODE;
353 $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
354 $qid = db2_tables($this->_connectionID);
355
356 $rs = new ADORecordSet_db2($qid);
357
358 $ADODB_FETCH_MODE = $savem;
359 if (!$rs) {
360 $false = false;
361 return $false;
362 }
363
364 $arr =& $rs->GetArray();
365
366 $rs->Close();
367 $arr2 = array();
368
369 if ($ttype) {
370 $isview = strncmp($ttype,'V',1) === 0;
371 }
372 for ($i=0; $i < sizeof($arr); $i++) {
373 if (!$arr[$i][2]) continue;
374 $type = $arr[$i][3];
375 if ($ttype) {
376 if ($isview) {
377 if (strncmp($type,'V',1) === 0) $arr2[] = $arr[$i][2];
378 } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2];
379 } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2];
380 }
381 return $arr2;
382 }
383
384 /*
385 See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2datetime_data_type_changes.asp
386 / SQL data type codes /
387 #define SQL_UNKNOWN_TYPE 0
388 #define SQL_CHAR 1
389 #define SQL_NUMERIC 2
390 #define SQL_DECIMAL 3
391 #define SQL_INTEGER 4
392 #define SQL_SMALLINT 5
393 #define SQL_FLOAT 6
394 #define SQL_REAL 7
395 #define SQL_DOUBLE 8
396 #if (DB2VER >= 0x0300)
397 #define SQL_DATETIME 9
398 #endif
399 #define SQL_VARCHAR 12
400
401
402 / One-parameter shortcuts for date/time data types /
403 #if (DB2VER >= 0x0300)
404 #define SQL_TYPE_DATE 91
405 #define SQL_TYPE_TIME 92
406 #define SQL_TYPE_TIMESTAMP 93
407
408 #define SQL_UNICODE (-95)
409 #define SQL_UNICODE_VARCHAR (-96)
410 #define SQL_UNICODE_LONGVARCHAR (-97)
411 */
412 function DB2Types($t)
413 {
414 switch ((integer)$t) {
415 case 1:
416 case 12:
417 case 0:
418 case -95:
419 case -96:
420 return 'C';
421 case -97:
422 case -1: //text
423 return 'X';
424 case -4: //image
425 return 'B';
426
427 case 9:
428 case 91:
429 return 'D';
430
431 case 10:
432 case 11:
433 case 92:
434 case 93:
435 return 'T';
436
437 case 4:
438 case 5:
439 case -6:
440 return 'I';
441
442 case -11: // uniqidentifier
443 return 'R';
444 case -7: //bit
445 return 'L';
446
447 default:
448 return 'N';
449 }
450 }
451
452 function &MetaColumns($table)
453 {
454 global $ADODB_FETCH_MODE;
455
456 $false = false;
457 if ($this->uCaseTables) $table = strtoupper($table);
458 $schema = '';
459 $this->_findschema($table,$schema);
460
461 $savem = $ADODB_FETCH_MODE;
462 $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
463
464 $colname = "%";
465 $qid = db2_columns($this->_connectionID, "", $schema, $table, $colname);
466 if (empty($qid)) return $false;
467
468 $rs =& new ADORecordSet_db2($qid);
469 $ADODB_FETCH_MODE = $savem;
470
471 if (!$rs) return $false;
472 $rs->_fetch();
473
474 $retarr = array();
475
476 /*
477 $rs->fields indices
478 0 TABLE_QUALIFIER
479 1 TABLE_SCHEM
480 2 TABLE_NAME
481 3 COLUMN_NAME
482 4 DATA_TYPE
483 5 TYPE_NAME
484 6 PRECISION
485 7 LENGTH
486 8 SCALE
487 9 RADIX
488 10 NULLABLE
489 11 REMARKS
490 */
491 while (!$rs->EOF) {
492 if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) {
493 $fld = new ADOFieldObject();
494 $fld->name = $rs->fields[3];
495 $fld->type = $this->DB2Types($rs->fields[4]);
496
497 // ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp
498 // access uses precision to store length for char/varchar
499 if ($fld->type == 'C' or $fld->type == 'X') {
500 if ($rs->fields[4] <= -95) // UNICODE
501 $fld->max_length = $rs->fields[7]/2;
502 else
503 $fld->max_length = $rs->fields[7];
504 } else
505 $fld->max_length = $rs->fields[7];
506 $fld->not_null = !empty($rs->fields[10]);
507 $fld->scale = $rs->fields[8];
508 $retarr[strtoupper($fld->name)] = $fld;
509 } else if (sizeof($retarr)>0)
510 break;
511 $rs->MoveNext();
512 }
513 $rs->Close(); //-- crashes 4.03pl1 -- why?
514
515 if (empty($retarr)) $retarr = false;
516 return $retarr;
517 }
518
519 function Prepare($sql)
520 {
521 if (! $this->_bindInputArray) return $sql; // no binding
522 $stmt = db2_prepare($this->_connectionID,$sql);
523 if (!$stmt) {
524 // we don't know whether db2 driver is parsing prepared stmts, so just return sql
525 return $sql;
526 }
527 return array($sql,$stmt,false);
528 }
529
530 /* returns queryID or false */
531 function _query($sql,$inputarr=false)
532 {
533 GLOBAL $php_errormsg;
534 if (isset($php_errormsg)) $php_errormsg = '';
535 $this->_error = '';
536
537 if ($inputarr) {
538 if (is_array($sql)) {
539 $stmtid = $sql[1];
540 } else {
541 $stmtid = db2_prepare($this->_connectionID,$sql);
542
543 if ($stmtid == false) {
544 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : '';
545 return false;
546 }
547 }
548
549 if (! db2_execute($stmtid,$inputarr)) {
550 if ($this->_haserrorfunctions) {
551 $this->_errorMsg = db2_errormsg();
552 $this->_errorCode = db2_error();
553 }
554 return false;
555 }
556
557 } else if (is_array($sql)) {
558 $stmtid = $sql[1];
559 if (!db2_execute($stmtid)) {
560 if ($this->_haserrorfunctions) {
561 $this->_errorMsg = db2_errormsg();
562 $this->_errorCode = db2_error();
563 }
564 return false;
565 }
566 } else
567 $stmtid = db2_exec($this->_connectionID,$sql);
568
569 $this->_lastAffectedRows = 0;
570 if ($stmtid) {
571 if (@db2_num_fields($stmtid) == 0) {
572 $this->_lastAffectedRows = db2_num_rows($stmtid);
573 $stmtid = true;
574 } else {
575 $this->_lastAffectedRows = 0;
576 }
577
578 if ($this->_haserrorfunctions) {
579 $this->_errorMsg = '';
580 $this->_errorCode = 0;
581 } else
582 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : '';
583 } else {
584 if ($this->_haserrorfunctions) {
585 $this->_errorMsg = db2_stmt_errormsg();
586 $this->_errorCode = db2_stmt_error();
587 } else
588 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : '';
589 }
590 return $stmtid;
591 }
592
593 /*
594 Insert a null into the blob field of the table first.
595 Then use UpdateBlob to store the blob.
596
597 Usage:
598
599 $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
600 $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
601 */
602 function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
603 {
604 return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
605 }
606
607 // returns true or false
608 function _close()
609 {
610 $ret = @db2_close($this->_connectionID);
611 $this->_connectionID = false;
612 return $ret;
613 }
614
615 function _affectedrows()
616 {
617 return $this->_lastAffectedRows;
618 }
619
620 }
621
622 /*--------------------------------------------------------------------------------------
623 Class Name: Recordset
624 --------------------------------------------------------------------------------------*/
625
626 class ADORecordSet_db2 extends ADORecordSet {
627
628 var $bind = false;
629 var $databaseType = "db2";
630 var $dataProvider = "db2";
631 var $useFetchArray;
632
633 function ADORecordSet_db2($id,$mode=false)
634 {
635 if ($mode === false) {
636 global $ADODB_FETCH_MODE;
637 $mode = $ADODB_FETCH_MODE;
638 }
639 $this->fetchMode = $mode;
640
641 $this->_queryID = $id;
642 }
643
644
645 // returns the field object
646 function &FetchField($offset = -1)
647 {
648 $o= new ADOFieldObject();
649 $o->name = @db2_field_name($this->_queryID,$offset);
650 $o->type = @db2_field_type($this->_queryID,$offset);
651 $o->max_length = db2_field_width($this->_queryID,$offset);
652 if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name);
653 else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name);
654 return $o;
655 }
656
657 /* Use associative array to get fields array */
658 function Fields($colname)
659 {
660 if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
661 if (!$this->bind) {
662 $this->bind = array();
663 for ($i=0; $i < $this->_numOfFields; $i++) {
664 $o = $this->FetchField($i);
665 $this->bind[strtoupper($o->name)] = $i;
666 }
667 }
668
669 return $this->fields[$this->bind[strtoupper($colname)]];
670 }
671
672
673 function _initrs()
674 {
675 global $ADODB_COUNTRECS;
676 $this->_numOfRows = ($ADODB_COUNTRECS) ? @db2_num_rows($this->_queryID) : -1;
677 $this->_numOfFields = @db2_num_fields($this->_queryID);
678 // some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0
679 if ($this->_numOfRows == 0) $this->_numOfRows = -1;
680 }
681
682 function _seek($row)
683 {
684 return false;
685 }
686
687 // speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated
688 function &GetArrayLimit($nrows,$offset=-1)
689 {
690 if ($offset <= 0) {
691 $rs =& $this->GetArray($nrows);
692 return $rs;
693 }
694 $savem = $this->fetchMode;
695 $this->fetchMode = ADODB_FETCH_NUM;
696 $this->Move($offset);
697 $this->fetchMode = $savem;
698
699 if ($this->fetchMode & ADODB_FETCH_ASSOC) {
700 $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE);
701 }
702
703 $results = array();
704 $cnt = 0;
705 while (!$this->EOF && $nrows != $cnt) {
706 $results[$cnt++] = $this->fields;
707 $this->MoveNext();
708 }
709
710 return $results;
711 }
712
713
714 function MoveNext()
715 {
716 if ($this->_numOfRows != 0 && !$this->EOF) {
717 $this->_currentRow++;
718
719 $this->fields = @db2_fetch_array($this->_queryID);
720 if ($this->fields) {
721 if ($this->fetchMode & ADODB_FETCH_ASSOC) {
722 $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE);
723 }
724 return true;
725 }
726 }
727 $this->fields = false;
728 $this->EOF = true;
729 return false;
730 }
731
732 function _fetch()
733 {
734
735 $this->fields = db2_fetch_array($this->_queryID);
736 if ($this->fields) {
737 if ($this->fetchMode & ADODB_FETCH_ASSOC) {
738 $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE);
739 }
740 return true;
741 }
742 $this->fields = false;
743 return false;
744 }
745
746 function _close()
747 {
748 return @db2_free_result($this->_queryID);
749 }
750
751 }
752 ?>