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