Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
/**
3
 * IBM DB2 Native Client driver.
4
 *
5
 * Originally DB2 drivers were dependent on an ODBC driver, and some installations
6
 * may still use that. To use an ODBC driver connection, use the odbc_db2
7
 * ADOdb driver. For Linux, you need the 'ibm_db2' PECL extension for PHP,
8
 * For Windows, you need to locate an appropriate version of the php_ibm_db2.dll,
9
 * as well as the IBM data server client software.
10
 * This is basically a full rewrite of the original driver, for information
11
 * about all the changes, see the update information on the ADOdb website
12
 * for version 5.21.0.
13
 *
14
 * @link http://pecl.php.net/package/ibm_db2 PECL Extension For DB2
15
 *
16
 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
17
 *
18
 * @package ADOdb
19
 * @link https://adodb.org Project's web site and documentation
20
 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
21
 *
22
 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
23
 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
24
 * any later version. This means you can use it in proprietary products.
25
 * See the LICENSE.md file distributed with this source code for details.
26
 * @license BSD-3-Clause
27
 * @license LGPL-2.1-or-later
28
 *
29
 * @copyright 2000-2013 John Lim
30
 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
31
 * @author Mark Newnham
32
 */
33
 
34
// security - hide paths
35
if (!defined('ADODB_DIR')) die();
36
 
37
define("_ADODB_DB2_LAYER", 2 );
38
 
39
 
40
class ADODB_db2 extends ADOConnection {
41
	var $databaseType = "db2";
42
	var $fmtDate = "'Y-m-d'";
43
	var $concat_operator = '||';
44
 
45
	var $sysTime = 'CURRENT TIME';
46
	var $sysDate = 'CURRENT DATE';
47
	var $sysTimeStamp = 'CURRENT TIMESTAMP';
48
 
49
	var $fmtTimeStamp = "'Y-m-d H:i:s'";
50
	var $replaceQuote = "''"; // string to use to replace quotes
51
	var $dataProvider = "db2";
52
	var $hasAffectedRows = true;
53
 
54
	var $binmode = DB2_BINARY;
55
 
56
	/*
57
	* setting this to true will make array elements in FETCH_ASSOC
58
	* mode case-sensitive breaking backward-compat
59
	*/
60
	var $useFetchArray = false;
61
	var $_bindInputArray = true;
62
	var $_genIDSQL = "VALUES NEXTVAL FOR %s";
63
	var $_genSeqSQL = "
64
	CREATE SEQUENCE %s START WITH %s
65
	NO MAXVALUE NO CYCLE INCREMENT BY 1 NO CACHE
66
	";
67
	var $_dropSeqSQL = "DROP SEQUENCE %s";
68
	var $_autocommit = true;
69
	var $_lastAffectedRows = 0;
70
	var $hasInsertID = true;
71
	var $hasGenID    = true;
72
 
73
	/*
74
	 * Character used to wrap column and table names for escaping special
75
	 * characters in column and table names as well as forcing upper and
76
	 * lower case
77
	 */
78
	public $nameQuote = '"';
79
 
80
	/*
81
	 * Holds information about the stored procedure request
82
	 * currently being built
83
	 */
84
	private $storedProcedureParameters = false;
85
 
86
 
87
	function __construct() {}
88
 
89
	protected function _insertID($table = '', $column = '')
90
	{
91
		return ADOConnection::GetOne('VALUES IDENTITY_VAL_LOCAL()');
92
	}
93
 
94
	public function _connect($argDSN, $argUsername, $argPassword, $argDatabasename)
95
	{
96
		return $this->doDB2Connect($argDSN, $argUsername, $argPassword, $argDatabasename);
97
	}
98
 
99
	public function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename)
100
	{
101
		return $this->doDB2Connect($argDSN, $argUsername, $argPassword, $argDatabasename,true);
102
	}
103
 
104
	private function doDB2Connect($argDSN, $argUsername, $argPassword, $argDatabasename, $persistent=false)
105
	{
106
 
107
		if (!function_exists('db2_connect')) {
108
			ADOConnection::outp("DB2 extension not installed.");
109
			return null;
110
		}
111
 
1441 ariadna 112
		$connectionParameters = $this->unpackParameters(
113
			$argDSN,
114
			$argUsername,
115
			$argPassword,
116
			$argDatabasename
117
		);
1 efrain 118
 
119
		if ($connectionParameters == null)
120
		{
121
			/*
122
			 * Error thrown
123
			 */
124
			return null;
125
		}
126
 
127
		$argDSN 		         = $connectionParameters['dsn'];
128
		$argUsername 	         = $connectionParameters['uid'];
129
		$argPassword 	         = $connectionParameters['pwd'];
130
		$argDatabasename         = $connectionParameters['database'];
131
		$useCataloguedConnection = $connectionParameters['catalogue'];
132
 
133
		if ($this->debug){
1441 ariadna 134
			if (strcmp($argDSN,'*LOCAL') == 0)
135
			{
136
				$connectMessage = '*LOCAL connection';
137
			}
138
			else if ($useCataloguedConnection)
139
			{
1 efrain 140
				$connectMessage = "Catalogued connection using parameters: ";
141
				$connectMessage .= "DB=$argDatabasename / ";
142
				$connectMessage .= "UID=$argUsername / ";
143
				$connectMessage .= "PWD=$argPassword";
144
			}
145
			else
146
			{
147
				$connectMessage = "Uncatalogued connection using DSN: $argDSN";
148
			}
149
			ADOConnection::outp($connectMessage);
150
		}
1441 ariadna 151
 
1 efrain 152
		/*
153
		 * This needs to be set before the connect().
154
		 */
155
		ini_set('ibm_db2.binmode', $this->binmode);
156
 
157
		if ($persistent)
158
			$db2Function = 'db2_pconnect';
159
		else
160
			$db2Function = 'db2_connect';
161
 
162
		/*
163
		* We need to flatten out the connectionParameters
164
		*/
165
 
166
		$db2Options = array();
167
		if ($this->connectionParameters)
168
		{
169
			foreach($this->connectionParameters as $p)
170
				foreach($p as $k=>$v)
171
					$db2Options[$k] = $v;
172
		}
173
 
174
		if ($useCataloguedConnection)
1441 ariadna 175
		{
1 efrain 176
			$this->_connectionID = $db2Function($argDatabasename,
177
												$argUsername,
178
												$argPassword,
179
												$db2Options);
1441 ariadna 180
		}
1 efrain 181
		else
1441 ariadna 182
 
1 efrain 183
			$this->_connectionID = $db2Function($argDSN,
1441 ariadna 184
												'',
185
												'',
1 efrain 186
												$db2Options);
187
 
188
 
189
		$this->_errorMsg = @db2_conn_errormsg();
190
 
191
		if ($this->_connectionID && $this->connectStmt)
192
			$this->execute($this->connectStmt);
193
 
1441 ariadna 194
		if ($this->_connectionID && $argDatabasename)
195
			$this->execute("SET SCHEMA=$argDatabasename");
196
 
1 efrain 197
		return $this->_connectionID != false;
198
 
199
	}
200
 
201
	/**
202
	 * Validates and preprocesses the passed parameters for consistency
203
	 *
204
	 * @param	string	$argDSN				Either DSN or database
205
	 * @param	string	$argUsername		User name or null
206
	 * @param	string	$argPassword		Password or null
207
	 * @param	string	$argDatabasename	Either DSN or database
208
	 *
209
	 * @return mixed  array if correct, null if not
210
	 */
211
	private function unpackParameters($argDSN, $argUsername, $argPassword, $argDatabasename)
