MediaWiki  REL1_19
DatabaseIbm_db2.php
Go to the documentation of this file.
00001 <?php
00016 class IBM_DB2Field implements Field {
00017         private $name = '';
00018         private $tablename = '';
00019         private $type = '';
00020         private $nullable = false;
00021         private $max_length = 0;
00022 
00030         static function fromText( $db, $table, $field ) {
00031                 global $wgDBmwschema;
00032 
00033                 $q = <<<SQL
00034 SELECT
00035 lcase( coltype ) AS typname,
00036 nulls AS attnotnull, length AS attlen
00037 FROM sysibm.syscolumns
00038 WHERE tbcreator=%s AND tbname=%s AND name=%s;
00039 SQL;
00040                 $res = $db->query(
00041                         sprintf( $q,
00042                                 $db->addQuotes( $wgDBmwschema ),
00043                                 $db->addQuotes( $table ),
00044                                 $db->addQuotes( $field )
00045                         )
00046                 );
00047                 $row = $db->fetchObject( $res );
00048                 if ( !$row ) {
00049                         return null;
00050                 }
00051                 $n = new IBM_DB2Field;
00052                 $n->type = $row->typname;
00053                 $n->nullable = ( $row->attnotnull == 'N' );
00054                 $n->name = $field;
00055                 $n->tablename = $table;
00056                 $n->max_length = $row->attlen;
00057                 return $n;
00058         }
00063         function name() { return $this->name; }
00068         function tableName() { return $this->tablename; }
00073         function type() { return $this->type; }
00078         function isNullable() { return $this->nullable; }
00083         function maxLength() { return $this->max_length; }
00084 }
00085 
00090 class IBM_DB2Blob {
00091         private $mData;
00092 
00093         public function __construct( $data ) {
00094                 $this->mData = $data;
00095         }
00096 
00097         public function getData() {
00098                 return $this->mData;
00099         }
00100 
00101         public function __toString() {
00102                 return $this->mData;
00103         }
00104 }
00105 
00112 class IBM_DB2Result{
00113         private $db;
00114         private $result;
00115         private $num_rows;
00116         private $current_pos;
00117         private $columns = array();
00118         private $sql;
00119 
00120         private $resultSet = array();
00121         private $loadedLines = 0;
00122 
00131         public function __construct( $db, $result, $num_rows, $sql, $columns ){
00132                 $this->db = $db;
00133                 
00134                 if( $result instanceof ResultWrapper ){
00135                         $this->result = $result->result;
00136                 }
00137                 else{
00138                         $this->result = $result;
00139                 }
00140                 
00141                 $this->num_rows = $num_rows;
00142                 $this->current_pos = 0;
00143                 if ( $this->num_rows > 0 ) {
00144                         // Make a lower-case list of the column names
00145                         // By default, DB2 column names are capitalized
00146                         //  while MySQL column names are lowercase
00147                         
00148                         // Is there a reasonable maximum value for $i?
00149                         // Setting to 2048 to prevent an infinite loop
00150                         for( $i = 0; $i < 2048; $i++ ) {
00151                                 $name = db2_field_name( $this->result, $i );
00152                                 if ( $name != false ) {
00153                                         continue;
00154                                 }
00155                                 else {
00156                                         return false;
00157                                 }
00158                                 
00159                                 $this->columns[$i] = strtolower( $name );
00160                         }
00161                 }
00162                 
00163                 $this->sql = $sql;
00164         }
00165 
00170         public function getResult() {
00171                 if ( $this->result ) {
00172                         return $this->result;
00173                 }
00174                 else return false;
00175         }
00176 
00181         public function getNum_rows() {
00182                 return $this->num_rows;
00183         }
00184 
00189         public function fetchObject() {
00190                 if ( $this->result 
00191                                 && $this->num_rows > 0 
00192                                 && $this->current_pos >= 0 
00193                                 && $this->current_pos < $this->num_rows ) 
00194                 {
00195                         $row = $this->fetchRow();
00196                         $ret = new stdClass();
00197                         
00198                         foreach ( $row as $k => $v ) {
00199                                 $lc = $this->columns[$k];
00200                                 $ret->$lc = $v;
00201                         }
00202                         return $ret;
00203                 }
00204                 return false;
00205         }
00206 
00212         public function fetchRow(){
00213                 if ( $this->result 
00214                                 && $this->num_rows > 0 
00215                                 && $this->current_pos >= 0 
00216                                 && $this->current_pos < $this->num_rows )
00217                 {
00218                         if ( $this->loadedLines <= $this->current_pos ) {
00219                                 $row = db2_fetch_array( $this->result );
00220                                 $this->resultSet[$this->loadedLines++] = $row;
00221                                 if ( $this->db->lastErrno() ) {
00222                                         throw new DBUnexpectedError( $this->db, 'Error in fetchRow(): '
00223                                                 . htmlspecialchars( $this->db->lastError() ) );
00224                                 }
00225                         }
00226 
00227                         if ( $this->loadedLines > $this->current_pos ){
00228                                 return $this->resultSet[$this->current_pos++];
00229                         }
00230                         
00231                 }
00232                 return false;
00233         }
00234 
00239         public function freeResult(){
00240                 unset( $this->resultSet );
00241                 if ( !@db2_free_result( $this->result ) ) {
00242                         throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
00243                 }
00244         }
00245 }
00246 
00251 class DatabaseIbm_db2 extends DatabaseBase {
00252         /*
00253          * Inherited members
00254         protected $mLastQuery = '';
00255         protected $mPHPError = false;
00256 
00257         protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
00258         protected $mOpened = false;
00259 
00260         protected $mTablePrefix;
00261         protected $mFlags;
00262         protected $mTrxLevel = 0;
00263         protected $mErrorCount = 0;
00264         protected $mLBInfo = array();
00265         protected $mFakeSlaveLag = null, $mFakeMaster = false;
00266          *
00267          */
00268 
00270         protected $mPort = null;
00272         protected $mSchema = null;
00274         protected $mSchemaSet = false;
00276         protected $mLastResult = null;
00278         protected $mAffectedRows = null;
00280         protected $mNumRows = null;
00282         protected $currentRow = 0;
00283 
00285         public $mConnOptions = array();
00287         public $mStmtOptions = array();
00288 
00290         const USE_GLOBAL = 'get from global';
00291 
00293         const NONE_OPTION = 0x00;
00295         const CONN_OPTION = 0x01;
00297         const STMT_OPTION = 0x02;
00298 
00300         const REGULAR_MODE = 'regular';
00302         const INSTALL_MODE = 'install';
00303 
00305         protected $mMode = self::REGULAR_MODE;
00306 
00308         protected $mInsertId = null;
00309 
00310         ######################################
00311         # Getters and Setters
00312         ######################################
00313 
00317         function cascadingDeletes() {
00318                 return true;
00319         }
00320 
00325         function cleanupTriggers() {
00326                 return true;
00327         }
00328 
00334         function strictIPs() {
00335                 return true;
00336         }
00337 
00341         function realTimestamps() {
00342                 return true;
00343         }
00344 
00348         function implicitGroupby() {
00349                 return false;
00350         }
00351 
00357         function implicitOrderby() {
00358                 return false;
00359         }
00360 
00365         function searchableIPs() {
00366                 return true;
00367         }
00368 
00372         function functionalIndexes() {
00373                 return true;
00374         }
00375 
00379         public function getWikiID() {
00380                 if( $this->mSchema ) {
00381                         return "{$this->mDBname}-{$this->mSchema}";
00382                 } else {
00383                         return $this->mDBname;
00384                 }
00385         }
00386 
00391         public function getType() {
00392                 return 'ibm_db2';
00393         }
00394 
00399         public function getDb(){
00400                 return $this->mConn;
00401         }
00402 
00412         public function __construct( $server = false, $user = false,
00413                                                         $password = false,
00414                                                         $dbName = false, $flags = 0,
00415                                                         $schema = self::USE_GLOBAL )
00416         {
00417                 global $wgDBmwschema;
00418 
00419                 if ( $schema == self::USE_GLOBAL ) {
00420                         $this->mSchema = $wgDBmwschema;
00421                 } else {
00422                         $this->mSchema = $schema;
00423                 }
00424 
00425                 // configure the connection and statement objects
00426                 $this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER',
00427                         self::CONN_OPTION | self::STMT_OPTION );
00428                 $this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON',
00429                         self::STMT_OPTION );
00430                 $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON',
00431                         self::STMT_OPTION );
00432                 parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags );
00433         }
00434 
00441         private function setDB2Option( $name, $const, $type ) {
00442                 if ( defined( $const ) ) {
00443                         if ( $type & self::CONN_OPTION ) {
00444                                 $this->mConnOptions[$name] = constant( $const );
00445                         }
00446                         if ( $type & self::STMT_OPTION ) {
00447                                 $this->mStmtOptions[$name] = constant( $const );
00448                         }
00449                 } else {
00450                         $this->installPrint(
00451                                 "$const is not defined. ibm_db2 version is likely too low." );
00452                 }
00453         }
00454 
00459         private function installPrint( $string ) {
00460                 wfDebug( "$string\n" );
00461                 if ( $this->mMode == self::INSTALL_MODE ) {
00462                         print "<li><pre>$string</pre></li>";
00463                         flush();
00464                 }
00465         }
00466 
00477         public function open( $server, $user, $password, $dbName ) {
00478                 wfProfileIn( __METHOD__ );
00479 
00480                 # Load IBM DB2 driver if missing
00481                 wfDl( 'ibm_db2' );
00482 
00483                 # Test for IBM DB2 support, to avoid suppressed fatal error
00484                 if ( !function_exists( 'db2_connect' ) ) {
00485                         throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" );
00486                 }
00487 
00488                 global $wgDBport;
00489 
00490                 // Close existing connection
00491                 $this->close();
00492                 // Cache conn info
00493                 $this->mServer = $server;
00494                 $this->mPort = $port = $wgDBport;
00495                 $this->mUser = $user;
00496                 $this->mPassword = $password;
00497                 $this->mDBname = $dbName;
00498 
00499                 $this->openUncataloged( $dbName, $user, $password, $server, $port );
00500 
00501                 if ( !$this->mConn ) {
00502                         $this->installPrint( "DB connection error\n" );
00503                         $this->installPrint(
00504                                 "Server: $server, Database: $dbName, User: $user, Password: "
00505                                 . substr( $password, 0, 3 ) . "...\n" );
00506                         $this->installPrint( $this->lastError() . "\n" );
00507                         wfProfileOut( __METHOD__ );
00508                         wfDebug( "DB connection error\n" );
00509                         wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
00510                         wfDebug( $this->lastError() . "\n" );
00511                         throw new DBConnectionError( $this, $this->lastError() );
00512                 }
00513 
00514                 // Some MediaWiki code is still transaction-less (?).
00515                 // The strategy is to keep AutoCommit on for that code
00516                 //  but switch it off whenever a transaction is begun.
00517                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
00518 
00519                 $this->mOpened = true;
00520                 $this->applySchema();
00521 
00522                 wfProfileOut( __METHOD__ );
00523                 return $this->mConn;
00524         }
00525 
00529         protected function openCataloged( $dbName, $user, $password ) {
00530                 wfSuppressWarnings();
00531                 $this->mConn = db2_pconnect( $dbName, $user, $password );
00532                 wfRestoreWarnings();
00533         }
00534 
00538         protected function openUncataloged( $dbName, $user, $password, $server, $port )
00539         {
00540                 $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";
00541                 wfSuppressWarnings();
00542                 $this->mConn = db2_pconnect( $dsn, "", "", array() );
00543                 wfRestoreWarnings();
00544         }
00545 
00550         public function close() {
00551                 $this->mOpened = false;
00552                 if ( $this->mConn ) {
00553                         if ( $this->trxLevel() > 0 ) {
00554                                 $this->commit();
00555                         }
00556                         return db2_close( $this->mConn );
00557                 } else {
00558                         return true;
00559                 }
00560         }
00561 
00566         public function lastError() {
00567                 $connerr = db2_conn_errormsg();
00568                 if ( $connerr ) {
00569                         //$this->rollback();
00570                         return $connerr;
00571                 }
00572                 $stmterr = db2_stmt_errormsg();
00573                 if ( $stmterr ) {
00574                         //$this->rollback();
00575                         return $stmterr;
00576                 }
00577 
00578                 return false;
00579         }
00580 
00586         public function lastErrno() {
00587                 $connerr = db2_conn_error();
00588                 if ( $connerr ) {
00589                         return $connerr;
00590                 }
00591                 $stmterr = db2_stmt_error();
00592                 if ( $stmterr ) {
00593                         return $stmterr;
00594                 }
00595                 return 0;
00596         }
00597 
00602         public function isOpen() { return $this->mOpened; }
00603 
00609         protected function doQuery( $sql ) {
00610                 $this->applySchema();
00611 
00612                 // Needed to handle any UTF-8 encoding issues in the raw sql
00613                 // Note that we fully support prepared statements for DB2
00614                 // prepare() and execute() should be used instead of doQuery() whenever possible
00615                 $sql = utf8_decode( $sql );
00616 
00617                 $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
00618                 if( $ret == false ) {
00619                         $error = db2_stmt_errormsg();
00620 
00621                         $this->installPrint( "<pre>$sql</pre>" );
00622                         $this->installPrint( $error );
00623                         throw new DBUnexpectedError( $this, 'SQL error: '
00624                                 . htmlspecialchars( $error ) );
00625                 }
00626                 $this->mLastResult = $ret;
00627                 $this->mAffectedRows = null; // Not calculated until asked for
00628                 return $ret;
00629         }
00630 
00634         public function getServerVersion() {
00635                 $info = db2_server_info( $this->mConn );
00636                 return $info->DBMS_VER;
00637         }
00638 
00643         public function tableExists( $table, $fname = __METHOD__ ) {
00644                 $schema = $this->mSchema;
00645 
00646                 $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" .
00647                         strtoupper( $table ) .
00648                         "' AND ST.CREATOR = '" .
00649                         strtoupper( $schema ) . "'";
00650                 $res = $this->query( $sql );
00651                 if ( !$res ) {
00652                         return false;
00653                 }
00654 
00655                 // If the table exists, there should be one of it
00656                 $row = $this->fetchRow( $res );
00657                 $count = $row[0];
00658                 if ( $count == '1' || $count == 1 ) {
00659                         return true;
00660                 }
00661 
00662                 return false;
00663         }
00664 
00674         public function fetchObject( $res ) {
00675                 if ( $res instanceof ResultWrapper ) {
00676                         $res = $res->result;
00677                 }
00678                 wfSuppressWarnings();
00679                 $row = db2_fetch_object( $res );
00680                 wfRestoreWarnings();
00681                 if( $this->lastErrno() ) {
00682                         throw new DBUnexpectedError( $this, 'Error in fetchObject(): '
00683                                 . htmlspecialchars( $this->lastError() ) );
00684                 }
00685                 return $row;
00686         }
00687 
00696         public function fetchRow( $res ) {
00697                 if ( $res instanceof ResultWrapper ) {
00698                         $res = $res->result;
00699                 }
00700                 if ( db2_num_rows( $res ) > 0) {
00701                         wfSuppressWarnings();
00702                         $row = db2_fetch_array( $res );
00703                         wfRestoreWarnings();
00704                         if ( $this->lastErrno() ) {
00705                                 throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
00706                                         . htmlspecialchars( $this->lastError() ) );
00707                         }
00708                         return $row;
00709                 }
00710                 return false;
00711         }
00712 
00720         public function addQuotes( $s ) {
00721                 //$this->installPrint( "DB2::addQuotes( $s )\n" );
00722                 if ( is_null( $s ) ) {
00723                         return 'NULL';
00724                 } elseif ( $s instanceof Blob ) {
00725                         return "'" . $s->fetch( $s ) . "'";
00726                 } elseif ( $s instanceof IBM_DB2Blob ) {
00727                         return "'" . $this->decodeBlob( $s ) . "'";
00728                 }
00729                 $s = $this->strencode( $s );
00730                 if ( is_numeric( $s ) ) {
00731                         return $s;
00732                 } else {
00733                         return "'$s'";
00734                 }
00735         }
00736 
00743         public function is_numeric_type( $type ) {
00744                 switch ( strtoupper( $type ) ) {
00745                         case 'SMALLINT':
00746                         case 'INTEGER':
00747                         case 'INT':
00748                         case 'BIGINT':
00749                         case 'DECIMAL':
00750                         case 'REAL':
00751                         case 'DOUBLE':
00752                         case 'DECFLOAT':
00753                                 return true;
00754                 }
00755                 return false;
00756         }
00757 
00763         public function strencode( $s ) {
00764                 // Bloody useless function
00765                 //  Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
00766                 //  But also necessary
00767                 $s = db2_escape_string( $s );
00768                 // Wide characters are evil -- some of them look like '
00769                 $s = utf8_encode( $s );
00770                 // Fix its stupidity
00771                 $from = array(  "\\\\", "\\'",  '\\n',  '\\t',  '\\"',  '\\r' );
00772                 $to = array(            "\\",           "''",           "\n",           "\t",           '"',            "\r" );
00773                 $s = str_replace( $from, $to, $s ); // DB2 expects '', not \' escaping
00774                 return $s;
00775         }
00776 
00780         protected function applySchema() {
00781                 if ( !( $this->mSchemaSet ) ) {
00782                         $this->mSchemaSet = true;
00783                         $this->begin();
00784                         $this->doQuery( "SET SCHEMA = $this->mSchema" );
00785                         $this->commit();
00786                 }
00787         }
00788 
00792         public function begin( $fname = 'DatabaseIbm_db2::begin' ) {
00793                 // BEGIN is implicit for DB2
00794                 // However, it requires that AutoCommit be off.
00795 
00796                 // Some MediaWiki code is still transaction-less (?).
00797                 // The strategy is to keep AutoCommit on for that code
00798                 //  but switch it off whenever a transaction is begun.
00799                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_OFF );
00800 
00801                 $this->mTrxLevel = 1;
00802         }
00803 
00808         public function commit( $fname = 'DatabaseIbm_db2::commit' ) {
00809                 db2_commit( $this->mConn );
00810 
00811                 // Some MediaWiki code is still transaction-less (?).
00812                 // The strategy is to keep AutoCommit on for that code
00813                 //  but switch it off whenever a transaction is begun.
00814                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
00815 
00816                 $this->mTrxLevel = 0;
00817         }
00818 
00822         public function rollback( $fname = 'DatabaseIbm_db2::rollback' ) {
00823                 db2_rollback( $this->mConn );
00824                 // turn auto-commit back on
00825                 // not sure if this is appropriate
00826                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
00827                 $this->mTrxLevel = 0;
00828         }
00829 
00840         function makeList( $a, $mode = LIST_COMMA ) {
00841                 if ( !is_array( $a ) ) {
00842                         throw new DBUnexpectedError( $this,
00843                                 'DatabaseIbm_db2::makeList called with incorrect parameters' );
00844                 }
00845 
00846                 // if this is for a prepared UPDATE statement
00847                 // (this should be promoted to the parent class
00848                 //  once other databases use prepared statements)
00849                 if ( $mode == LIST_SET_PREPARED ) {
00850                         $first = true;
00851                         $list = '';
00852                         foreach ( $a as $field => $value ) {
00853                                 if ( !$first ) {
00854                                         $list .= ", $field = ?";
00855                                 } else {
00856                                         $list .= "$field = ?";
00857                                         $first = false;
00858                                 }
00859                         }
00860                         $list .= '';
00861 
00862                         return $list;
00863                 }
00864 
00865                 // otherwise, call the usual function
00866                 return parent::makeList( $a, $mode );
00867         }
00868 
00877         public function limitResult( $sql, $limit, $offset=false ) {
00878                 if( !is_numeric( $limit ) ) {
00879                         throw new DBUnexpectedError( $this,
00880                                 "Invalid non-numeric limit passed to limitResult()\n" );
00881                 }
00882                 if( $offset ) {
00883                         if ( stripos( $sql, 'where' ) === false ) {
00884                                 return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )";
00885                         } else {
00886                                 return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )";
00887                         }
00888                 }
00889                 return "$sql FETCH FIRST $limit ROWS ONLY ";
00890         }
00891 
00899         public function tableName( $name, $format = 'quoted' ) {
00900                 // we want maximum compatibility with MySQL schema
00901                 return $name;
00902         }
00903 
00910         public function timestamp( $ts = 0 ) {
00911                 // TS_MW cannot be easily distinguished from an integer
00912                 return wfTimestamp( TS_DB2, $ts );
00913         }
00914 
00920         public function nextSequenceValue( $seqName ) {
00921                 // Not using sequences in the primary schema to allow for easier migration
00922                 //  from MySQL
00923                 // Emulating MySQL behaviour of using NULL to signal that sequences
00924                 // aren't used
00925                 /*
00926                 $safeseq = preg_replace( "/'/", "''", $seqName );
00927                 $res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
00928                 $row = $this->fetchRow( $res );
00929                 $this->mInsertId = $row[0];
00930                 return $this->mInsertId;
00931                 */
00932                 return null;
00933         }
00934 
00939         public function insertId() {
00940                 return $this->mInsertId;
00941         }
00942 
00952         private function calcInsertId( $table, $primaryKey, $stmt ) {
00953                 if ( $primaryKey ) {
00954                         $this->mInsertId = db2_last_insert_id( $this->mConn );
00955                 }
00956         }
00957 
00971         public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert',
00972                 $options = array() )
00973         {
00974                 if ( !count( $args ) ) {
00975                         return true;
00976                 }
00977                 // get database-specific table name (not used)
00978                 $table = $this->tableName( $table );
00979                 // format options as an array
00980                 $options = IBM_DB2Helper::makeArray( $options );
00981                 // format args as an array of arrays
00982                 if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) {
00983                         $args = array( $args );
00984                 }
00985 
00986                 // prevent insertion of NULL into primary key columns
00987                 list( $args, $primaryKeys ) = $this->removeNullPrimaryKeys( $table, $args );
00988                 // if there's only one primary key
00989                 // we'll be able to read its value after insertion
00990                 $primaryKey = false;
00991                 if ( count( $primaryKeys ) == 1 ) {
00992                         $primaryKey = $primaryKeys[0];
00993                 }
00994 
00995                 // get column names
00996                 $keys = array_keys( $args[0] );
00997                 $key_count = count( $keys );
00998 
00999                 // If IGNORE is set, we use savepoints to emulate mysql's behavior
01000                 $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
01001 
01002                 // assume success
01003                 $res = true;
01004                 // If we are not in a transaction, we need to be for savepoint trickery
01005                 if ( !$this->mTrxLevel ) {
01006                         $this->begin();
01007                 }
01008 
01009                 $sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES ';
01010                 if ( $key_count == 1 ) {
01011                         $sql .= '( ? )';
01012                 } else {
01013                         $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )';
01014                 }
01015                 $this->installPrint( "Preparing the following SQL:" );
01016                 $this->installPrint( "$sql" );
01017                 $this->installPrint( print_r( $args, true ));
01018                 $stmt = $this->prepare( $sql );
01019 
01020                 // start a transaction/enter transaction mode
01021                 $this->begin();
01022 
01023                 if ( !$ignore ) {
01024                         //$first = true;
01025                         foreach ( $args as $row ) {
01026                                 //$this->installPrint( "Inserting " . print_r( $row, true ));
01027                                 // insert each row into the database
01028                                 $res = $res & $this->execute( $stmt, $row );
01029                                 if ( !$res ) {
01030                                         $this->installPrint( 'Last error:' );
01031                                         $this->installPrint( $this->lastError() );
01032                                 }
01033                                 // get the last inserted value into a generated column
01034                                 $this->calcInsertId( $table, $primaryKey, $stmt );
01035                         }
01036                 } else {
01037                         $olde = error_reporting( 0 );
01038                         // For future use, we may want to track the number of actual inserts
01039                         // Right now, insert (all writes) simply return true/false
01040                         $numrowsinserted = 0;
01041 
01042                         // always return true
01043                         $res = true;
01044 
01045                         foreach ( $args as $row ) {
01046                                 $overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
01047                                 db2_exec( $this->mConn, $overhead, $this->mStmtOptions );
01048 
01049                                 $res2 = $this->execute( $stmt, $row );
01050 
01051                                 if ( !$res2 ) {
01052                                         $this->installPrint( 'Last error:' );
01053                                         $this->installPrint( $this->lastError() );
01054                                 }
01055                                 // get the last inserted value into a generated column
01056                                 $this->calcInsertId( $table, $primaryKey, $stmt );
01057 
01058                                 $errNum = $this->lastErrno();
01059                                 if ( $errNum ) {
01060                                         db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore",
01061                                                 $this->mStmtOptions );
01062                                 } else {
01063                                         db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore",
01064                                                 $this->mStmtOptions );
01065                                         $numrowsinserted++;
01066                                 }
01067                         }
01068 
01069                         $olde = error_reporting( $olde );
01070                         // Set the affected row count for the whole operation
01071                         $this->mAffectedRows = $numrowsinserted;
01072                 }
01073                 // commit either way
01074                 $this->commit();
01075                 $this->freePrepared( $stmt );
01076 
01077                 return $res;
01078         }
01079 
01088         private function removeNullPrimaryKeys( $table, $args ) {
01089                 $schema = $this->mSchema;
01090 
01091                 // find out the primary keys
01092                 $keyres = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '"
01093                   . strtoupper( $table )
01094                   . "' AND TBCREATOR = '"
01095                   . strtoupper( $schema )
01096                   . "' AND KEYSEQ > 0" );
01097 
01098                 $keys = array();
01099                 for (
01100                         $row = $this->fetchRow( $keyres );
01101                         $row != null;
01102                         $row = $this->fetchRow( $keyres )
01103                 )
01104                 {
01105                         $keys[] = strtolower( $row[0] );
01106                 }
01107                 // remove primary keys
01108                 foreach ( $args as $ai => $row ) {
01109                         foreach ( $keys as $key ) {
01110                                 if ( $row[$key] == null ) {
01111                                         unset( $row[$key] );
01112                                 }
01113                         }
01114                         $args[$ai] = $row;
01115                 }
01116                 // return modified hash
01117                 return array( $args, $keys );
01118         }
01119 
01132         public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update',
01133                 $options = array() )
01134         {
01135                 $table = $this->tableName( $table );
01136                 $opts = $this->makeUpdateOptions( $options );
01137                 $sql = "UPDATE $opts $table SET "
01138                         . $this->makeList( $values, LIST_SET_PREPARED );
01139                 if ( $conds != '*' ) {
01140                         $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01141                 }
01142                 $stmt = $this->prepare( $sql );
01143                 $this->installPrint( 'UPDATE: ' . print_r( $values, true ) );
01144                 // assuming for now that an array with string keys will work
01145                 // if not, convert to simple array first
01146                 $result = $this->execute( $stmt, $values );
01147                 $this->freePrepared( $stmt );
01148 
01149                 return $result;
01150         }
01151 
01157         public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
01158                 if ( !$conds ) {
01159                         throw new DBUnexpectedError( $this,
01160                                 'DatabaseIbm_db2::delete() called with no conditions' );
01161                 }
01162                 $table = $this->tableName( $table );
01163                 $sql = "DELETE FROM $table";
01164                 if ( $conds != '*' ) {
01165                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
01166                 }
01167                 $result = $this->query( $sql, $fname );
01168 
01169                 return $result;
01170         }
01171 
01176         public function affectedRows() {
01177                 if ( !is_null( $this->mAffectedRows ) ) {
01178                         // Forced result for simulated queries
01179                         return $this->mAffectedRows;
01180                 }
01181                 if( empty( $this->mLastResult ) ) {
01182                         return 0;
01183                 }
01184                 return db2_num_rows( $this->mLastResult );
01185         }
01186 
01193         public function numRows( $res ) {
01194                 if ( $res instanceof ResultWrapper ) {
01195                         $res = $res->result;
01196                 }
01197 
01198                 if ( $this->mNumRows ) {
01199                         return $this->mNumRows;
01200                 } else {
01201                         return 0;
01202                 }
01203         }
01204 
01211         public function dataSeek( $res, $row ) {
01212                 if ( $res instanceof ResultWrapper ) {
01213                         return $res = $res->result;
01214                 }
01215                 if ( $res instanceof IBM_DB2Result ) {
01216                         return $res->dataSeek( $row );
01217                 }
01218                 wfDebug( "dataSeek operation in DB2 database\n" );
01219                 return false;
01220         }
01221 
01222         ###
01223         # Fix notices in Block.php
01224         ###
01225 
01231         public function freeResult( $res ) {
01232                 if ( $res instanceof ResultWrapper ) {
01233                         $res = $res->result;
01234                 }
01235                 wfSuppressWarnings();
01236                 $ok = db2_free_result( $res );
01237                 wfRestoreWarnings();
01238                 if ( !$ok ) {
01239                         throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
01240                 }
01241         }
01242 
01248         public function numFields( $res ) {
01249                 if ( $res instanceof ResultWrapper ) {
01250                         $res = $res->result;
01251                 }
01252                 if ( $res instanceof IBM_DB2Result ) {
01253                         $res = $res->getResult();
01254                 }
01255                 return db2_num_fields( $res );
01256         }
01257 
01264         public function fieldName( $res, $n ) {
01265                 if ( $res instanceof ResultWrapper ) {
01266                         $res = $res->result;
01267                 }
01268                 if ( $res instanceof IBM_DB2Result ) {
01269                         $res = $res->getResult();
01270                 }
01271                 return db2_field_name( $res, $n );
01272         }
01273 
01292         public function select( $table, $vars, $conds = '', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
01293         {
01294                 $res = parent::select( $table, $vars, $conds, $fname, $options,
01295                         $join_conds );
01296                 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01297 
01298                 // We must adjust for offset
01299                 if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
01300                         $limit = $options['LIMIT'];
01301                         $offset = $options['OFFSET'];
01302                 }
01303 
01304                 // DB2 does not have a proper num_rows() function yet, so we must emulate
01305                 // DB2 9.5.4 and the corresponding ibm_db2 driver will introduce
01306                 //  a working one
01307                 // TODO: Yay!
01308 
01309                 // we want the count
01310                 $vars2 = array( 'count( * ) as num_rows' );
01311                 // respecting just the limit option
01312                 $options2 = array();
01313                 if ( isset( $options['LIMIT'] ) ) {
01314                         $options2['LIMIT'] = $options['LIMIT'];
01315                 }
01316                 // but don't try to emulate for GROUP BY
01317                 if ( isset( $options['GROUP BY'] ) ) {
01318                         return $res;
01319                 }
01320 
01321                 $res2 = parent::select( $table, $vars2, $conds, $fname, $options2,
01322                         $join_conds );
01323                 
01324                 $obj = $this->fetchObject( $res2 );
01325                 $this->mNumRows = $obj->num_rows;
01326                 
01327                 return new ResultWrapper( $this, new IBM_DB2Result( $this, $res, $obj->num_rows, $vars, $sql ) );
01328         }
01329 
01340         function makeSelectOptions( $options ) {
01341                 $preLimitTail = $postLimitTail = '';
01342                 $startOpts = '';
01343 
01344                 $noKeyOptions = array();
01345                 foreach ( $options as $key => $option ) {
01346                         if ( is_numeric( $key ) ) {
01347                                 $noKeyOptions[$option] = true;
01348                         }
01349                 }
01350 
01351                 if ( isset( $options['GROUP BY'] ) ) {
01352                         $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
01353                 }
01354                 if ( isset( $options['HAVING'] ) ) {
01355                         $preLimitTail .= " HAVING {$options['HAVING']}";
01356                 }
01357                 if ( isset( $options['ORDER BY'] ) ) {
01358                         $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
01359                 }
01360 
01361                 if ( isset( $noKeyOptions['DISTINCT'] )
01362                         || isset( $noKeyOptions['DISTINCTROW'] ) )
01363                 {
01364                         $startOpts .= 'DISTINCT';
01365                 }
01366 
01367                 return array( $startOpts, '', $preLimitTail, $postLimitTail );
01368         }
01369 
01374         public static function getSoftwareLink() {
01375                 return '[http://www.ibm.com/db2/express/ IBM DB2]';
01376         }
01377 
01384         public function getSearchEngine() {
01385                 return 'SearchIBM_DB2';
01386         }
01387 
01392         public function wasDeadlock() {
01393                 // get SQLSTATE
01394                 $err = $this->lastErrno();
01395                 switch( $err ) {
01396                         // This is literal port of the MySQL logic and may be wrong for DB2
01397                         case '40001':   // sql0911n, Deadlock or timeout, rollback
01398                         case '57011':   // sql0904n, Resource unavailable, no rollback
01399                         case '57033':   // sql0913n, Deadlock or timeout, no rollback
01400                         $this->installPrint( "In a deadlock because of SQLSTATE $err" );
01401                         return true;
01402                 }
01403                 return false;
01404         }
01405 
01411         public function ping() {
01412                 // db2_ping() doesn't exist
01413                 // Emulate
01414                 $this->close();
01415                 $this->mConn = $this->openUncataloged( $this->mDBName, $this->mUser,
01416                         $this->mPassword, $this->mServer, $this->mPort );
01417 
01418                 return false;
01419         }
01420         ######################################
01421         # Unimplemented and not applicable
01422         ######################################
01423 
01427         public function limitResultForUpdate( $sql, $num ) {
01428                 $this->installPrint( 'Not implemented for DB2: limitResultForUpdate()' );
01429                 return $sql;
01430         }
01431 
01436         public function fillPreparedArg( $matches ) {
01437                 $this->installPrint( 'Not useful for DB2: fillPreparedArg()' );
01438                 return '';
01439         }
01440 
01441         ######################################
01442         # Reflection
01443         ######################################
01444 
01453         public function indexInfo( $table, $index,
01454                 $fname = 'DatabaseIbm_db2::indexExists' )
01455         {
01456                 $table = $this->tableName( $table );
01457                 $sql = <<<SQL
01458 SELECT name as indexname
01459 FROM sysibm.sysindexes si
01460 WHERE si.name='$index' AND si.tbname='$table'
01461 AND sc.tbcreator='$this->mSchema'
01462 SQL;
01463                 $res = $this->query( $sql, $fname );
01464                 if ( !$res ) {
01465                         return null;
01466                 }
01467                 $row = $this->fetchObject( $res );
01468                 if ( $row != null ) {
01469                         return $row;
01470                 } else {
01471                         return false;
01472                 }
01473         }
01474 
01481         public function fieldInfo( $table, $field ) {
01482                 return IBM_DB2Field::fromText( $this, $table, $field );
01483         }
01484 
01491         public function fieldType( $res, $index ) {
01492                 if ( $res instanceof ResultWrapper ) {
01493                         $res = $res->result;
01494                 }
01495                 if ( $res instanceof IBM_DB2Result ) {
01496                         $res = $res->getResult();
01497                 }
01498                 return db2_field_type( $res, $index );
01499         }
01500 
01508         public function indexUnique ( $table, $index,
01509                 $fname = 'DatabaseIbm_db2::indexUnique' )
01510         {
01511                 $table = $this->tableName( $table );
01512                 $sql = <<<SQL
01513 SELECT si.name as indexname
01514 FROM sysibm.sysindexes si
01515 WHERE si.name='$index' AND si.tbname='$table'
01516 AND sc.tbcreator='$this->mSchema'
01517 AND si.uniquerule IN ( 'U', 'P' )
01518 SQL;
01519                 $res = $this->query( $sql, $fname );
01520                 if ( !$res ) {
01521                         return null;
01522                 }
01523                 if ( $this->fetchObject( $res ) ) {
01524                         return true;
01525                 }
01526                 return false;
01527 
01528         }
01529 
01536         public function textFieldSize( $table, $field ) {
01537                 $table = $this->tableName( $table );
01538                 $sql = <<<SQL
01539 SELECT length as size
01540 FROM sysibm.syscolumns sc
01541 WHERE sc.name='$field' AND sc.tbname='$table'
01542 AND sc.tbcreator='$this->mSchema'
01543 SQL;
01544                 $res = $this->query( $sql );
01545                 $row = $this->fetchObject( $res );
01546                 $size = $row->size;
01547                 return $size;
01548         }
01549 
01555         public function encodeBlob( $b ) {
01556                 return new IBM_DB2Blob( $b );
01557         }
01558 
01564         public function decodeBlob( $b ) {
01565                 return "$b";
01566         }
01567 
01574         public function buildConcat( $stringList ) {
01575                 // || is equivalent to CONCAT
01576                 // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz'
01577                 return implode( ' || ', $stringList );
01578         }
01579 
01585         public function extractUnixEpoch( $column ) {
01586                 // TODO
01587                 // see SpecialAncientpages
01588         }
01589 
01590         ######################################
01591         # Prepared statements
01592         ######################################
01593 
01606         public function prepare( $sql, $func = 'DB2::prepare' ) {
01607                 $stmt = db2_prepare( $this->mConn, $sql, $this->mStmtOptions );
01608                 return $stmt;
01609         }
01610 
01615         public function freePrepared( $prepared ) {
01616                 return db2_free_stmt( $prepared );
01617         }
01618 
01625         public function execute( $prepared, $args = null ) {
01626                 if( !is_array( $args ) ) {
01627                         # Pull the var args
01628                         $args = func_get_args();
01629                         array_shift( $args );
01630                 }
01631                 $res = db2_execute( $prepared, $args );
01632                 if ( !$res ) {
01633                         $this->installPrint( db2_stmt_errormsg() );
01634                 }
01635                 return $res;
01636         }
01637 
01644         public function safeQuery( $query, $args = null ) {
01645                 // copied verbatim from Database.php
01646                 $prepared = $this->prepare( $query, 'DB2::safeQuery' );
01647                 if( !is_array( $args ) ) {
01648                         # Pull the var args
01649                         $args = func_get_args();
01650                         array_shift( $args );
01651                 }
01652                 $retval = $this->execute( $prepared, $args );
01653                 $this->freePrepared( $prepared );
01654                 return $retval;
01655         }
01656 
01664         public function fillPrepared( $preparedQuery, $args ) {
01665                 reset( $args );
01666                 $this->preparedArgs =& $args;
01667 
01668                 foreach ( $args as $i => $arg ) {
01669                         db2_bind_param( $preparedQuery, $i+1, $args[$i] );
01670                 }
01671 
01672                 return $preparedQuery;
01673         }
01674 
01678         public function setMode( $mode ) {
01679                 $old = $this->mMode;
01680                 $this->mMode = $mode;
01681                 return $old;
01682         }
01683 
01690         function bitNot( $field ) {
01691                 // expecting bit-fields smaller than 4bytes
01692                 return "BITNOT( $field )";
01693         }
01694 
01702         function bitAnd( $fieldLeft, $fieldRight ) {
01703                 return "BITAND( $fieldLeft, $fieldRight )";
01704         }
01705 
01713         function bitOr( $fieldLeft, $fieldRight ) {
01714                 return "BITOR( $fieldLeft, $fieldRight )";
01715         }
01716 }
01717 
01718 class IBM_DB2Helper {
01719         public static function makeArray( $maybeArray ) {
01720                 if ( !is_array( $maybeArray ) ) {
01721                         return array( $maybeArray );
01722                 }
01723 
01724                 return $maybeArray;
01725         }
01726 }