]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/db/DatabaseIbm_db2.php
MediaWiki 1.15.5
[autoinstallsdev/mediawiki.git] / includes / db / DatabaseIbm_db2.php
1 <?php
2 /**
3  * This script is the IBM DB2 database abstraction layer
4  *
5  * See maintenance/ibm_db2/README for development notes and other specific information
6  * @ingroup Database
7  * @file
8  * @author leo.petr+mediawiki@gmail.com
9  */
10
11 /**
12  * Utility class for generating blank objects
13  * Intended as an equivalent to {} in Javascript
14  * @ingroup Database
15  */
16 class BlankObject {
17 }
18
19 /**
20  * This represents a column in a DB2 database
21  * @ingroup Database
22  */
23 class IBM_DB2Field {
24         private $name, $tablename, $type, $nullable, $max_length;
25
26         /**
27          * Builder method for the class 
28          * @param Object $db Database interface
29          * @param string $table table name
30          * @param string $field column name
31          * @return IBM_DB2Field
32          */
33         static function fromText($db, $table, $field) {
34                 global $wgDBmwschema;
35
36                 $q = <<<END
37 SELECT
38 lcase(coltype) AS typname,
39 nulls AS attnotnull, length AS attlen
40 FROM sysibm.syscolumns
41 WHERE tbcreator=%s AND tbname=%s AND name=%s;
42 END;
43                 $res = $db->query(sprintf($q,
44                                 $db->addQuotes($wgDBmwschema),
45                                 $db->addQuotes($table),
46                                 $db->addQuotes($field)));
47                 $row = $db->fetchObject($res);
48                 if (!$row)
49                         return null;
50                 $n = new IBM_DB2Field;
51                 $n->type = $row->typname;
52                 $n->nullable = ($row->attnotnull == 'N');
53                 $n->name = $field;
54                 $n->tablename = $table;
55                 $n->max_length = $row->attlen;
56                 return $n;
57         }
58         /**
59          * Get column name
60          * @return string column name
61          */
62         function name() { return $this->name; }
63         /**
64          * Get table name
65          * @return string table name
66          */
67         function tableName() { return $this->tablename; }
68         /**
69          * Get column type
70          * @return string column type
71          */
72         function type() { return $this->type; }
73         /**
74          * Can column be null?
75          * @return bool true or false
76          */
77         function nullable() { return $this->nullable; }
78         /**
79          * How much can you fit in the column per row?
80          * @return int length
81          */
82         function maxLength() { return $this->max_length; }
83 }
84
85 /**
86  * Wrapper around binary large objects
87  * @ingroup Database
88  */
89 class IBM_DB2Blob {
90         private $mData;
91
92         function __construct($data) {
93                 $this->mData = $data;
94         }
95
96         function getData() {
97                 return $this->mData;
98         }
99 }
100
101 /**
102  * Primary database interface
103  * @ingroup Database
104  */
105 class DatabaseIbm_db2 extends Database {
106         /*
107          * Inherited members
108         protected $mLastQuery = '';
109         protected $mPHPError = false;
110
111         protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
112         protected $mOut, $mOpened = false;
113
114         protected $mFailFunction;
115         protected $mTablePrefix;
116         protected $mFlags;
117         protected $mTrxLevel = 0;
118         protected $mErrorCount = 0;
119         protected $mLBInfo = array();
120         protected $mFakeSlaveLag = null, $mFakeMaster = false;
121          *
122          */
123         
124         /// Server port for uncataloged connections
125         protected $mPort = NULL;
126         /// Whether connection is cataloged
127         protected $mCataloged = NULL;
128         /// Schema for tables, stored procedures, triggers
129         protected $mSchema = NULL;
130         /// Whether the schema has been applied in this session
131         protected $mSchemaSet = false;
132         /// Result of last query
133         protected $mLastResult = NULL;
134         /// Number of rows affected by last INSERT/UPDATE/DELETE
135         protected $mAffectedRows = NULL;
136         /// Number of rows returned by last SELECT
137         protected $mNumRows = NULL;
138         
139         
140         const CATALOGED = "cataloged";
141         const UNCATALOGED = "uncataloged";
142         const USE_GLOBAL = "get from global";
143         
144         /// Last sequence value used for a primary key
145         protected $mInsertId = NULL;
146         
147         /*
148          * These can be safely inherited
149          * 
150          * Getter/Setter: (18)
151          * failFunction
152          * setOutputPage
153          * bufferResults
154          * ignoreErrors
155          * trxLevel
156          * errorCount
157          * getLBInfo
158          * setLBInfo
159          * lastQuery
160          * isOpen
161          * setFlag
162          * clearFlag
163          * getFlag
164          * getProperty
165          * getDBname
166          * getServer
167          * tableNameCallback
168          * tablePrefix
169          * 
170          * Administrative: (8)
171          * debug
172          * installErrorHandler
173          * restoreErrorHandler
174          * connectionErrorHandler
175          * reportConnectionError
176          * sourceFile
177          * sourceStream
178          * replaceVars
179          * 
180          * Database: (5)
181          * query
182          * set
183          * selectField
184          * generalizeSQL
185          * update
186          * strreplace
187          * deadlockLoop
188          * 
189          * Prepared Statement: 6
190          * prepare
191          * freePrepared
192          * execute
193          * safeQuery
194          * fillPrepared
195          * fillPreparedArg
196          * 
197          * Slave/Master: (4) 
198          * masterPosWait
199          * getSlavePos
200          * getMasterPos
201          * getLag
202          * 
203          * Generation: (9)
204          * tableNames
205          * tableNamesN
206          * tableNamesWithUseIndexOrJOIN
207          * escapeLike
208          * delete
209          * insertSelect
210          * timestampOrNull
211          * resultObject
212          * aggregateValue
213          * selectSQLText
214          * selectRow
215          * makeUpdateOptions
216          * 
217          * Reflection: (1)
218          * indexExists
219          */
220         
221         /*
222          * These need to be implemented TODO
223          * 
224          * Administrative: 7 / 7
225          * constructor [Done]
226          * open [Done]
227          * openCataloged [Done]
228          * close [Done]
229          * newFromParams [Done]
230          * openUncataloged [Done]
231          * setup_database [Done]
232          * 
233          * Getter/Setter: 13 / 13
234          * cascadingDeletes [Done]
235          * cleanupTriggers  [Done]
236          * strictIPs  [Done]
237          * realTimestamps  [Done]
238          * impliciGroupby  [Done]
239          * implicitOrderby  [Done]
240          * searchableIPs  [Done]
241          * functionalIndexes  [Done]
242          * getWikiID  [Done]
243          * isOpen [Done]
244          * getServerVersion [Done]
245          * getSoftwareLink [Done]
246          * getSearchEngine [Done]
247          * 
248          * Database driver wrapper: 23 / 23
249          * lastError [Done]
250          * lastErrno [Done]
251          * doQuery [Done]
252          * tableExists [Done]
253          * fetchObject [Done]
254          * fetchRow [Done]
255          * freeResult [Done]
256          * numRows [Done]
257          * numFields [Done]
258          * fieldName [Done]
259          * insertId [Done]
260          * dataSeek [Done]
261          * affectedRows [Done]
262          * selectDB [Done]
263          * strencode [Done]
264          * conditional [Done]
265          * wasDeadlock [Done]
266          * ping [Done]
267          * getStatus [Done]
268          * setTimeout [Done]
269          * lock [Done]
270          * unlock [Done]
271          * insert [Done]
272          * select [Done]
273          * 
274          * Slave/master: 2 / 2
275          * setFakeSlaveLag [Done]
276          * setFakeMaster [Done]
277          * 
278          * Reflection: 6 / 6
279          * fieldExists [Done]
280          * indexInfo [Done]
281          * fieldInfo [Done]
282          * fieldType [Done]
283          * indexUnique [Done]
284          * textFieldSize [Done]
285          * 
286          * Generation: 16 / 16
287          * tableName [Done]
288          * addQuotes [Done]
289          * makeList [Done]
290          * makeSelectOptions [Done]
291          * estimateRowCount [Done]
292          * nextSequenceValue [Done]
293          * useIndexClause [Done]
294          * replace [Done]
295          * deleteJoin [Done]
296          * lowPriorityOption [Done]
297          * limitResult [Done]
298          * limitResultForUpdate [Done]
299          * timestamp [Done]
300          * encodeBlob [Done]
301          * decodeBlob [Done]
302          * buildConcat [Done]
303          */
304         
305         ######################################
306         # Getters and Setters
307         ######################################
308         
309         /**
310          * Returns true if this database supports (and uses) cascading deletes
311          */
312         function cascadingDeletes() {
313                 return true;
314         }
315
316         /**
317          * Returns true if this database supports (and uses) triggers (e.g. on the page table)
318          */
319         function cleanupTriggers() {
320                 return true;
321         }
322
323         /**
324          * Returns true if this database is strict about what can be put into an IP field.
325          * Specifically, it uses a NULL value instead of an empty string.
326          */
327         function strictIPs() {
328                 return true;
329         }
330         
331         /**
332          * Returns true if this database uses timestamps rather than integers
333         */
334         function realTimestamps() {
335                 return true;
336         }
337
338         /**
339          * Returns true if this database does an implicit sort when doing GROUP BY
340          */
341         function implicitGroupby() {
342                 return false;
343         }
344
345         /**
346          * Returns true if this database does an implicit order by when the column has an index
347          * For example: SELECT page_title FROM page LIMIT 1
348          */
349         function implicitOrderby() {
350                 return false;
351         }
352
353         /**
354          * Returns true if this database can do a native search on IP columns
355          * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
356          */
357         function searchableIPs() {
358                 return true;
359         }
360
361         /**
362          * Returns true if this database can use functional indexes
363          */
364         function functionalIndexes() {
365                 return true;
366         }
367         
368         /**
369          * Returns a unique string representing the wiki on the server
370          */
371         function getWikiID() {
372                 if( $this->mSchema ) {
373                         return "{$this->mDBname}-{$this->mSchema}";
374                 } else {
375                         return $this->mDBname;
376                 }
377         }
378         
379         
380         ######################################
381         # Setup
382         ######################################
383         
384         
385         /**
386          * 
387          * @param string $server hostname of database server
388          * @param string $user username
389          * @param string $password
390          * @param string $dbName database name on the server
391          * @param function $failFunction (optional)
392          * @param integer $flags database behaviour flags (optional, unused)
393          */
394         public function DatabaseIbm_db2($server = false, $user = false, $password = false,
395                                                         $dbName = false, $failFunction = false, $flags = 0,
396                                                         $schema = self::USE_GLOBAL )
397         {
398
399                 global $wgOut, $wgDBmwschema;
400                 # Can't get a reference if it hasn't been set yet
401                 if ( !isset( $wgOut ) ) {
402                         $wgOut = NULL;
403                 }
404                 $this->mOut =& $wgOut;
405                 $this->mFailFunction = $failFunction;
406                 $this->mFlags = DBO_TRX | $flags;
407                 
408                 if ( $schema == self::USE_GLOBAL ) {
409                         $this->mSchema = $wgDBmwschema;
410                 }
411                 else {
412                         $this->mSchema = $schema;
413                 }
414                 
415                 $this->open( $server, $user, $password, $dbName);
416         }
417         
418         /**
419          * Opens a database connection and returns it
420          * Closes any existing connection
421          * @return a fresh connection
422          * @param string $server hostname
423          * @param string $user
424          * @param string $password
425          * @param string $dbName database name
426          */
427         public function open( $server, $user, $password, $dbName )
428         {
429                 // Load the port number
430                 global $wgDBport_db2, $wgDBcataloged;
431                 wfProfileIn( __METHOD__ );
432                 
433                 // Load IBM DB2 driver if missing
434                 if (!@extension_loaded('ibm_db2')) {
435                         @dl('ibm_db2.so');
436                 }
437                 // Test for IBM DB2 support, to avoid suppressed fatal error
438                 if ( !function_exists( 'db2_connect' ) ) {
439                         $error = "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?\n";
440                         wfDebug($error);
441                         $this->reportConnectionError($error);
442                 }
443
444                 if (!strlen($user)) { // Copied from Postgres
445                         return null;
446                 }
447                 
448                 // Close existing connection
449                 $this->close();
450                 // Cache conn info
451                 $this->mServer = $server;
452                 $this->mPort = $port = $wgDBport_db2;
453                 $this->mUser = $user;
454                 $this->mPassword = $password;
455                 $this->mDBname = $dbName;
456                 $this->mCataloged = $cataloged = $wgDBcataloged;
457                 
458                 if ( $cataloged == self::CATALOGED ) {
459                         $this->openCataloged($dbName, $user, $password);
460                 }
461                 elseif ( $cataloged == self::UNCATALOGED ) {
462                         $this->openUncataloged($dbName, $user, $password, $server, $port);
463                 }
464                 // Don't do this
465                 // Not all MediaWiki code is transactional
466                 // Rather, turn it off in the begin function and turn on after a commit
467                 // db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
468                 db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
469
470                 if ( $this->mConn == false ) {
471                         wfDebug( "DB connection error\n" );
472                         wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
473                         wfDebug( $this->lastError()."\n" );
474                         return null;
475                 }
476
477                 $this->mOpened = true;
478                 $this->applySchema();
479                 
480                 wfProfileOut( __METHOD__ );
481                 return $this->mConn;
482         }
483         
484         /**
485          * Opens a cataloged database connection, sets mConn
486          */
487         protected function openCataloged( $dbName, $user, $password )
488         {
489                 @$this->mConn = db2_connect($dbName, $user, $password);
490         }
491         
492         /**
493          * Opens an uncataloged database connection, sets mConn
494          */
495         protected function openUncataloged( $dbName, $user, $password, $server, $port )
496         {
497                 $str = "DRIVER={IBM DB2 ODBC DRIVER};";
498                 $str .= "DATABASE=$dbName;";
499                 $str .= "HOSTNAME=$server;";
500                 if ($port) $str .= "PORT=$port;";
501                 $str .= "PROTOCOL=TCPIP;";
502                 $str .= "UID=$user;";
503                 $str .= "PWD=$password;";
504                 
505                 @$this->mConn = db2_connect($str, $user, $password);
506         }
507         
508         /**
509          * Closes a database connection, if it is open
510          * Returns success, true if already closed
511          */
512         public function close() {
513                 $this->mOpened = false;
514                 if ( $this->mConn ) {
515                         if ($this->trxLevel() > 0) {
516                                 $this->commit();
517                         }
518                         return db2_close( $this->mConn );
519                 }
520                 else {
521                         return true;
522                 }
523         }
524         
525         /**
526          * Returns a fresh instance of this class
527          * @static
528          * @return 
529          * @param string $server hostname of database server
530          * @param string $user username
531          * @param string $password
532          * @param string $dbName database name on the server
533          * @param function $failFunction (optional)
534          * @param integer $flags database behaviour flags (optional, unused)
535          */
536         static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0)
537         {
538                 return new DatabaseIbm_db2( $server, $user, $password, $dbName, $failFunction, $flags );
539         }
540         
541         /**
542          * Retrieves the most current database error
543          * Forces a database rollback
544          */
545         public function lastError() {
546                 if ($this->lastError2()) {
547                         $this->rollback();
548                         return true;
549                 }
550                 return false;
551         }
552         
553         private function lastError2() {
554                 $connerr = db2_conn_errormsg();
555                 if ($connerr) return $connerr;
556                 $stmterr = db2_stmt_errormsg();
557                 if ($stmterr) return $stmterr;
558                 if ($this->mConn) return "No open connection.";
559                 if ($this->mOpened) return "No open connection allegedly.";
560                 
561                 return false;
562         }
563         
564         /**
565          * Get the last error number
566          * Return 0 if no error
567          * @return integer
568          */
569         public function lastErrno() {
570                 $connerr = db2_conn_error();
571                 if ($connerr) return $connerr;
572                 $stmterr = db2_stmt_error();
573                 if ($stmterr) return $stmterr;
574                 return 0;
575         }
576         
577         /**
578          * Is a database connection open?
579          * @return 
580          */
581         public function isOpen() { return $this->mOpened; }
582         
583         /**
584          * The DBMS-dependent part of query()
585          * @param  $sql String: SQL query.
586          * @return object Result object to feed to fetchObject, fetchRow, ...; or false on failure
587          * @access private
588          */
589         /*private*/
590         public function doQuery( $sql ) {
591                 //print "<li><pre>$sql</pre></li>";
592                 // Switch into the correct namespace
593                 $this->applySchema();
594                 
595                 $ret = db2_exec( $this->mConn, $sql );
596                 if( !$ret ) {
597                         print "<br><pre>";
598                         print $sql;
599                         print "</pre><br>";
600                         $error = db2_stmt_errormsg();
601                         throw new DBUnexpectedError($this,  'SQL error: ' . htmlspecialchars( $error ) );
602                 }
603                 $this->mLastResult = $ret;
604                 $this->mAffectedRows = NULL;    // Not calculated until asked for
605                 return $ret;
606         }
607         
608         /**
609          * @return string Version information from the database
610          */
611         public function getServerVersion() {
612                 $info = db2_server_info( $this->mConn );
613                 return $info->DBMS_VER;
614         }
615         
616         /**
617          * Queries whether a given table exists
618          * @return boolean
619          */
620         public function tableExists( $table ) {
621                 $schema = $this->mSchema;
622                 $sql = <<< EOF
623 SELECT COUNT(*) FROM SYSIBM.SYSTABLES ST
624 WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema'
625 EOF;
626                 $res = $this->query( $sql );
627                 if (!$res) return false;
628                 
629                 // If the table exists, there should be one of it
630                 @$row = $this->fetchRow($res);
631                 $count = $row[0];
632                 if ($count == '1' or $count == 1) {
633                         return true;
634                 }
635                 
636                 return false;
637         }
638         
639         /**
640          * Fetch the next row from the given result object, in object form.
641          * Fields can be retrieved with $row->fieldname, with fields acting like
642          * member variables.
643          *
644          * @param $res SQL result object as returned from Database::query(), etc.
645          * @return DB2 row object
646          * @throws DBUnexpectedError Thrown if the database returns an error
647          */
648         public function fetchObject( $res ) {
649                 if ( $res instanceof ResultWrapper ) {
650                         $res = $res->result;
651                 }
652                 @$row = db2_fetch_object( $res );
653                 if( $this->lastErrno() ) {
654                         throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
655                 }
656                 // Make field names lowercase for compatibility with MySQL
657                 if ($row)
658                 {
659                         $row2 = new BlankObject();
660                         foreach ($row as $key => $value)
661                         {
662                                 $keyu = strtolower($key);
663                                 $row2->$keyu = $value;
664                         }
665                         $row = $row2;
666                 }
667                 return $row;
668         }
669
670         /**
671          * Fetch the next row from the given result object, in associative array
672          * form.  Fields are retrieved with $row['fieldname'].
673          *
674          * @param $res SQL result object as returned from Database::query(), etc.
675          * @return DB2 row object
676          * @throws DBUnexpectedError Thrown if the database returns an error
677          */
678         public function fetchRow( $res ) {
679                 if ( $res instanceof ResultWrapper ) {
680                         $res = $res->result;
681                 }
682                 @$row = db2_fetch_array( $res );
683                 if ( $this->lastErrno() ) {
684                         throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
685                 }
686                 return $row;
687         }
688         
689         /**
690          * Override if introduced to base Database class
691          */
692         public function initial_setup() {
693                 // do nothing
694         }
695         
696         /**
697          * Create tables, stored procedures, and so on
698          */
699         public function setup_database() {
700                 // Timeout was being changed earlier due to mysterious crashes
701                 // Changing it now may cause more problems than not changing it
702                 //set_time_limit(240);
703                 try {
704                         // TODO: switch to root login if available
705                         
706                         // Switch into the correct namespace
707                         $this->applySchema();
708                         $this->begin();
709                         
710                         $res = dbsource( "../maintenance/ibm_db2/tables.sql", $this);
711                         $res = null;
712         
713                         // TODO: update mediawiki_version table
714                         
715                         // TODO: populate interwiki links
716                         
717                         $this->commit();
718                 }
719                 catch (MWException $mwe)
720                 {
721                         print "<br><pre>$mwe</pre><br>";
722                 }
723         }
724
725         /**
726          * Escapes strings
727          * Doesn't escape numbers
728          * @param string s string to escape
729          * @return escaped string
730          */
731         public function addQuotes( $s ) {
732                 //wfDebug("DB2::addQuotes($s)\n");
733                 if ( is_null( $s ) ) {
734                         return "NULL";
735                 } else if ($s instanceof Blob) {
736                         return "'".$s->fetch($s)."'";
737                 }
738                 $s = $this->strencode($s);
739                 if ( is_numeric($s) ) {
740                         return $s;
741                 }
742                 else {
743                         return "'$s'";
744                 }
745         }
746         
747         /**
748          * Escapes strings
749          * Only escapes numbers going into non-numeric fields
750          * @param string s string to escape
751          * @return escaped string
752          */
753         public function addQuotesSmart( $table, $field, $s ) {
754                 if ( is_null( $s ) ) {
755                         return "NULL";
756                 } else if ($s instanceof Blob) {
757                         return "'".$s->fetch($s)."'";
758                 }
759                 $s = $this->strencode($s);
760                 if ( is_numeric($s) ) {
761                         // Check with the database if the column is actually numeric
762                         // This allows for numbers in titles, etc
763                         $res = $this->doQuery("SELECT $field FROM $table FETCH FIRST 1 ROWS ONLY");
764                         $type = db2_field_type($res, strtoupper($field));
765                         if ( $this->is_numeric_type( $type ) ) {
766                                 //wfDebug("DB2: Numeric value going in a numeric column: $s in $type $field in $table\n");
767                                 return $s;
768                         }
769                         else {
770                                 wfDebug("DB2: Numeric in non-numeric: '$s' in $type $field in $table\n");
771                                 return "'$s'";
772                         }
773                 }
774                 else {
775                         return "'$s'";
776                 }
777         }
778         
779         /**
780          * Verifies that a DB2 column/field type is numeric
781          * @return bool true if numeric
782          * @param string $type DB2 column type
783          */
784         public function is_numeric_type( $type ) {
785                 switch (strtoupper($type)) {
786                 case 'SMALLINT':
787                 case 'INTEGER':
788                 case 'INT':
789                 case 'BIGINT':
790                 case 'DECIMAL':
791                 case 'REAL':
792                 case 'DOUBLE':
793                 case 'DECFLOAT':
794                         return true;
795                 }
796                 return false;
797         }
798         
799         /**
800          * Alias for addQuotes()
801          * @param string s string to escape
802          * @return escaped string
803          */
804         public function strencode( $s ) {
805                 // Bloody useless function
806                 //  Prepends backslashes to \x00, \n, \r, \, ', " and \x1a. 
807                 //  But also necessary
808                 $s = db2_escape_string($s);
809                 // Wide characters are evil -- some of them look like '
810                 $s = utf8_encode($s);
811                 // Fix its stupidity
812                 $from = array("\\\\",   "\\'",  '\\n',  '\\t',  '\\"',  '\\r');
813                 $to =   array("\\",             "''",   "\n",   "\t",   '"',    "\r");
814                 $s = str_replace($from, $to, $s); // DB2 expects '', not \' escaping
815                 return $s;
816         }
817         
818         /**
819          * Switch into the database schema
820          */
821         protected function applySchema() {
822                 if ( !($this->mSchemaSet) ) {
823                         $this->mSchemaSet = true;
824                         $this->begin();
825                         $this->doQuery("SET SCHEMA = $this->mSchema");
826                         $this->commit();
827                 }       
828         }
829         
830         /**
831          * Start a transaction (mandatory)
832          */
833         public function begin() {
834                 // turn off auto-commit
835                 db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
836                 $this->mTrxLevel = 1;
837         }
838         
839         /**
840          * End a transaction
841          * Must have a preceding begin()
842          */
843         public function commit() {
844                 db2_commit($this->mConn);
845                 // turn auto-commit back on
846                 db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
847                 $this->mTrxLevel = 0;
848         }
849         
850         /**
851          * Cancel a transaction
852          */
853         public function rollback() {
854                 db2_rollback($this->mConn);
855                 // turn auto-commit back on
856                 // not sure if this is appropriate
857                 db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
858                 $this->mTrxLevel = 0;
859         }
860         
861         /**
862          * Makes an encoded list of strings from an array
863          * $mode:
864          *        LIST_COMMA         - comma separated, no field names
865          *        LIST_AND           - ANDed WHERE clause (without the WHERE)
866          *        LIST_OR            - ORed WHERE clause (without the WHERE)
867          *        LIST_SET           - comma separated with field names, like a SET clause
868          *        LIST_NAMES         - comma separated field names
869          */
870         public function makeList( $a, $mode = LIST_COMMA ) {
871                 wfDebug("DB2::makeList()\n");
872                 if ( !is_array( $a ) ) {
873                         throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
874                 }
875
876                 $first = true;
877                 $list = '';
878                 foreach ( $a as $field => $value ) {
879                         if ( !$first ) {
880                                 if ( $mode == LIST_AND ) {
881                                         $list .= ' AND ';
882                                 } elseif($mode == LIST_OR) {
883                                         $list .= ' OR ';
884                                 } else {
885                                         $list .= ',';
886                                 }
887                         } else {
888                                 $first = false;
889                         }
890                         if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
891                                 $list .= "($value)";
892                         } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
893                                 $list .= "$value";
894                         } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
895                                 if( count( $value ) == 0 ) {
896                                         throw new MWException( __METHOD__.': empty input' );
897                                 } elseif( count( $value ) == 1 ) {
898                                         // Special-case single values, as IN isn't terribly efficient
899                                         // Don't necessarily assume the single key is 0; we don't
900                                         // enforce linear numeric ordering on other arrays here.
901                                         $value = array_values( $value );
902                                         $list .= $field." = ".$this->addQuotes( $value[0] );
903                                 } else {
904                                         $list .= $field." IN (".$this->makeList($value).") ";
905                                 }
906                         } elseif( is_null($value) ) {
907                                 if ( $mode == LIST_AND || $mode == LIST_OR ) {
908                                         $list .= "$field IS ";
909                                 } elseif ( $mode == LIST_SET ) {
910                                         $list .= "$field = ";
911                                 }
912                                 $list .= 'NULL';
913                         } else {
914                                 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
915                                         $list .= "$field = ";
916                                 }
917                                 if ( $mode == LIST_NAMES ) {
918                                         $list .= $value;
919                                 }
920                                 // Leo: Can't insert quoted numbers into numeric columns
921                                 // (?) Might cause other problems. May have to check column type before insertion.
922                                 else if ( is_numeric($value) ) {
923                                         $list .= $value;
924                                 }
925                                 else {
926                                         $list .= $this->addQuotes( $value );
927                                 }
928                         }
929                 }
930                 return $list;
931         }
932         
933         /**
934          * Makes an encoded list of strings from an array
935          * Quotes numeric values being inserted into non-numeric fields
936          * @return string
937          * @param string $table name of the table
938          * @param array $a list of values
939          * @param $mode:
940          *        LIST_COMMA         - comma separated, no field names
941          *        LIST_AND           - ANDed WHERE clause (without the WHERE)
942          *        LIST_OR            - ORed WHERE clause (without the WHERE)
943          *        LIST_SET           - comma separated with field names, like a SET clause
944          *        LIST_NAMES         - comma separated field names
945          */
946         public function makeListSmart( $table, $a, $mode = LIST_COMMA ) {
947                 if ( !is_array( $a ) ) {
948                         throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
949                 }
950
951                 $first = true;
952                 $list = '';
953                 foreach ( $a as $field => $value ) {
954                         if ( !$first ) {
955                                 if ( $mode == LIST_AND ) {
956                                         $list .= ' AND ';
957                                 } elseif($mode == LIST_OR) {
958                                         $list .= ' OR ';
959                                 } else {
960                                         $list .= ',';
961                                 }
962                         } else {
963                                 $first = false;
964                         }
965                         if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
966                                 $list .= "($value)";
967                         } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
968                                 $list .= "$value";
969                         } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
970                                 if( count( $value ) == 0 ) {
971                                         throw new MWException( __METHOD__.': empty input' );
972                                 } elseif( count( $value ) == 1 ) {
973                                         // Special-case single values, as IN isn't terribly efficient
974                                         // Don't necessarily assume the single key is 0; we don't
975                                         // enforce linear numeric ordering on other arrays here.
976                                         $value = array_values( $value );
977                                         $list .= $field." = ".$this->addQuotes( $value[0] );
978                                 } else {
979                                         $list .= $field." IN (".$this->makeList($value).") ";
980                                 }
981                         } elseif( is_null($value) ) {
982                                 if ( $mode == LIST_AND || $mode == LIST_OR ) {
983                                         $list .= "$field IS ";
984                                 } elseif ( $mode == LIST_SET ) {
985                                         $list .= "$field = ";
986                                 }
987                                 $list .= 'NULL';
988                         } else {
989                                 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
990                                         $list .= "$field = ";
991                                 }
992                                 if ( $mode == LIST_NAMES ) {
993                                         $list .= $value;
994                                 }
995                                 else {
996                                         $list .= $this->addQuotesSmart( $table, $field, $value );
997                                 }
998                         }
999                 }
1000                 return $list;
1001         }
1002         
1003         /**
1004          * Construct a LIMIT query with optional offset
1005          * This is used for query pages
1006          * $sql string SQL query we will append the limit too
1007          * $limit integer the SQL limit
1008          * $offset integer the SQL offset (default false)
1009          */
1010         public function limitResult($sql, $limit, $offset=false) {
1011                 if( !is_numeric($limit) ) {
1012                         throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
1013                 }
1014                 if( $offset ) {
1015                         wfDebug("Offset parameter not supported in limitResult()\n");
1016                 }
1017                 // TODO implement proper offset handling
1018                 // idea: get all the rows between 0 and offset, advance cursor to offset
1019                 return "$sql FETCH FIRST $limit ROWS ONLY ";
1020         }
1021         
1022         /**
1023          * Handle reserved keyword replacement in table names
1024          * @return 
1025          * @param $name Object
1026          */
1027         public function tableName( $name ) {
1028                 # Replace reserved words with better ones
1029                 switch( $name ) {
1030                         case 'user':
1031                                 return 'mwuser';
1032                         case 'text':
1033                                 return 'pagecontent';
1034                         default:
1035                                 return $name;
1036                 }
1037         }
1038         
1039         /**
1040          * Generates a timestamp in an insertable format
1041          * @return string timestamp value
1042          * @param timestamp $ts
1043          */
1044         public function timestamp( $ts=0 ) {
1045                 // TS_MW cannot be easily distinguished from an integer
1046                 return wfTimestamp(TS_DB2,$ts);
1047         }
1048
1049         /**
1050          * Return the next in a sequence, save the value for retrieval via insertId()
1051          * @param string seqName Name of a defined sequence in the database
1052          * @return next value in that sequence
1053          */
1054         public function nextSequenceValue( $seqName ) {
1055                 $safeseq = preg_replace( "/'/", "''", $seqName );
1056                 $res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
1057                 $row = $this->fetchRow( $res );
1058                 $this->mInsertId = $row[0];
1059                 $this->freeResult( $res );
1060                 return $this->mInsertId;
1061         }
1062         
1063         /**
1064          * This must be called after nextSequenceVal
1065          * @return Last sequence value used as a primary key
1066          */
1067         public function insertId() {
1068                 return $this->mInsertId;
1069         }
1070         
1071         /**
1072          * INSERT wrapper, inserts an array into a table
1073          *
1074          * $args may be a single associative array, or an array of these with numeric keys,
1075          * for multi-row insert
1076          *
1077          * @param array $table   String: Name of the table to insert to.
1078          * @param array $args    Array: Items to insert into the table.
1079          * @param array $fname   String: Name of the function, for profiling
1080          * @param mixed $options String or Array. Valid options: IGNORE
1081          *
1082          * @return bool Success of insert operation. IGNORE always returns true.
1083          */
1084         public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', $options = array() ) {
1085                 wfDebug("DB2::insert($table)\n");
1086                 if ( !count( $args ) ) {
1087                         return true;
1088                 }
1089
1090                 $table = $this->tableName( $table );
1091
1092                 if ( !is_array( $options ) )
1093                         $options = array( $options );
1094
1095                 if ( isset( $args[0] ) && is_array( $args[0] ) ) {
1096                 }
1097                 else {
1098                         $args = array($args);
1099                 }
1100                 $keys = array_keys( $args[0] );
1101
1102                 // If IGNORE is set, we use savepoints to emulate mysql's behavior
1103                 $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
1104                 
1105                 // Cache autocommit value at the start
1106                 $oldautocommit = db2_autocommit($this->mConn);
1107
1108                 // If we are not in a transaction, we need to be for savepoint trickery
1109                 $didbegin = 0;
1110                 if (! $this->mTrxLevel) {
1111                         $this->begin();
1112                         $didbegin = 1;
1113                 }
1114                 if ( $ignore ) {
1115                         $olde = error_reporting( 0 );
1116                         // For future use, we may want to track the number of actual inserts
1117                         // Right now, insert (all writes) simply return true/false
1118                         $numrowsinserted = 0;
1119                 }
1120
1121                 $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
1122
1123                 if ( !$ignore ) {
1124                         $first = true;
1125                         foreach ( $args as $row ) {
1126                                 if ( $first ) {
1127                                         $first = false;
1128                                 } else {
1129                                         $sql .= ',';
1130                                 }
1131                                 $sql .= '(' . $this->makeListSmart( $table, $row ) . ')';
1132                         }
1133                         $res = (bool)$this->query( $sql, $fname, $ignore );
1134                 }
1135                 else {
1136                         $res = true;
1137                         $origsql = $sql;
1138                         foreach ( $args as $row ) {
1139                                 $tempsql = $origsql;
1140                                 $tempsql .= '(' . $this->makeListSmart( $table, $row ) . ')';
1141
1142                                 if ( $ignore ) {
1143                                         db2_exec($this->mConn, "SAVEPOINT $ignore");
1144                                 }
1145
1146                                 $tempres = (bool)$this->query( $tempsql, $fname, $ignore );
1147
1148                                 if ( $ignore ) {
1149                                         $bar = db2_stmt_error();
1150                                         if ($bar != false) {
1151                                                 db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore" );
1152                                         }
1153                                         else {
1154                                                 db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore" );
1155                                                 $numrowsinserted++;
1156                                         }
1157                                 }
1158
1159                                 // If any of them fail, we fail overall for this function call
1160                                 // Note that this will be ignored if IGNORE is set
1161                                 if (! $tempres)
1162                                         $res = false;
1163                         }
1164                 }
1165
1166                 if ($didbegin) {
1167                         $this->commit();
1168                 }
1169                 // if autocommit used to be on, it's ok to commit everything
1170                 else if ($oldautocommit)
1171                 {
1172                         $this->commit();
1173                 }
1174                 
1175                 if ( $ignore ) {
1176                         $olde = error_reporting( $olde );
1177                         // Set the affected row count for the whole operation
1178                         $this->mAffectedRows = $numrowsinserted;
1179
1180                         // IGNORE always returns true
1181                         return true;
1182                 }
1183                 
1184                 return $res;
1185         }
1186         
1187         /**
1188          * UPDATE wrapper, takes a condition array and a SET array
1189          *
1190          * @param string $table  The table to UPDATE
1191          * @param array  $values An array of values to SET
1192          * @param array  $conds  An array of conditions (WHERE). Use '*' to update all rows.
1193          * @param string $fname  The Class::Function calling this function
1194          *                       (for the log)
1195          * @param array  $options An array of UPDATE options, can be one or
1196          *                        more of IGNORE, LOW_PRIORITY
1197          * @return bool
1198          */
1199         function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
1200                 $table = $this->tableName( $table );
1201                 $opts = $this->makeUpdateOptions( $options );
1202                 $sql = "UPDATE $opts $table SET " . $this->makeListSmart( $table, $values, LIST_SET );
1203                 if ( $conds != '*' ) {
1204                         $sql .= " WHERE " . $this->makeListSmart( $table, $conds, LIST_AND );
1205                 }
1206                 return $this->query( $sql, $fname );
1207         }
1208         
1209         /**
1210          * DELETE query wrapper
1211          *
1212          * Use $conds == "*" to delete all rows
1213          */
1214         function delete( $table, $conds, $fname = 'Database::delete' ) {
1215                 if ( !$conds ) {
1216                         throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
1217                 }
1218                 $table = $this->tableName( $table );
1219                 $sql = "DELETE FROM $table";
1220                 if ( $conds != '*' ) {
1221                         $sql .= ' WHERE ' . $this->makeListSmart( $table, $conds, LIST_AND );
1222                 }
1223                 return $this->query( $sql, $fname );
1224         }
1225         
1226         /**
1227          * Returns the number of rows affected by the last query or 0
1228          * @return int the number of rows affected by the last query
1229          */
1230         public function affectedRows() {
1231                 if ( !is_null( $this->mAffectedRows ) ) {
1232                         // Forced result for simulated queries
1233                         return $this->mAffectedRows;
1234                 }
1235                 if( empty( $this->mLastResult ) )
1236                         return 0;
1237                 return db2_num_rows( $this->mLastResult );
1238         }
1239         
1240         /**
1241          * USE INDEX clause
1242          * DB2 doesn't have them and returns ""
1243          * @param sting $index
1244          */
1245         public function useIndexClause( $index ) {
1246                 return "";
1247         }
1248         
1249         /**
1250          * Simulates REPLACE with a DELETE followed by INSERT
1251          * @param $table Object
1252          * @param array $uniqueIndexes array consisting of indexes and arrays of indexes
1253          * @param array $rows Rows to insert
1254          * @param string $fname Name of the function for profiling
1255          * @return nothing
1256          */
1257         function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseIbm_db2::replace' ) {
1258                 $table = $this->tableName( $table );
1259
1260                 if (count($rows)==0) {
1261                         return;
1262                 }
1263
1264                 # Single row case
1265                 if ( !is_array( reset( $rows ) ) ) {
1266                         $rows = array( $rows );
1267                 }
1268
1269                 foreach( $rows as $row ) {
1270                         # Delete rows which collide
1271                         if ( $uniqueIndexes ) {
1272                                 $sql = "DELETE FROM $table WHERE ";
1273                                 $first = true;
1274                                 foreach ( $uniqueIndexes as $index ) {
1275                                         if ( $first ) {
1276                                                 $first = false;
1277                                                 $sql .= "(";
1278                                         } else {
1279                                                 $sql .= ') OR (';
1280                                         }
1281                                         if ( is_array( $index ) ) {
1282                                                 $first2 = true;
1283                                                 foreach ( $index as $col ) {
1284                                                         if ( $first2 ) {
1285                                                                 $first2 = false;
1286                                                         } else {
1287                                                                 $sql .= ' AND ';
1288                                                         }
1289                                                         $sql .= $col.'=' . $this->addQuotes( $row[$col] );
1290                                                 }
1291                                         } else {
1292                                                 $sql .= $index.'=' . $this->addQuotes( $row[$index] );
1293                                         }
1294                                 }
1295                                 $sql .= ')';
1296                                 $this->query( $sql, $fname );
1297                         }
1298
1299                         # Now insert the row
1300                         $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
1301                                 $this->makeList( $row, LIST_COMMA ) . ')';
1302                         $this->query( $sql, $fname );
1303                 }
1304         }
1305         
1306         /**
1307          * Returns the number of rows in the result set
1308          * Has to be called right after the corresponding select query
1309          * @param Object $res result set
1310          * @return int number of rows
1311          */
1312         public function numRows( $res ) {
1313                 if ( $res instanceof ResultWrapper ) {
1314                         $res = $res->result;
1315                 }
1316                 if ( $this->mNumRows ) {
1317                         return $this->mNumRows;
1318                 }
1319                 else {
1320                         return 0;
1321                 }
1322         }
1323         
1324         /**
1325          * Moves the row pointer of the result set
1326          * @param Object $res result set
1327          * @param int $row row number
1328          * @return success or failure
1329          */
1330         public function dataSeek( $res, $row ) {
1331                 if ( $res instanceof ResultWrapper ) {
1332                         $res = $res->result;
1333                 }
1334                 return db2_fetch_row( $res, $row );
1335         }
1336         
1337         ###
1338         # Fix notices in Block.php 
1339         ###
1340         
1341         /**
1342          * Frees memory associated with a statement resource
1343          * @param Object $res Statement resource to free
1344          * @return bool success or failure
1345          */
1346         public function freeResult( $res ) {
1347                 if ( $res instanceof ResultWrapper ) {
1348                         $res = $res->result;
1349                 }
1350                 if ( !@db2_free_result( $res ) ) {
1351                         throw new DBUnexpectedError($this,  "Unable to free DB2 result\n" );
1352                 }
1353         }
1354         
1355         /**
1356          * Returns the number of columns in a resource
1357          * @param Object $res Statement resource
1358          * @return Number of fields/columns in resource
1359          */
1360         public function numFields( $res ) {
1361                 if ( $res instanceof ResultWrapper ) {
1362                         $res = $res->result;
1363                 }
1364                 return db2_num_fields( $res );
1365         }
1366         
1367         /**
1368          * Returns the nth column name
1369          * @param Object $res Statement resource
1370          * @param int $n Index of field or column
1371          * @return string name of nth column
1372          */
1373         public function fieldName( $res, $n ) {
1374                 if ( $res instanceof ResultWrapper ) {
1375                         $res = $res->result;
1376                 }
1377                 return db2_field_name( $res, $n );
1378         }
1379         
1380         /**
1381          * SELECT wrapper
1382          *
1383          * @param mixed  $table   Array or string, table name(s) (prefix auto-added)
1384          * @param mixed  $vars    Array or string, field name(s) to be retrieved
1385          * @param mixed  $conds   Array or string, condition(s) for WHERE
1386          * @param string $fname   Calling function name (use __METHOD__) for logs/profiling
1387          * @param array  $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
1388          *                        see Database::makeSelectOptions code for list of supported stuff
1389          * @param array $join_conds Associative array of table join conditions (optional)
1390          *                        (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
1391          * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
1392          */
1393         public function select( $table, $vars, $conds='', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
1394         {
1395                 $res = parent::select( $table, $vars, $conds, $fname, $options, $join_conds );
1396                 
1397                 // We must adjust for offset
1398                 if ( isset( $options['LIMIT'] ) ) {
1399                         if ( isset ($options['OFFSET'] ) ) {
1400                                 $limit = $options['LIMIT'];
1401                                 $offset = $options['OFFSET'];
1402                         }
1403                 }
1404                 
1405                 
1406                 // DB2 does not have a proper num_rows() function yet, so we must emulate it
1407                 // DB2 9.5.3/9.5.4 and the corresponding ibm_db2 driver will introduce a working one
1408                 // Yay!
1409                 
1410                 // we want the count
1411                 $vars2 = array('count(*) as num_rows');
1412                 // respecting just the limit option
1413                 $options2 = array();
1414                 if ( isset( $options['LIMIT'] ) ) $options2['LIMIT'] = $options['LIMIT'];
1415                 // but don't try to emulate for GROUP BY
1416                 if ( isset( $options['GROUP BY'] ) ) return $res;
1417                 
1418                 $res2 = parent::select( $table, $vars2, $conds, $fname, $options2, $join_conds );
1419                 $obj = $this->fetchObject($res2);
1420                 $this->mNumRows = $obj->num_rows;
1421                 
1422                 wfDebug("DatabaseIbm_db2::select: There are $this->mNumRows rows.\n");
1423                 
1424                 return $res;
1425         }
1426         
1427         /**
1428          * Handles ordering, grouping, and having options ('GROUP BY' => colname)
1429          * Has limited support for per-column options (colnum => 'DISTINCT')
1430          * 
1431          * @private
1432          *
1433          * @param array $options an associative array of options to be turned into
1434          *              an SQL query, valid keys are listed in the function.
1435          * @return array
1436          */
1437         function makeSelectOptions( $options ) {
1438                 $preLimitTail = $postLimitTail = '';
1439                 $startOpts = '';
1440
1441                 $noKeyOptions = array();
1442                 foreach ( $options as $key => $option ) {
1443                         if ( is_numeric( $key ) ) {
1444                                 $noKeyOptions[$option] = true;
1445                         }
1446                 }
1447
1448                 if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
1449                 if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
1450                 if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
1451                 
1452                 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
1453                 
1454                 return array( $startOpts, '', $preLimitTail, $postLimitTail );
1455         }
1456         
1457         /**
1458          * Returns link to IBM DB2 free download
1459          * @return string wikitext of a link to the server software's web site
1460          */
1461         public function getSoftwareLink() {
1462                 return "[http://www.ibm.com/software/data/db2/express/?s_cmp=ECDDWW01&s_tact=MediaWiki IBM DB2]";
1463         }
1464         
1465         /**
1466          * Does nothing
1467          * @param object $db
1468          * @return bool true
1469          */
1470         public function selectDB( $db ) {
1471                 return true;
1472         }
1473         
1474         /**
1475          * Returns an SQL expression for a simple conditional.
1476          * Uses CASE on DB2
1477          *
1478          * @param string $cond SQL expression which will result in a boolean value
1479          * @param string $trueVal SQL expression to return if true
1480          * @param string $falseVal SQL expression to return if false
1481          * @return string SQL fragment
1482          */
1483         public function conditional( $cond, $trueVal, $falseVal ) {
1484                 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
1485         }
1486         
1487         ###
1488         # Fix search crash
1489         ###
1490         /**
1491          * Get search engine class. All subclasses of this
1492          * need to implement this if they wish to use searching.
1493          * 
1494          * @return string
1495          */
1496         public function getSearchEngine() {
1497                 return "SearchIBM_DB2";
1498         }
1499         
1500         ###
1501         # Tuesday the 14th of October, 2008
1502         ###
1503         /**
1504          * Did the last database access fail because of deadlock?
1505          * @return bool
1506          */
1507         public function wasDeadlock() {
1508                 // get SQLSTATE
1509                 $err = $this->lastErrno();
1510                 switch($err) {
1511                         case '40001':   // sql0911n, Deadlock or timeout, rollback
1512                         case '57011':   // sql0904n, Resource unavailable, no rollback
1513                         case '57033':   // sql0913n, Deadlock or timeout, no rollback
1514                         wfDebug("In a deadlock because of SQLSTATE $err");
1515                         return true;
1516                 }
1517                 return false;
1518         }
1519         
1520         /**
1521          * Ping the server and try to reconnect if it there is no connection
1522          * The connection may be closed and reopened while this happens
1523          * @return bool whether the connection exists
1524          */
1525         public function ping() {
1526                 // db2_ping() doesn't exist
1527                 // Emulate
1528                 $this->close();
1529                 if ($this->mCataloged == NULL) {
1530                         return false;
1531                 }
1532                 else if ($this->mCataloged) {
1533                         $this->mConn = $this->openCataloged($this->mDBName, $this->mUser, $this->mPassword);
1534                 }
1535                 else if (!$this->mCataloged) {
1536                         $this->mConn = $this->openUncataloged($this->mDBName, $this->mUser, $this->mPassword, $this->mServer, $this->mPort);
1537                 }
1538                 return false;
1539         }
1540         ######################################
1541         # Unimplemented and not applicable
1542         ######################################
1543         /**
1544          * Not implemented
1545          * @return string ''
1546          * @deprecated
1547          */
1548         public function getStatus( $which ) { wfDebug('Not implemented for DB2: getStatus()'); return ''; }
1549         /**
1550          * Not implemented
1551          * @deprecated
1552          */
1553         public function setTimeout( $timeout ) { wfDebug('Not implemented for DB2: setTimeout()'); }
1554         /**
1555          * Not implemented
1556          * TODO
1557          * @return bool true
1558          */
1559         public function lock( $lockName, $method ) { wfDebug('Not implemented for DB2: lock()'); return true; }
1560         /**
1561          * Not implemented
1562          * TODO
1563          * @return bool true
1564          */
1565         public function unlock( $lockName, $method ) { wfDebug('Not implemented for DB2: unlock()'); return true; }
1566         /**
1567          * Not implemented
1568          * @deprecated
1569          */
1570         public function setFakeSlaveLag( $lag ) { wfDebug('Not implemented for DB2: setFakeSlaveLag()'); }
1571         /**
1572          * Not implemented
1573          * @deprecated
1574          */
1575         public function setFakeMaster( $enabled ) { wfDebug('Not implemented for DB2: setFakeMaster()'); }
1576         /**
1577          * Not implemented
1578          * @return string $sql
1579          * @deprecated
1580          */ 
1581         public function limitResultForUpdate($sql, $num) { return $sql; }
1582         /**
1583          * No such option
1584          * @return string ''
1585          * @deprecated
1586          */
1587         public function lowPriorityOption() { return ''; }
1588         
1589         ######################################
1590         # Reflection
1591         ######################################
1592         
1593         /**
1594          * Query whether a given column exists in the mediawiki schema
1595          * @param string $table name of the table
1596          * @param string $field name of the column
1597          * @param string $fname function name for logging and profiling
1598          */
1599         public function fieldExists( $table, $field, $fname = 'DatabaseIbm_db2::fieldExists' ) {
1600                 $table = $this->tableName( $table );
1601                 $schema = $this->mSchema;
1602                 $etable = preg_replace("/'/", "''", $table);
1603                 $eschema = preg_replace("/'/", "''", $schema);
1604                 $ecol = preg_replace("/'/", "''", $field);
1605                 $sql = <<<SQL
1606 SELECT 1 as fieldexists
1607 FROM sysibm.syscolumns sc
1608 WHERE sc.name='$ecol' AND sc.tbname='$etable' AND sc.tbcreator='$eschema'
1609 SQL;
1610                 $res = $this->query( $sql, $fname );
1611                 $count = $res ? $this->numRows($res) : 0;
1612                 if ($res)
1613                         $this->freeResult( $res );
1614                 return $count;
1615         }
1616         
1617         /**
1618          * Returns information about an index
1619          * If errors are explicitly ignored, returns NULL on failure
1620          * @param string $table table name
1621          * @param string $index index name
1622          * @param string
1623          * @return object query row in object form
1624          */
1625         public function indexInfo( $table, $index, $fname = 'DatabaseIbm_db2::indexExists' ) {
1626                 $table = $this->tableName( $table );
1627                 $sql = <<<SQL
1628 SELECT name as indexname
1629 FROM sysibm.sysindexes si
1630 WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
1631 SQL;
1632                 $res = $this->query( $sql, $fname );
1633                 if ( !$res ) {
1634                         return NULL;
1635                 }
1636                 $row = $this->fetchObject( $res );
1637                 if ($row != NULL) return $row;
1638                 else return false;
1639         }
1640         
1641         /**
1642          * Returns an information object on a table column
1643          * @param string $table table name
1644          * @param string $field column name
1645          * @return IBM_DB2Field
1646          */
1647         public function fieldInfo( $table, $field ) {
1648                 return IBM_DB2Field::fromText($this, $table, $field);
1649         }
1650         
1651         /**
1652          * db2_field_type() wrapper
1653          * @param Object $res Result of executed statement
1654          * @param mixed $index number or name of the column
1655          * @return string column type
1656          */
1657         public function fieldType( $res, $index ) {
1658                 if ( $res instanceof ResultWrapper ) {
1659                         $res = $res->result;
1660                 }
1661                 return db2_field_type( $res, $index );
1662         }
1663         
1664         /**
1665          * Verifies that an index was created as unique
1666          * @param string $table table name
1667          * @param string $index index name
1668          * @param string $fnam function name for profiling
1669          * @return bool
1670          */
1671         public function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
1672                 $table = $this->tableName( $table );
1673                 $sql = <<<SQL
1674 SELECT si.name as indexname
1675 FROM sysibm.sysindexes si
1676 WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
1677 AND si.uniquerule IN ('U', 'P')
1678 SQL;
1679                 $res = $this->query( $sql, $fname );
1680                 if ( !$res ) {
1681                         return null;
1682                 }
1683                 if ($this->fetchObject( $res )) {
1684                         return true;
1685                 }
1686                 return false;
1687
1688         }
1689         
1690         /**
1691          * Returns the size of a text field, or -1 for "unlimited"
1692          * @param string $table table name
1693          * @param string $field column name
1694          * @return int length or -1 for unlimited
1695          */
1696         public function textFieldSize( $table, $field ) {
1697                 $table = $this->tableName( $table );
1698                 $sql = <<<SQL
1699 SELECT length as size
1700 FROM sysibm.syscolumns sc
1701 WHERE sc.name='$field' AND sc.tbname='$table' AND sc.tbcreator='$this->mSchema'
1702 SQL;
1703                 $res = $this->query($sql);
1704                 $row = $this->fetchObject($res);
1705                 $size = $row->size;
1706                 $this->freeResult( $res );
1707                 return $size;
1708         }
1709         
1710         /**
1711          * DELETE where the condition is a join
1712          * @param string $delTable deleting from this table
1713          * @param string $joinTable using data from this table
1714          * @param string $delVar variable in deleteable table
1715          * @param string $joinVar variable in data table
1716          * @param array $conds conditionals for join table
1717          * @param string $fname function name for profiling
1718          */
1719         public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseIbm_db2::deleteJoin" ) {
1720                 if ( !$conds ) {
1721                         throw new DBUnexpectedError($this,  'Database::deleteJoin() called with empty $conds' );
1722                 }
1723
1724                 $delTable = $this->tableName( $delTable );
1725                 $joinTable = $this->tableName( $joinTable );
1726                 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
1727                 if ( $conds != '*' ) {
1728                         $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
1729                 }
1730                 $sql .= ')';
1731
1732                 $this->query( $sql, $fname );
1733         }
1734         
1735         /**
1736          * Estimate rows in dataset
1737          * Returns estimated count, based on COUNT(*) output
1738          * Takes same arguments as Database::select()
1739          * @param string $table table name
1740          * @param array $vars unused
1741          * @param array $conds filters on the table
1742          * @param string $fname function name for profiling
1743          * @param array $options options for select
1744          * @return int row count
1745          */
1746         public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
1747                 $rows = 0;
1748                 $res = $this->select ($table, 'COUNT(*) as mwrowcount', $conds, $fname, $options );
1749                 if ($res) {
1750                         $row = $this->fetchRow($res);
1751                         $rows = (isset($row['mwrowcount'])) ? $row['mwrowcount'] : 0;
1752                 }
1753                 $this->freeResult($res);
1754                 return $rows;
1755         }
1756         
1757         /**
1758          * Description is left as an exercise for the reader
1759          * @param mixed $b data to be encoded
1760          * @return IBM_DB2Blob
1761          */
1762         public function encodeBlob($b) {
1763                 return new IBM_DB2Blob($b);
1764         }
1765         
1766         /**
1767          * Description is left as an exercise for the reader
1768          * @param IBM_DB2Blob $b data to be decoded
1769          * @return mixed
1770          */
1771         public function decodeBlob($b) {
1772                 return $b->getData();
1773         }
1774         
1775         /**
1776          * Convert into a list of string being concatenated
1777          * @param array $stringList strings that need to be joined together by the SQL engine
1778          * @return string joined by the concatenation operator
1779          */
1780         public function buildConcat( $stringList ) {
1781                 // || is equivalent to CONCAT
1782                 // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz'
1783                 return implode( ' || ', $stringList );
1784         }
1785         
1786         /**
1787          * Generates the SQL required to convert a DB2 timestamp into a Unix epoch
1788          * @param string $column name of timestamp column
1789          * @return string SQL code
1790          */
1791         public function extractUnixEpoch( $column ) {
1792                 // TODO
1793                 // see SpecialAncientpages
1794         }
1795 }
1796 ?>