212
	{
213
 
214
 
1441 ariadna 215
		$connectionParameters = array(
216
			'dsn'=>'',
217
			'uid'=>'',
218
			'pwd'=>'',
219
			'database'=>'',
220
			'catalogue'=>true
221
		);
1 efrain 222
 
223
		/*
1441 ariadna 224
		* Shortcut for *LOCAL
225
		*/
226
		if (strcmp($argDSN,'*LOCAL') == 0)
227
		{
228
			$connectionParameters['dsn']      = $argDSN;
229
			$connectionParameters['database'] = $argDatabasename;
230
			$connectionParameters['catalogue'] = false;
231
 
232
			return $connectionParameters;
233
		}
234
 
235
		/*
1 efrain 236
		 * Uou can either connect to a catalogued connection
237
		 * with a database name e.g. 'SAMPLE'
238
		 * or an uncatalogued connection with a DSN like connection
239
		 * DATABASE=database;HOSTNAME=hostname;PORT=port;PROTOCOL=TCPIP;UID=username;PWD=password;
240
		 */
241
 
242
		if (!$argDSN && !$argDatabasename)
243
		{
244
			$errorMessage = 'Supply either catalogued or uncatalogued connection parameters';
245
			$this->_errorMsg = $errorMessage;
246
			if ($this->debug)
247
				ADOConnection::outp($errorMessage);
248
			return null;
249
		}
250
 
251
		$useCataloguedConnection = true;
252
		$schemaName 			 = '';
253
 
254
		if ($argDSN && $argDatabasename)
255
		{
256
			/*
257
			 * If a catalogued connection if provided,
258
			 * as well as user and password
259
			 * that will take priority
260
			 */
261
			if ($argUsername && $argPassword && !$this->isDsn($argDatabasename))
262
			{
263
				if ($this->debug){
264
					$errorMessage = 'Warning: Because you provided user,';
265
					$errorMessage.= 'password and database, DSN connection ';
266
					$errorMessage.= 'parameters were discarded';
267
					ADOConnection::outp($errorMessage);
268
 
269
				}
270
				$argDSN = '';
271
			}
272
			else if ($this->isDsn($argDSN) && $this->isDsn($argDatabasename))
273
			{
274
				$errorMessage = 'Supply uncatalogued connection parameters ';
275
				$errorMessage.= 'in either the database or DSN arguments, ';
276
				$errorMessage.= 'but not both';
277
 
278
				if ($this->debug)
279
					ADOConnection::outp($errorMessage);
280
				return null;
281
			}
282
		}
283
 
284
		if (!$this->isDsn($argDSN) && $this->isDsn($argDatabasename))
285
		{
286
			/*
287
			 * Switch them around for next test
288
			 */
289
			$temp           = $argDSN;
290
			$argDsn         = $argDatabasename;
291
			$argDatabasenME = $temp;
292
		}
293
 
294
		if ($this->isDsn($argDSN))
295
		{
296
 
297
			if (!preg_match('/uid=/i',$argDSN)
298
			||  !preg_match('/pwd=/i',$argDSN))
299
			{
300
				$errorMessage = 'For uncatalogued connections, provide ';
301
				$errorMessage.= 'both UID and PWD in the connection string';
302
 
303
				if ($this->debug)
304
					ADOConnection::outp($errorMessage);
305
				return null;
306
			}
307
 
308
			if (preg_match('/database=/i',$argDSN))
309
			{
310
				if ($argDatabasename)
311
				{
312
					$argDatabasename = '';
313
					if ($this->debug)
314
					{
315
						$errorMessage = 'Warning: Because you provided ';
316
						$errorMessage.= 'database information in the DSN ';
317
						$errorMessage.= 'parameters, the supplied database ';
318
						$errorMessage.= 'name was discarded';
319
						ADOConnection::outp($errorMessage);
320
					}
321
				}
322
				$useCataloguedConnection = false;
323
 
324
			}
325
			elseif ($argDatabasename)
326
			{
327
				$this->database = $argDatabasename;
328
				$argDSN .= ';database=' . $argDatabasename;
329
				$argDatabasename = '';
330
				$useCataloguedConnection = false;
331
 
332
			}
333
			else
334
			{
335
				$errorMessage = 'Uncatalogued connection parameters ';
336
				$errorMessage.= 'must contain a database= argument';
337
 
338
				if ($this->debug)
339
					ADOConnection::outp($errorMessage);
340
				return null;
341
			}
342
		}
343
 
344
		if ($argDSN && !$argDatabasename && $useCataloguedConnection)
345
		{
346
			$argDatabasename = $argDSN;
347
			$argDSN          = '';
348
		}
349
 
350
 
351
		if ($useCataloguedConnection
352
		&& (!$argDatabasename
353
		|| !$argUsername
354
		|| !$argPassword))
355
		{
356
 
357
			$errorMessage = 'For catalogued connections, provide ';
358
			$errorMessage.= 'database, username and password';
359
			$this->_errorMsg = $errorMessage;
360
			if ($this->debug)
361
				ADOConnection::outp($errorMessage);
362
			return null;
363
 
364
		}
365
 
366
		if ($argDatabasename)
367
			$this->database = $argDatabasename;
368
		elseif (!$this->database)
369
			$this->database = $this->getDatabasenameFromDsn($argDSN);
370
 
371
 
372
		$connectionParameters = array('dsn'=>$argDSN,
373
									  'uid'=>$argUsername,
374
									  'pwd'=>$argPassword,
375
									  'database'=>$argDatabasename,
376
									  'catalogue'=>$useCataloguedConnection
377
									  );
378
 
379
		return $connectionParameters;
380
 
381
	}
382
 
383
	/**
384
	  * Does the provided string look like a DSN
385
	  *
386
	  * @param	string	$dsnString
387
	  *
388
	  * @return bool
389
	  */
390
	private function isDsn($dsnString){
391
		$dsnArray = preg_split('/[;=]+/',$dsnString);
392
		if (count($dsnArray) > 2)
393
			return true;
394
		return false;
395
	}
396
 
397
 
398
	/**
399
	  * Gets the database name from the DSN
400
	  *
401
	  * @param	string	$dsnString
402
	  *
403
	  * @return string
404
	  */
405
	private function getDatabasenameFromDsn($dsnString){
406
 
407
		$dsnArray = preg_split('/[;=]+/',$dsnString);
408
		$dbIndex  = array_search('database',$dsnArray);
409
 
410
		return $dsnArray[$dbIndex + 1];
411
	}
412
 
413
 
414
	/**
415
	* format and return date string in database timestamp format
416
	*
417
	* @param	mixed	$ts		either a string or a unixtime
418
	* @param	bool	$isField	discarded
419
	*
420
	* @return string
421
	*/
422
	function dbTimeStamp($ts,$isField=false)
423
	{
424
		if (empty($ts) && $ts !== 0) return 'null';
425
		if (is_string($ts)) $ts = ADORecordSet::unixTimeStamp($ts);
426
		return 'TO_DATE('.adodb_date($this->fmtTimeStamp,$ts).",'YYYY-MM-DD HH24:MI:SS')";
427
	}
428
 
429
	/**
430
	* Format date column in sql string given an input format that understands Y M D
431
	*
432
	* @param	string	$fmt
433
	* @param	bool	$col
434
	*
435
	* @return string
436
	*/
437
	function sqlDate($fmt, $col=false)
438
	{
439
		if (!$col) $col = $this->sysDate;
440
 
441
		/* use TO_CHAR() if $fmt is TO_CHAR() allowed fmt */
442
		if ($fmt== 'Y-m-d H:i:s')
443
			return 'TO_CHAR('.$col.", 'YYYY-MM-DD HH24:MI:SS')";
444
 
445
		$s = '';
446
 
447
		$len = strlen($fmt);
448
		for ($i=0; $i < $len; $i++) {
449
			if ($s) $s .= $this->concat_operator;
450
			$ch = $fmt[$i];
451
			switch($ch) {
452
			case 'Y':
453
			case 'y':
454
				if ($len==1) return "year($col)";
455
				$s .= "char(year($col))";
456
				break;
457
			case 'M':
458
				if ($len==1) return "monthname($col)";
459
				$s .= "substr(monthname($col),1,3)";
460
				break;
461
			case 'm':
462
				if ($len==1) return "month($col)";
463
				$s .= "right(digits(month($col)),2)";
464
				break;
465
			case 'D':
466
			case 'd':
467
				if ($len==1) return "day($col)";
468
				$s .= "right(digits(day($col)),2)";
469
				break;
470
			case 'H':
471
			case 'h':
472
				if ($len==1) return "hour($col)";
473
				if ($col != $this->sysDate) $s .= "right(digits(hour($col)),2)";
474
				else $s .= "''";
475
				break;
476
			case 'i':
477
			case 'I':
478
				if ($len==1) return "minute($col)";
479
				if ($col != $this->sysDate)
480
					$s .= "right(digits(minute($col)),2)";
481
					else $s .= "''";
482
				break;
483
			case 'S':
484
			case 's':
485
				if ($len==1) return "second($col)";
486
				if ($col != $this->sysDate)
487
					$s .= "right(digits(second($col)),2)";
488
				else $s .= "''";
489
				break;
490
			default:
491
				if ($ch == '\\') {
492
					$i++;
493
					$ch = substr($fmt,$i,1);
494
				}
495
				$s .= $this->qstr($ch);
496
			}
497
		}
498
		return $s;
499
	}
500
 
501
 
502
	function serverInfo()
503
	{
504
		$sql = "SELECT service_level, fixpack_num
505
				  FROM TABLE(sysproc.env_get_inst_info())
506
					AS INSTANCEINFO";
507
		$row = $this->GetRow($sql);
508
 
509
 
510
		if ($row) {
511
			$info['version'] = $row[0].':'.$row[1];
512
			$info['fixpack'] = $row[1];
513
			$info['description'] = '';
514
		} else {
515
			return ADOConnection::serverInfo();
516
		}
517
 
518
		return $info;
519
	}
520
 
521
	function createSequence($seqname='adodbseq',$start=1)
522
	{
523
		if (empty($this->_genSeqSQL))
524
			return false;
525
 
526
		$ok = $this->execute(sprintf($this->_genSeqSQL,$seqname,$start));
527
		if (!$ok)
528
			return false;
529
		return true;
530
	}
531
 
532
	function dropSequence($seqname='adodbseq')
533
	{
534
		if (empty($this->_dropSeqSQL)) return false;
535
		return $this->execute(sprintf($this->_dropSeqSQL,$seqname));
536
	}
537
 
538
	function selectLimit($sql,$nrows=-1,$offset=-1,$inputArr=false,$secs2cache=0)
539
	{
540
		$nrows = (integer) $nrows;
541
 
542
		if ($offset <= 0)
543
		{
544
			if ($nrows >= 0)
545
				$sql .=  " FETCH FIRST $nrows ROWS ONLY ";
546
 
547
			$rs = $this->execute($sql,$inputArr);
548
 
549
		}
550
		else
551
		{
552
			if ($offset > 0 && $nrows < 0);
553
 
554
			else
555
			{
556
				$nrows += $offset;
557
				$sql .=  " FETCH FIRST $nrows ROWS ONLY ";
558
			}
559
 
560
			/*
561
			 * DB2 has no native support for mid table offset
562
			 */
563
			$rs = ADOConnection::selectLimit($sql,$nrows,$offset,$inputArr);
564
 
565
		}
566
 
567
		return $rs;
568
	}
569
 
570
 
571
	function errorMsg()
572
	{
573
		if ($this->_errorMsg !== false)
574
			return $this->_errorMsg;
575
 
576
		if (empty($this->_connectionID))
577
			return @db2_conn_errormsg();
578
 
579
		return @db2_conn_errormsg($this->_connectionID);
580
	}
581
 
582
	function errorNo()
583
	{
584
 
585
		if ($this->_errorCode !== false)
586
			return $this->_errorCode;
587
 
588
 
589
		if (empty($this->_connectionID))
590
			$e = @db2_conn_error();
591
 
592
		else
593
			$e = @db2_conn_error($this->_connectionID);
594
 
595
		return $e;
596
	}
597
 
598
 
599
 
600
	function beginTrans()
601
	{
602
		if (!$this->hasTransactions)
603
			return false;
604
		if ($this->transOff)
605
			return true;
606
 
607
		$this->transCnt += 1;
608
 
609
		$this->_autocommit = false;
610
 
611
		return db2_autocommit($this->_connectionID,false);
612
	}
613
 
614
	function CommitTrans($ok=true)
615
	{
616
		if ($this->transOff)
617
			return true;
618
 
619
		if (!$ok)
620
			return $this->RollbackTrans();
621
 
622
		if ($this->transCnt)
623
			$this->transCnt -= 1;
624
 
625
		$this->_autocommit = true;
626
		$ret = @db2_commit($this->_connectionID);
627
		@db2_autocommit($this->_connectionID,true);
628
		return $ret;
629
	}
630
 
631
	function RollbackTrans()
632
	{
633
		if ($this->transOff) return true;
634
		if ($this->transCnt) $this->transCnt -= 1;
635
		$this->_autocommit = true;
636
		$ret = @db2_rollback($this->_connectionID);
637
		@db2_autocommit($this->_connectionID,true);
638
		return $ret;
639
	}
640
 
641
	/**
642
	 * Return a list of Primary Keys for a specified table
643
	 *
644
	 * We don't use db2_statistics as the function does not seem to play
645
	 * well with mixed case table names
646
	 *
647
	 * @param string   $table
648
	 * @param bool     $primary    (optional) only return primary keys
649
	 * @param bool     $owner      (optional) not used in this driver
650
	 *
651
	 * @return string[]    Array of indexes
652
	 */
653
	public function metaPrimaryKeys($table,$owner=false)
654
	{
655
 
656
		$primaryKeys = array();
657
 
658
		global $ADODB_FETCH_MODE;
659
 
660
		$schema = '';
661
		$this->_findschema($table,$schema);
662
 
663
		$table = $this->getTableCasedValue($table);
664
 
665
		$savem 			  = $ADODB_FETCH_MODE;
666
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
667
		$this->setFetchMode(ADODB_FETCH_NUM);
668
 
669
 
670
		$sql = "SELECT *
671
				  FROM syscat.indexes
672
				 WHERE tabname='$table'";
673
 
674
		$rows = $this->getAll($sql);
675
 
676
		$this->setFetchMode($savem);
677
		$ADODB_FETCH_MODE = $savem;
678
 
679
		if (empty($rows))
680
			return false;
681
 
682
		foreach ($rows as $r)
683
		{
684
			if ($r[7] != 'P')
685
				continue;
686
 
687
			$cols = explode('+',$r[6]);
688
			foreach ($cols as $colIndex=>$col)
689
			{
690
				if ($colIndex == 0)
691
					continue;
692
				$columnName = $this->getMetaCasedValue($col);
693
				$primaryKeys[] = $columnName;
694
			}
695
			break;
696
		}
697
		return $primaryKeys;
698
	}
699
 
700
	/**
701
	 * Returns a list of Foreign Keys associated with a specific table.
702
	 *
703
	 * @param string $table
704
	 * @param string $owner       discarded
705
	 * @param bool   $upper       discarded
706
	 * @param bool   $associative discarded
707
	 *
708
	 * @return string[]|false An array where keys are tables, and values are foreign keys;
709
	 *                        false if no foreign keys could be found.
710
	 */
711
	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
712
	{
713
 
714
		global $ADODB_FETCH_MODE;
715
 
716
		$schema = '';
717
		$this->_findschema($table,$schema);
718
 
719
		$savem = $ADODB_FETCH_MODE;
720
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
721
 
722
		$this->setFetchMode(ADODB_FETCH_NUM);
723
 
724
		$sql = "SELECT SUBSTR(tabname,1,20) table_name,
725
					   SUBSTR(constname,1,20) fk_name,
726
					   SUBSTR(REFTABNAME,1,12) parent_table,
727
					   SUBSTR(refkeyname,1,20) pk_orig_table,
728
					   fk_colnames
729
				 FROM syscat.references
730
				WHERE tabname = '$table'";
731
 
732
		$results = $this->getAll($sql);
733
 
734
		$ADODB_FETCH_MODE = $savem;
735
		$this->setFetchMode($savem);
736
 
737
		if (empty($results))
738
			return false;
739
 
740
		$foreignKeys = array();
741
 
742
		foreach ($results as $r)
743
		{
744
			$parentTable = trim($this->getMetaCasedValue($r[2]));
745
			$keyName     = trim($this->getMetaCasedValue($r[1]));
746
			$foreignKeys[$parentTable] = $keyName;
747
		}
748
 
749
		return $foreignKeys;
750
	}
751
 
752
	/**
753
	 * Returns a list of tables
754
	 *
755
	 * @param string	$ttype (optional)
756
	 * @param	string	$schema	(optional)
757
	 * @param	string	$mask	(optional)
758
	 *
759
	 * @return array
760
	 */
761
	public function metaTables($ttype=false,$schema=false,$mask=false)
762
	{
763
 
764
		global $ADODB_FETCH_MODE;
765
 
766
		$savem 			  = $ADODB_FETCH_MODE;
767
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
768
 
769
		/*
770
		* Values for TABLE_TYPE
771
		* ---------------------------
772
		* ALIAS, HIERARCHY TABLE, INOPERATIVE VIEW, NICKNAME,
773
		* MATERIALIZED QUERY TABLE, SYSTEM TABLE, TABLE,
774
		* TYPED TABLE, TYPED VIEW, and VIEW
775
		*
776
		* If $ttype passed as '', match 'TABLE' and 'VIEW'
777
		* If $ttype passed as 'T' it is assumed to be 'TABLE'
778
		* if $ttype passed as 'V' it is assumed to be 'VIEW'
779
		*/
780
		$ttype = strtoupper($ttype);
781
		if ($ttype) {
782
			/*
783
			 * @todo We could do valid type checking or array type
784
			 */
785
			 if ($ttype == 'V')
786
				$ttype = 'VIEW';
787
			if ($ttype == 'T')
788
				$ttype = 'TABLE';
789
		}
790
 
791
		if (!$schema)
792
			$schema = '%';
793
 
794
		if (!$mask)
795
			$mask = '%';
796
 
797
		$qid = @db2_tables($this->_connectionID,NULL,$schema,$mask,$ttype);
798
 
799
		$rs = new ADORecordSet_db2($qid);
800
 
801
		$ADODB_FETCH_MODE = $savem;
802
 
803
		if (!$rs)
804
			return false;
805
 
806
		$arr = $rs->getArray();
807
 
808
		$rs->Close();
809
 
810
		$tableList = array();
811
 
812
		/*
813
		* Array items
814
		* ---------------------------------
815
		* 0 TABLE_CAT	The catalog that contains the table.
816
		*				The value is NULL if this table does not have catalogs.
817
		* 1 TABLE_SCHEM	Name of the schema that contains the table.
818
		* 2 TABLE_NAME	Name of the table.
819
		* 3 TABLE_TYPE	Table type identifier for the table.
820
		* 4 REMARKS		Description of the table.
821
		*/
822
 
823
		for ($i=0; $i < sizeof($arr); $i++)
824
		{
825
 
826
			$tableRow = $arr[$i];
827
			$tableName = $tableRow[2];
828
			$tableType = $tableRow[3];
829
 
830
			if (!$tableName)
831
				continue;
832
 
833
			if ($ttype == '' && (strcmp($tableType,'TABLE') <> 0 && strcmp($tableType,'VIEW') <> 0))
834
				continue;
835
 
836
			/*
837
			 * Set metacasing if required
838
			 */
839
			$tableName = $this->getMetaCasedValue($tableName);
840
 
841
			/*
842
			 * If we requested a schema, we prepend the schema
843
			   name to the table name
844
			 */
845
			if (strcmp($schema,'%') <> 0)
846
				$tableName = $schema . '.' . $tableName;
847
 
848
			$tableList[] = $tableName;
849
 
850
		}
851
		return $tableList;
852
	}
853
 
854
	/**
855
	  * Return a list of indexes for a specified table
856
	  *
857
	  * We don't use db2_statistics as the function does not seem to play
858
	  * well with mixed case table names
859
	  *
860
	  * @param string   $table
861
	  * @param bool     $primary    (optional) only return primary keys
862
	  * @param bool     $owner      (optional) not used in this driver
863
	  *
864
	  * @return string[]    Array of indexes
865
	  */
866
	public function metaIndexes($table, $primary = false, $owner = false) {
867
 
868
		global $ADODB_FETCH_MODE;
869
 
870
		 /* Array(
871
		 *   [name_of_index] => Array(
872
		 *     [unique] => true or false
873
		 *     [columns] => Array(
874
		 *       [0] => firstcol
875
		 *       [1] => nextcol
876
		 *       [2] => etc........
877
		 *     )
878
		 *   )
879
		 * )
880
		 */
881
		$indices 		= array();
882
		$primaryKeyName = '';
883
 
884
		$table = $this->getTableCasedValue($table);
885
 
886
 
887
		$savem 			  = $ADODB_FETCH_MODE;
888
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
889
		$this->setFetchMode(ADODB_FETCH_NUM);
890
 
891
		$sql = "SELECT *
892
				  FROM syscat.indexes
893
				 WHERE tabname='$table'";
894
 
895
		$rows = $this->getAll($sql);
896
 
897
		$this->setFetchMode($savem);
898
		$ADODB_FETCH_MODE = $savem;
899
 
900
		if (empty($rows))
901
			return false;
902
 
903
		foreach ($rows as $r)
904
		{
905
 
906
			$primaryIndex = $r[7] == 'P'?1:0;
907
			if (!$primary)
908
				/*
909
				 * Primary key not requested, ignore that one
910
				 */
911
				if ($r[7] == 'P')
912
					continue;
913
 
914
			$indexName = $this->getMetaCasedValue($r[1]);
915
			if (!isset($indices[$indexName]))
916
			{
917
				$unique = ($r[7] == 'U')?1:0;
918
				$indices[$indexName] = array('unique'=>$unique,
919
											 'primary'=>$primaryIndex,
920
											 'columns'=>array()
921
										);
922
			}
923
			$cols = explode('+',$r[6]);
924
			foreach ($cols as $colIndex=>$col)
925
			{
926
				if ($colIndex == 0)
927
					continue;
928
				$columnName = $this->getMetaCasedValue($col);
929
				$indices[$indexName]['columns'][] = $columnName;
930
			}
931
 
932
		}
933
 
934
		return $indices;
935
 
936
	}
937
 
938
	/**
939
	 * List procedures or functions in an array.
940
	 *
941
	 * We interrogate syscat.routines instead of calling the PHP
942
	 * function procedures because ADOdb requires the type of procedure
943
	 * this is not available in the php function
944
	 *
945
	 * @param	string $procedureNamePattern (optional)
946
	 * @param	string $catalog				 (optional)
947
	 * @param	string $schemaPattern		 (optional)
948
 
949
	 * @return array of procedures on current database.
950
	 *
951
	 */
952
	public function metaProcedures($procedureNamePattern = null, $catalog  = null, $schemaPattern  = null) {
953
 
954
 
955
		global $ADODB_FETCH_MODE;
956
 
957
		$metaProcedures = array();
958
		$procedureSQL   = '';
959
		$catalogSQL     = '';
960
		$schemaSQL      = '';
961
 
962
		$savem 			  = $ADODB_FETCH_MODE;
963
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
964
 
965
		if ($procedureNamePattern)
966
			$procedureSQL = "AND ROUTINENAME LIKE " . strtoupper($this->qstr($procedureNamePattern));
967
 
968
		if ($catalog)
969
			$catalogSQL = "AND OWNER=" . strtoupper($this->qstr($catalog));
970
 
971
		if ($schemaPattern)
972
			$schemaSQL = "AND ROUTINESCHEMA LIKE {$this->qstr($schemaPattern)}";
973
 
974
 
975
		$fields = "
976
		ROUTINENAME,
977
		CASE ROUTINETYPE
978
			 WHEN 'P' THEN 'PROCEDURE'
979
			 WHEN 'F' THEN 'FUNCTION'
980
			 ELSE 'METHOD'
981
			 END AS ROUTINETYPE_NAME,
982
		ROUTINESCHEMA,
983
		REMARKS";
984
 
985
		$SQL = "SELECT $fields
986
				  FROM syscat.routines
987
				 WHERE OWNER IS NOT NULL
988
				  $procedureSQL
989
				  $catalogSQL
990
				  $schemaSQL
991
				ORDER BY ROUTINENAME
992
				";
993
 
994
		$result = $this->execute($SQL);
995
 
996
		$ADODB_FETCH_MODE = $savem;
997
 
998
		if (!$result)
999
			return false;
1000
 
1001
		while ($r = $result->fetchRow()){
1002
			$procedureName = $this->getMetaCasedValue($r[0]);
1003
			$schemaName    = $this->getMetaCasedValue($r[2]);
1004
			$metaProcedures[$procedureName] = array('type'=> $r[1],
1005
												   'catalog' => '',
1006
												   'schema'  => $schemaName,
1007
												   'remarks' => $r[3]
1008
													);
1009
		}
1010
 
1011
		return $metaProcedures;
1012
 
1013
	}
1014
 
1015
	/**
1016
	  * Lists databases. Because instances are independent, we only know about
1017
	  * the current database name
1018
	  *
1019
	  * @return string[]
1020
	  */
1021
	public function metaDatabases(){
1022
 
1023
		$dbName = $this->getMetaCasedValue($this->database);
1024
 
1025
		return (array)$dbName;
1026
 
1027
	}
1028
 
1029
 
1030
 
1031
 
1032
/*
1033
See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2datetime_data_type_changes.asp
1034
/ SQL data type codes /
1035
#define	SQL_UNKNOWN_TYPE	0
1036
#define SQL_CHAR			1
1037
#define SQL_NUMERIC		 2
1038
#define SQL_DECIMAL		 3
1039
#define SQL_INTEGER		 4
1040
#define SQL_SMALLINT		5
1041
#define SQL_FLOAT		   6
1042
#define SQL_REAL			7
1043
#define SQL_DOUBLE		  8
1044
#if (DB2VER >= 0x0300)
1045
#define SQL_DATETIME		9
1046
#endif
1047
#define SQL_VARCHAR		12
1048
 
1049
 
1050
/ One-parameter shortcuts for date/time data types /
1051
#if (DB2VER >= 0x0300)
1052
#define SQL_TYPE_DATE	  91
1053
#define SQL_TYPE_TIME	  92
1054
#define SQL_TYPE_TIMESTAMP 93
1055
 
1056
#define SQL_UNICODE                             (-95)
1057
#define SQL_UNICODE_VARCHAR                     (-96)
1058
#define SQL_UNICODE_LONGVARCHAR                 (-97)
1059
*/
1060
	function DB2Types($t)
1061
	{
1062
		switch ((integer)$t) {
1063
		case 1:
1064
		case 12:
1065
		case 0:
1066
		case -95:
1067
		case -96:
1068
			return 'C';
1069
		case -97:
1070
		case -1: //text
1071
			return 'X';
1072
		case -4: //image
1073
			return 'B';
1074
 
1075
		case 9:
1076
		case 91:
1077
			return 'D';
1078
 
1079
		case 10:
1080
		case 11:
1081
		case 92:
1082
		case 93:
1083
			return 'T';
1084
 
1085
		case 4:
1086
		case 5:
1087
		case -6:
1088
			return 'I';
1089
 
1090
		case -11: // uniqidentifier
1091
			return 'R';
1092
		case -7: //bit
1093
			return 'L';
1094
 
1095
		default:
1096
			return 'N';
1097
		}
1098
	}
1099
 
1100
	public function metaColumns($table, $normalize=true)
1101
	{
1102
		global $ADODB_FETCH_MODE;
1103
 
1104
		$savem = $ADODB_FETCH_MODE;
1105
 
1106
		$schema = '%';
1107
		$this->_findschema($table,$schema);
1108
		$table = $this->getTableCasedValue($table);
1109
		$colname = "%";
1110
		$qid = db2_columns($this->_connectionID, null, $schema, $table, $colname);
1111
		if (empty($qid))
1112
		{
1113
			if ($this->debug)
1114
			{
1115
				$errorMessage = @db2_conn_errormsg($this->_connectionID);
1116
				ADOConnection::outp($errorMessage);
1117
			}
1118
			return false;
1119
		}
1120
 
1121
		$rs = new ADORecordSet_db2($qid);
1122
 
1123
		if (!$rs)
1124
			return false;
1125
 
1126
		$rs->_fetch();
1127
 
1128
		$retarr = array();
1129
 
1130
		/*
1131
		$rs->fields indices
1132
 
1133
		1 TABLE_SCHEM
1134
		2 TABLE_NAME
1135
		3 COLUMN_NAME
1136
		4 DATA_TYPE
1137
		5 TYPE_NAME
1138
		6 PRECISION
1139
		7 LENGTH
1140
		8 SCALE
1141
		9 RADIX
1142
		10 NULLABLE
1143
		11 REMARKS
1144
		12 Column Default
1145
		13 SQL Data Type
1146
		14 SQL DateTime SubType
1147
		15 Max length in Octets
1148
		16 Ordinal Position
1149
		17 Is NULLABLE
1150
		*/
1151
		while (!$rs->EOF)
1152
		{
1153
			if ($rs->fields[2] == $table)
1154
			{
1155
 
1156
				$fld       = new ADOFieldObject();
1157
				$fld->name = $rs->fields[3];
1158
				$fld->type = $this->DB2Types($rs->fields[4]);
1159
 
1160
				// ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp
1161
				// access uses precision to store length for char/varchar
1162
 
1163
				if ($fld->type == 'C' or $fld->type == 'X') {
1164
					if ($rs->fields[4] <= -95) // UNICODE
1165
						$fld->max_length = $rs->fields[7]/2;
1166
					else
1167
						$fld->max_length = $rs->fields[7];
1168
				} else
1169
					$fld->max_length = $rs->fields[7];
1170
 
1171
				$fld->not_null         = !empty($rs->fields[10]);
1172
				$fld->scale            = $rs->fields[8];
1173
				$fld->primary_key      = false;
1174
 
1175
				//$columnName = $this->getMetaCasedValue($fld->name);
1176
				$columnName = strtoupper($fld->name);
1177
				$retarr[$columnName] = $fld;
1178
 
1179
			}
1180
			else if (sizeof($retarr)>0)
1181
				break;
1182
 
1183
			$rs->MoveNext();
1184
 
1185
		}
1186
 
1187
		$rs->Close();
1188
		if (empty($retarr))
1189
			$retarr = false;
1190
 
1191
		/*
1192
		 * Now we find out if the column is part of a primary key
1193
		 */
1194
 
1195
		$qid = @db2_primary_keys($this->_connectionID, "", $schema, $table);
1196
		if (empty($qid))
1197
			return false;
1198
 
1199
		$rs = new ADORecordSet_db2($qid);
1200
 
1201
		if (!$rs)
1202
		{
1203
			$ADODB_FETCH_MODE = $savem;
1204
			return $retarr;
1205
		}
1206
		$rs->_fetch();
1207
 
1208
		/*
1209
		$rs->fields indices
1210
 
1211
		1 TABLE_SCHEM
1212
		2 TABLE_NAME
1213
		3 COLUMN_NAME
1214
		4 KEY_SEQ
1215
		5 PK_NAME
1216
		*/
1217
		while (!$rs->EOF) {
1218
			if (strtoupper(trim($rs->fields[2])) == $table
1219
			&& (!$schema || strtoupper($rs->fields[1]) == $schema))
1220
			{
1221
				$retarr[strtoupper($rs->fields[3])]->primary_key = true;
1222
			}
1223
			else if (sizeof($retarr)>0)
1224
				break;
1225
 
1226
			$rs->MoveNext();
1227
		}
1228
		$rs->Close();
1229
 
1230
		$ADODB_FETCH_MODE = $savem;
1231
 
1232
		if (empty($retarr))
1233
			return false;
1234
 
1235
		/*
1236
		* If the fetch mode is numeric, return as numeric array
1237
		*/
1238
		if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM)
1239
			$retarr = array_values($retarr);
1240
 
1241
		return $retarr;
1242
	}
1243
 
1244
	/**
1245
	  * In this version if prepareSp, we just check to make sure
1246
	  * that the name of the stored procedure is correct
1247
	  * If true, we returns an array
1248
	  * else false
1249
	  *
1250
	  * @param	string	$procedureName
1251
	  * @param	mixed   $parameters (not used in db2 connections)
1252
	  * @return mixed[]
1253
	  */
1254
	function prepareSp($procedureName,$parameters=false) {
1255
 
1256
		global $ADODB_FETCH_MODE;
1257
 
1258
		$this->storedProcedureParameters = array('name'=>'',
1259
												 'resource'=>false,
1260
												 'in'=>array(),
1261
												 'out'=>array(),
1262
												 'index'=>array(),
1263
												 'parameters'=>array(),
1264
												 'keyvalue' => array());
1265
 
1266
		//$procedureName = strtoupper($procedureName);
1267
		//$procedureName = $this->getTableCasedValue($procedureName);
1268
 
1269
		$savem = $ADODB_FETCH_MODE;
1270
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
1271
 
1272
		$qid = db2_procedures($this->_connectionID, NULL , '%' , $procedureName );
1273
 
1274
		$ADODB_FETCH_MODE = $savem;
1275
 
1276
		if (!$qid)
1277
		{
1278
			if ($this->debug)
1279
				ADOConnection::outp(sprintf('No Procedure of name %s available',$procedureName));
1280
			return false;
1281
		}
1282
 
1283
 
1284
 
1285
		$this->storedProcedureParameters['name'] = $procedureName;
1286
		/*
1287
		 * Now we know we have a valid procedure name, lets see if it requires
1288
		 * parameters
1289
		 */
1290
		$savem = $ADODB_FETCH_MODE;
1291
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
1292
 
1293
		$qid = db2_procedure_columns($this->_connectionID, NULL , '%' , $procedureName , NULL );
1294
 
1295
		$ADODB_FETCH_MODE = $savem;
1296
 
1297
		if (!$qid)
1298
		{
1299
			if ($this->debug)
1300
				ADOConnection::outp(sprintf('No columns of name %s available',$procedureName));
1301
			return false;
1302
		}
1303
		$rs = new ADORecordSet_db2($qid);
1304
		if (!$rs)
1305
			return false;
1306
 
1307
		$preparedStatement = 'CALL %s(%s)';
1308
		$parameterMarkers = array();
1309
		while (!$rs->EOF)
1310
		{
1311
			$parameterName = $rs->fields[3];
1312
			if ($parameterName == '')
1313
			{
1314
				$rs->moveNext();
1315
				continue;
1316
			}
1317
			$parameterType = $rs->fields[4];
1318
			$ordinalPosition = $rs->fields[17];
1319
			switch($parameterType)
1320
			{
1321
			case DB2_PARAM_IN:
1322
			case DB2_PARAM_INOUT:
1323
				$this->storedProcedureParameters['in'][$parameterName] = '';
1324
				break;
1325
			case DB2_PARAM_INOUT:
1326
			case DB2_PARAM_OUT:
1327
				$this->storedProcedureParameters['out'][$parameterName] = '';
1328
				break;
1329
			}
1330
			$this->storedProcedureParameters['index'][$parameterName] = $ordinalPosition;
1331
			$this->storedProcedureParameters['parameters'][$ordinalPosition] = $rs->fields;
1332
			$rs->moveNext();
1333
 
1334
		}
1335
		$parameterCount = count($this->storedProcedureParameters['index']);
1336
		$parameterMarkers = array_fill(0,$parameterCount,'?');
1337
 
1338
		/*
1339
		 * We now know how many parameters to bind to the stored procedure
1340
		 */
1341
		$parameterList = implode(',',$parameterMarkers);
1342
 
1343
		$sql = sprintf($preparedStatement,$procedureName,$parameterList);
1344
 
1345
		$spResource = @db2_prepare($this->_connectionID,$sql);
1346
 
1347
		if (!$spResource)
1348
		{
1349
			$errorMessage = @db2_conn_errormsg($this->_connectionID);
1350
			$this->_errorMsg = $errorMessage;
1351
 
1352
			if ($this->debug)
1353
				ADOConnection::outp($errorMessage);
1354
 
1355
			return false;
1356
		}
1357
 
1358
		$this->storedProcedureParameters['resource'] = $spResource;
1359
 
1360
		if ($this->debug)
1361
		{
1362
 
1363
			ADOConnection::outp('The following parameters will be used in the SP call');
1364
			ADOConnection::outp(print_r($this->storedProcedureParameters));
1365
		}
1366
		/*
1367
		 * We now have a stored parameter resource
1368
		 * to bind to. The spResource and sql that is returned are
1369
		 * not usable, its for dummy compatibility. Everything
1370
		 * will be handled by the storedProcedureParameters
1371
		 * array
1372
		 */
1373
		return array($sql,$spResource);
1374
 
1375
	}
1376
 
1377
	private function storedProcedureParameter(&$stmt,
1378
											  &$var,
1379
											  $name,
1380
											  $isOutput=false,
1381
											  $maxLen=4000,
1382
											  $type=false)
1383
	{
1384
 
1385
 
1386
		$name = strtoupper($name);
1387
 
1388
		/*
1389
		 * Must exist in the list of parameter names for the type
1390
		 */
1391
		if ($isOutput
1392
		&& !isset( $this->storedProcedureParameters['out'][$name]))
1393
		{
1394
			$errorMessage = sprintf('%s is not a valid OUT parameter name',$name);
1395
 
1396
			$this->_errorMsg = $errorMessage;
1397
			if ($this->debug)
1398
				ADOConnection::outp($errorMessage);
1399
			return false;
1400
		}
1401
 
1402
		if (!$isOutput
1403
		&& !isset( $this->storedProcedureParameters['in'][$name]))
1404
		{
1405
			$errorMessage = sprintf('%s is not a valid IN parameter name',$name);
1406
 
1407
			$this->_errorMsg = $errorMessage;
1408
			if ($this->debug)
1409
				ADOConnection::outp($errorMessage);
1410
			return false;
1411
		}
1412
 
1413
		/*
1414
		 * We will use these values to bind to when we execute
1415
		 * the query
1416
		 */
1417
		$this->storedProcedureParameters['keyvalue'][$name] = &$var;
1418
 
1419
		return true;
1420
 
1421
	}
1422
 
1423
	/**
1424
	* Executes a prepared stored procedure.
1425
	*
1426
	* The function uses the previously accumulated information and
1427
	* resources in the $storedProcedureParameters array
1428
	*
1429
	* @return mixed	The statement id if successful, or false
1430
	*/
1431
	private function executeStoredProcedure()
1432
	{
1433
 
1434
		/*
1435
		 * Get the previously built resource
1436
		 */
1437
		$stmtid = $this->storedProcedureParameters['resource'];
1438
 
1439
		/*
1440
		 * Bind our variables to the DB2 procedure
1441
		 */
1442
		foreach ($this->storedProcedureParameters['keyvalue'] as $spName=>$spValue){
1443
 
1444
			/*
1445
			 * Get the ordinal position, required for binding
1446
			 */
1447
			$ordinalPosition = $this->storedProcedureParameters['index'][$spName];
1448
 
1449
			/*
1450
			 * Get the db2 column dictionary for the parameter
1451
			 */
1452
			$columnDictionary = $this->storedProcedureParameters['parameters'][$ordinalPosition];
1453
			$parameterType    = $columnDictionary[4];
1454
			$dataType         = $columnDictionary[5];
1455
			$precision        = $columnDictionary[10];
1456
			$scale        	  = $columnDictionary[9];
1457
 
1458
			$ok = @db2_bind_param ($this->storedProcedureParameters['resource'],
1459
								  $ordinalPosition ,
1460
								  $spName,
1461
								  $parameterType,
1462
								  $dataType,
1463
								  $precision,
1464
								  $scale
1465
								  );
1466
 
1467
			if (!$ok)
1468
			{
1469
				$this->_errorMsg  = @db2_stmt_errormsg();
1470
				$this->_errorCode = @db2_stmt_error();
1471
 
1472
				if ($this->debug)
1473
					ADOConnection::outp($this->_errorMsg);
1474
				return false;
1475
			}
1476
 
1477
			if ($this->debug)
1478
				ADOConnection::outp("Correctly Bound parameter $spName to procedure");
1479
 
1480
			/*
1481
			 * Build a variable in the current environment that matches
1482
			 * the parameter name
1483
			 */
1484
			${$spName} = $spValue;
1485
 
1486
		}
1487
 
1488
		/*
1489
		 * All bound, execute
1490
		 */
1491
 
1492
		if (!@db2_execute($stmtid))
1493
		{
1494
			$this->_errorMsg = @db2_stmt_errormsg();
1495
			$this->_errorCode = @db2_stmt_error();
1496
 
1497
			if ($this->debug)
1498
				ADOConnection::outp($this->_errorMsg);
1499
			return false;
1500
		}
1501
 
1502
		/*
1503
		 * We now take the changed parameters back into the
1504
		 * stored procedures array where we can query them later
1505
		 * Remember that $spValue was passed in by reference, so we
1506
		 * can access the value in the variable that was originally
1507
		 * passed to inParameter or outParameter
1508
		 */
1509
		foreach ($this->storedProcedureParameters['keyvalue'] as $spName=>$spValue)
1510
		{
1511
			/*
1512
			 * We make it available to the environment
1513
			 */
1514
			$spValue = ${$spName};
1515
			$this->storedProcedureParameters['keyvalue'][$spName] = $spValue;
1516
		}
1517
 
1518
		return $stmtid;
1519
	}
1520
 
1521
	/**
1522
	 *
1523
	 * Accepts an input or output parameter to bind to either a stored
1524
	 * or prepared statements. For DB2, this should not be called as an
1525
	 * API. always wrap with inParameter and outParameter
1526
	 *
1527
	 * @param mixed[] $stmt 		Statement returned by Prepare() or PrepareSP().
1528
	 * @param mixed   $var 		PHP variable to bind to. Can set to null (for isNull support).
1529
	 * @param string  $name 		Name of stored procedure variable name to bind to.
1530
	 * @param int	 $isOutput 	optional) Indicates direction of parameter
1531
	 * 							0/false=IN  1=OUT  2= IN/OUT
1532
	 *							This is ignored for Stored Procedures
1533
	 * @param int	$maxLen		(optional)Holds an maximum length of the variable.
1534
	 *							This is ignored for Stored Procedures
1535
	 * @param int	$type 		(optional) The data type of $var.
1536
	 *							This is ignored for Stored Procedures
1537
	 *
1538
	 * @return bool				Success of the operation
1539
	 */
1540
	public function parameter(&$stmt, &$var, $name, $isOutput=false, $maxLen=4000, $type=false)
1541
	{
1542
 
1543
		/*
1544
		 * If the $stmt is the name of a stored procedure we are
1545
		 * setting up, we will process it one way, otherwise
1546
		 * we assume we are setting up a prepared statement
1547
		*/
1548
		if (is_array($stmt))
1549
		{
1550
			if ($this->debug)
1551
				ADOConnection::outp("Adding parameter to stored procedure");
1552
			if ($stmt[1] == $this->storedProcedureParameters['resource'])
1553
				return $this->storedProcedureParameter($stmt[1],
1554
														$var,
1555
														$name,
1556
														$isOutput,
1557
														$maxLen,
1558
														$type);
1559
 
1560
		}
1561
 
1562
		/*
1563
		 * We are going to add a parameter to a prepared statement
1564
		 */
1565
		if ($this->debug)
1566
			ADOConnection::outp("Adding parameter to prepared statement");
1567
	}
1568
 
1569
 
1570
	/**
1571
	 * Prepares a prepared SQL statement, not used for stored procedures
1572
	 *
1573
	 * @param string	$sql
1574
	 *
1575
	 * @return mixed
1576
	 */
1577
	function prepare($sql)
1578
	{
1579
 
1580
		if (! $this->_bindInputArray) return $sql; // no binding
1581
 
1582
		$stmt = @db2_prepare($this->_connectionID,$sql);
1583
		if (!$stmt) {
1584
			// we don't know whether db2 driver is parsing prepared stmts, so just return sql
1585
			return $sql;
1586
		}
1587
		return array($sql,$stmt,false);
1588
	}
1589
 
1590
	/**
1591
	 * Executes a query
1592
	 *
1593
	 * @param	mixed $sql
1594
	 * @param	mixed $inputarr	An optional array of parameters
1595
	 *
1596
	 * @return mixed				either the queryID or false
1597
	 */
1598
	function _query($sql, $inputarr = false)
1599
	{
1600
		$db2Options = array();
1601
		/*
1602
		 * Use DB2 Internal case handling for best speed
1603
		 */
1604
		switch(ADODB_ASSOC_CASE)
1605
		{
1606
		case ADODB_ASSOC_CASE_UPPER:
1607
			$db2Options = array('db2_attr_case'=>DB2_CASE_UPPER);
1608
			$setOption = @db2_set_option($this->_connectionID,$db2Options,1);
1609
			break;
1610
 
1611
		 case ADODB_ASSOC_CASE_LOWER:
1612
			$db2Options = array('db2_attr_case'=>DB2_CASE_LOWER);
1613
			$setOption = @db2_set_option($this->_connectionID,$db2Options,1);
1614
			break;
1615
 
1616
		default:
1617
			$db2Options = array('db2_attr_case'=>DB2_CASE_NATURAL);
1618
			$setOption = @db2_set_option($this->_connectionID,$db2Options,1);
1619
		}
1620
 
1621
		if ($inputarr)
1622
		{
1623
			if (is_array($sql))
1624
			{
1625
				$stmtid = $sql[1];
1626
			}
1627
			else
1628
			{
1629
				$stmtid = @db2_prepare($this->_connectionID,$sql);
1630
 
1631
				if ($stmtid == false)
1632
				{
1633
					$this->_errorMsg  = @db2_stmt_errormsg();
1634
					$this->_errorCode = @db2_stmt_error();
1635
 
1636
					if ($this->debug)
1637
						ADOConnection::outp($this->_errorMsg);
1638
 
1639
					return false;
1640
				}
1641
			}
1642
 
1643
			if (! @db2_execute($stmtid,$inputarr))
1644
			{
1645
				$this->_errorMsg = @db2_stmt_errormsg();
1646
				$this->_errorCode = @db2_stmt_error();
1647
				if ($this->debug)
1648
					ADOConnection::outp($this->_errorMsg);
1649
				return false;
1650
			}
1651
 
1652
		}
1653
		else if (is_array($sql))
1654
		{
1655
 
1656
			/*
1657
			 * Either a prepared statement or a stored procedure
1658
			 */
1659
 
1660
			if (is_array($this->storedProcedureParameters)
1661
				&& is_resource($this->storedProcedureParameters['resource']
1662
			))
1663
				/*
1664
				 * This is all handled in the separate method for
1665
				 * readability
1666
				 */
1667
				return $this->executeStoredProcedure();
1668
 
1669
			/*
1670
			 * First, we prepare the statement
1671
			 */
1672
			$stmtid = @db2_prepare($this->_connectionID,$sql[0]);
1673
			if (!$stmtid){
1674
				$this->_errorMsg = @db2_stmt_errormsg();
1675
				$this->_errorCode = @db2_stmt_error();
1676
				if ($this->debug)
1677
					ADOConnection::outp("Prepare failed: " . $this->_errorMsg);
1678
 
1679
				return false;
1680
			}
1681
			/*
1682
			 * We next bind some input parameters
1683
			 */
1684
			$ordinal = 1;
1685
			foreach ($sql[1] as $psVar=>$psVal){
1686
				${$psVar} = $psVal;
1687
				$ok = @db2_bind_param($stmtid, $ordinal, $psVar, DB2_PARAM_IN);
1688
				if (!$ok)
1689
				{
1690
					$this->_errorMsg = @db2_stmt_errormsg();
1691
					$this->_errorCode = @db2_stmt_error();
1692
					if ($this->debug)
1693
						ADOConnection::outp("Bind failed: " . $this->_errorMsg);
1694
					return false;
1695
				}
1696
			}
1697
 
1698
			if (!@db2_execute($stmtid))
1699
			{
1700
				$this->_errorMsg = @db2_stmt_errormsg();
1701
				$this->_errorCode = @db2_stmt_error();
1702
				if ($this->debug)
1703
					ADOConnection::outp($this->_errorMsg);
1704
				return false;
1705
			}
1706
 
1707
			return $stmtid;
1708
		}
1709
		else
1710
		{
1711
 
1712
			$stmtid = @db2_exec($this->_connectionID,$sql);
1713
		}
1714
		$this->_lastAffectedRows = 0;
1715
		if ($stmtid)
1716
		{
1717
			if (@db2_num_fields($stmtid) == 0)
1718
			{
1719
				$this->_lastAffectedRows = db2_num_rows($stmtid);
1720
				$stmtid = true;
1721
			}
1722
			else
1723
			{
1724
				$this->_lastAffectedRows = 0;
1725
			}
1726
 
1727
			$this->_errorMsg = '';
1728
			$this->_errorCode = 0;
1729
 
1730
		}
1731
		else
1732
		{
1733
 
1734
			$this->_errorMsg = @db2_stmt_errormsg();
1735
			$this->_errorCode = @db2_stmt_error();
1736
 
1737
		}
1738
		return $stmtid;
1739
	}
1740
 
1741
	/*
1742
		Insert a null into the blob field of the table first.
1743
		Then use UpdateBlob to store the blob.
1744
 
1745
		Usage:
1746
 
1747
		$conn->execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
1748
		$conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
1749
	*/
1750
	function updateBlob($table,$column,$val,$where,$blobtype='BLOB')
1751
	{
1752
		return $this->execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
1753
	}
1754
 
1755
	// returns true or false
1756
	function _close()
1757
	{
1758
		$ret = @db2_close($this->_connectionID);
1759
		$this->_connectionID = false;
1760
		return $ret;
1761
	}
1762
 
1763
	function _affectedrows()
1764
	{
1765
		return $this->_lastAffectedRows;
1766
	}
1767
 
1768
	/**
1769
	 * Gets a meta cased parameter
1770
	 *
1771
	 * Receives an input variable to be processed per the metaCasing
1772
	 * rule, and returns the same value, processed
1773
	 *
1774
	 * @param string $value
1775
	 *
1776
	 * @return string
1777
	 */
1778
	final public function getMetaCasedValue($value)
1779
	{
1780
		global $ADODB_ASSOC_CASE;
1781
 
1782
		switch($ADODB_ASSOC_CASE)
1783
		{
1784
		case ADODB_ASSOC_CASE_LOWER:
1785
			$value = strtolower($value);
1786
			break;
1787
		case ADODB_ASSOC_CASE_UPPER:
1788
			$value = strtoupper($value);
1789
			break;
1790
		}
1791
		return $value;
1792
	}
1793
 
1794
 
1795
	const TABLECASE_LOWER    =  0;
1796
	const TABLECASE_UPPER    =  1;
1797
	const TABLECASE_DEFAULT  =  2;
1798
 
1799
	/**
1800
	 * Controls the casing of the table provided to the meta functions
1801
	 */
1802
	private $tableCase = 2;
1803
 
1804
	/**
1805
	 * Sets the table case parameter
1806
	 *
1807
	 * @param int $caseOption
1808
	 * @return null
1809
	 */
1810
	final public function setTableCasing($caseOption)
1811
	{
1812
		$this->tableCase = $caseOption;
1813
	}
1814
 
1815
	/**
1816
	 * Gets the table casing parameter
1817
	 *
1818
	 * @return int $caseOption
1819
	 */
1820
	final public function getTableCasing()
1821
	{
1822
		return $this->tableCase;
1823
	}
1824
 
1825
	/**
1826
	 * Gets a table cased parameter
1827
	 *
1828
	 * Receives an input variable to be processed per the tableCasing
1829
	 * rule, and returns the same value, processed
1830
	 *
1831
	 * @param string $value
1832
	 *
1833
	 * @return string
1834
	 */
1835
	final public function getTableCasedValue($value)
1836
	{
1837
		switch($this->tableCase)
1838
		{
1839
		case self::TABLECASE_LOWER:
1840
			$value = strtolower($value);
1841
			break;
1842
		case self::TABLECASE_UPPER:
1843
			$value = strtoupper($value);
1844
			break;
1845
		}
1846
		return $value;
1847
	}
1848
 
1849
}
1850
 
1851
/*--------------------------------------------------------------------------------------
1852
	 Class Name: Recordset
1853
--------------------------------------------------------------------------------------*/
1854
 
1855
class ADORecordSet_db2 extends ADORecordSet {
1856
 
1857
	var $bind = false;
1858
	var $databaseType = "db2";
1859
	var $dataProvider = "db2";
1860
	var $useFetchArray;
1861
 
1862
	function __construct($id,$mode=false)
1863
	{
1864
		if ($mode === false) {
1865
			global $ADODB_FETCH_MODE;
1866
			$mode = $ADODB_FETCH_MODE;
1867
		}
1868
		$this->fetchMode = $mode;
1869
 
1870
		$this->_queryID = $id;
1871
	}
1872
 
1873
 
1874
	// returns the field object
1875
	function fetchField($offset = 0)
1876
	{
1877
		$o			   = new ADOFieldObject();
1878
		$o->name 	   = @db2_field_name($this->_queryID,$offset);
1879
		$o->type 	   = @db2_field_type($this->_queryID,$offset);
1880
		$o->max_length = @db2_field_width($this->_queryID,$offset);
1881
 
1882
		/*
1883
		if (ADODB_ASSOC_CASE == 0)
1884
			$o->name = strtolower($o->name);
1885
		else if (ADODB_ASSOC_CASE == 1)
1886
			$o->name = strtoupper($o->name);
1887
		*/
1888
		return $o;
1889
	}
1890
 
1891
	/* Use associative array to get fields array */
1892
	function fields($colname)
1893
	{
1894
 
1895
		if ($this->fetchMode & ADODB_FETCH_ASSOC) {
1896
			return $this->fields[$colname];
1897
		}
1898
 
1899
		if (!$this->bind) {
1900
			$this->bind = array();
1901
			for ($i=0; $i < $this->_numOfFields; $i++) {
1902
				$o = $this->FetchField($i);
1903
				$this->bind[strtoupper($o->name)] = $i;
1904
			}
1905
		}
1906
 
1907
		 return $this->fields[$this->bind[strtoupper($colname)]];
1908
	}
1909
 
1910
 
1911
	function _initrs()
1912
	{
1913
		global $ADODB_COUNTRECS;
1914
		$this->_numOfRows = ($ADODB_COUNTRECS) ? @db2_num_rows($this->_queryID) : -1;
1915
 
1916
		$this->_numOfFields = @db2_num_fields($this->_queryID);
1917
 
1918
		// some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0
1919
 
1920
		if ($this->_numOfRows == 0)
1921
			$this->_numOfRows = -1;
1922
	}
1923
 
1924
	function _seek($row)
1925
	{
1926
		return false;
1927
	}
1928
 
1929
	function getArrayLimit($nrows,$offset=0)
1930
	{
1931
		if ($offset <= 0) {
1932
			$rs = $this->GetArray($nrows);
1933
			return $rs;
1934
		}
1935
 
1936
		$this->Move($offset);
1937
 
1938
 
1939
		$results = array();
1940
		$cnt = 0;
1941
		while (!$this->EOF && $nrows != $cnt) {
1942
			$results[$cnt++] = $this->fields;
1943
			$this->MoveNext();
1944
		}
1945
 
1946
		return $results;
1947
	}
1948
 
1949
	function moveNext()
1950
	{
1951
		if ($this->EOF || $this->_numOfRows == 0)
1952
			return false;
1953
 
1954
		$this->_currentRow++;
1955
 
1956
		$this->processCoreFetch();
1957
		return $this->processMoveRecord();
1958
 
1959
	}
1960
 
1961
	private function processCoreFetch()
1962
	{
1963
		switch ($this->fetchMode){
1964
		case ADODB_FETCH_ASSOC:
1965
 
1966
			/*
1967
			 * Associative array
1968
			 */
1969
			$this->fields = @db2_fetch_assoc($this->_queryID);
1970
			break;
1971
 
1972
		case ADODB_FETCH_BOTH:
1973
			/*
1974
			 * Fetch both numeric and Associative array
1975
			 */
1976
			$this->fields = @db2_fetch_both($this->_queryID);
1977
			break;
1978
		default:
1979
			/*
1980
			 * Numeric array
1981
			 */
1982
			$this->fields = @db2_fetch_array($this->_queryID);
1983
			break;
1984
		}
1985
	}
1986
 
1987
	private function processMoveRecord()
1988
	{
1989
		if (!$this->fields){
1990
			$this->EOF = true;
1991
			return false;
1992
		}
1993
 
1994
		return true;
1995
	}
1996
 
1997
	function _fetch()
1998
	{
1999
		$this->processCoreFetch();
2000
		if ($this->fields)
2001
			return true;
2002
 
2003
		$this->fields = false;
2004
		return false;
2005
	}
2006
 
2007
	function _close()
2008
	{
2009
		$ok = @db2_free_result($this->_queryID);
2010
		if (!$ok)
2011
		{
2012
			$this->connection->_errorMsg  = @db2_stmt_errormsg($this->_queryID);
2013
			$this->connection->_errorCode = @db2_stmt_error();
2014
 
2015
			if ($this->debug)
2016
				ADOConnection::outp($this->connection->_errorMsg);
2017
			return false;
2018
		}
2019
	}
2020
 
2021
}