MediaWiki  REL1_20
DatabaseIbm_db2.php
Go to the documentation of this file.
00001 <?php
00031 class IBM_DB2Field implements Field {
00032         private $name = '';
00033         private $tablename = '';
00034         private $type = '';
00035         private $nullable = false;
00036         private $max_length = 0;
00037 
00045         static function fromText( $db, $table, $field ) {
00046                 global $wgDBmwschema;
00047 
00048                 $q = <<<SQL
00049 SELECT
00050 lcase( coltype ) AS typname,
00051 nulls AS attnotnull, length AS attlen
00052 FROM sysibm.syscolumns
00053 WHERE tbcreator=%s AND tbname=%s AND name=%s;
00054 SQL;
00055                 $res = $db->query(
00056                         sprintf( $q,
00057                                 $db->addQuotes( $wgDBmwschema ),
00058                                 $db->addQuotes( $table ),
00059                                 $db->addQuotes( $field )
00060                         )
00061                 );
00062                 $row = $db->fetchObject( $res );
00063                 if ( !$row ) {
00064                         return null;
00065                 }
00066                 $n = new IBM_DB2Field;
00067                 $n->type = $row->typname;
00068                 $n->nullable = ( $row->attnotnull == 'N' );
00069                 $n->name = $field;
00070                 $n->tablename = $table;
00071                 $n->max_length = $row->attlen;
00072                 return $n;
00073         }
00078         function name() { return $this->name; }
00083         function tableName() { return $this->tablename; }
00088         function type() { return $this->type; }
00093         function isNullable() { return $this->nullable; }
00098         function maxLength() { return $this->max_length; }
00099 }
00100 
00105 class IBM_DB2Blob {
00106         private $mData;
00107 
00108         public function __construct( $data ) {
00109                 $this->mData = $data;
00110         }
00111 
00112         public function getData() {
00113                 return $this->mData;
00114         }
00115 
00116         public function __toString() {
00117                 return $this->mData;
00118         }
00119 }
00120 
00127 class IBM_DB2Result{
00128         private $db;
00129         private $result;
00130         private $num_rows;
00131         private $current_pos;
00132         private $columns = array();
00133         private $sql;
00134 
00135         private $resultSet = array();
00136         private $loadedLines = 0;
00137 
00146         public function __construct( $db, $result, $num_rows, $sql, $columns ){
00147                 $this->db = $db;
00148 
00149                 if( $result instanceof ResultWrapper ){
00150                         $this->result = $result->result;
00151                 }
00152                 else{
00153                         $this->result = $result;
00154                 }
00155 
00156                 $this->num_rows = $num_rows;
00157                 $this->current_pos = 0;
00158                 if ( $this->num_rows > 0 ) {
00159                         // Make a lower-case list of the column names
00160                         // By default, DB2 column names are capitalized
00161                         //  while MySQL column names are lowercase
00162 
00163                         // Is there a reasonable maximum value for $i?
00164                         // Setting to 2048 to prevent an infinite loop
00165                         for( $i = 0; $i < 2048; $i++ ) {
00166                                 $name = db2_field_name( $this->result, $i );
00167                                 if ( $name != false ) {
00168                                         continue;
00169                                 }
00170                                 else {
00171                                         return false;
00172                                 }
00173 
00174                                 $this->columns[$i] = strtolower( $name );
00175                         }
00176                 }
00177 
00178                 $this->sql = $sql;
00179         }
00180 
00185         public function getResult() {
00186                 if ( $this->result ) {
00187                         return $this->result;
00188                 }
00189                 else return false;
00190         }
00191 
00196         public function getNum_rows() {
00197                 return $this->num_rows;
00198         }
00199 
00204         public function fetchObject() {
00205                 if ( $this->result
00206                                 && $this->num_rows > 0
00207                                 && $this->current_pos >= 0
00208                                 && $this->current_pos < $this->num_rows )
00209                 {
00210                         $row = $this->fetchRow();
00211                         $ret = new stdClass();
00212 
00213                         foreach ( $row as $k => $v ) {
00214                                 $lc = $this->columns[$k];
00215                                 $ret->$lc = $v;
00216                         }
00217                         return $ret;
00218                 }
00219                 return false;
00220         }
00221 
00227         public function fetchRow(){
00228                 if ( $this->result
00229                                 && $this->num_rows > 0
00230                                 && $this->current_pos >= 0
00231                                 && $this->current_pos < $this->num_rows )
00232                 {
00233                         if ( $this->loadedLines <= $this->current_pos ) {
00234                                 $row = db2_fetch_array( $this->result );
00235                                 $this->resultSet[$this->loadedLines++] = $row;
00236                                 if ( $this->db->lastErrno() ) {
00237                                         throw new DBUnexpectedError( $this->db, 'Error in fetchRow(): '
00238                                                 . htmlspecialchars( $this->db->lastError() ) );
00239                                 }
00240                         }
00241 
00242                         if ( $this->loadedLines > $this->current_pos ){
00243                                 return $this->resultSet[$this->current_pos++];
00244                         }
00245 
00246                 }
00247                 return false;
00248         }
00249 
00254         public function freeResult(){
00255                 unset( $this->resultSet );
00256                 if ( !@db2_free_result( $this->result ) ) {
00257                         throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
00258                 }
00259         }
00260 }
00261 
00266 class DatabaseIbm_db2 extends DatabaseBase {
00267         /*
00268          * Inherited members
00269         protected $mLastQuery = '';
00270         protected $mPHPError = false;
00271 
00272         protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
00273         protected $mOpened = false;
00274 
00275         protected $mTablePrefix;
00276         protected $mFlags;
00277         protected $mTrxLevel = 0;
00278         protected $mErrorCount = 0;
00279         protected $mLBInfo = array();
00280         protected $mFakeSlaveLag = null, $mFakeMaster = false;
00281          *
00282          */
00283 
00285         protected $mPort = null;
00287         protected $mSchema = null;
00289         protected $mSchemaSet = false;
00291         protected $mLastResult = null;
00293         protected $mAffectedRows = null;
00295         protected $mNumRows = null;
00297         protected $currentRow = 0;
00298 
00300         public $mConnOptions = array();
00302         public $mStmtOptions = array();
00303 
00305         const USE_GLOBAL = 'get from global';
00306 
00308         const NONE_OPTION = 0x00;
00310         const CONN_OPTION = 0x01;
00312         const STMT_OPTION = 0x02;
00313 
00315         const REGULAR_MODE = 'regular';
00317         const INSTALL_MODE = 'install';
00318 
00320         protected $mMode = self::REGULAR_MODE;
00321 
00323         protected $mInsertId = null;
00324 
00325         ######################################
00326         # Getters and Setters
00327         ######################################
00328 
00333         function cascadingDeletes() {
00334                 return true;
00335         }
00336 
00342         function cleanupTriggers() {
00343                 return true;
00344         }
00345 
00352         function strictIPs() {
00353                 return true;
00354         }
00355 
00360         function realTimestamps() {
00361                 return true;
00362         }
00363 
00368         function implicitGroupby() {
00369                 return false;
00370         }
00371 
00378         function implicitOrderby() {
00379                 return false;
00380         }
00381 
00387         function searchableIPs() {
00388                 return true;
00389         }
00390 
00395         function functionalIndexes() {
00396                 return true;
00397         }
00398 
00403         public function getWikiID() {
00404                 if( $this->mSchema ) {
00405                         return "{$this->mDBname}-{$this->mSchema}";
00406                 } else {
00407                         return $this->mDBname;
00408                 }
00409         }
00410 
00415         public function getType() {
00416                 return 'ibm_db2';
00417         }
00418 
00423         public function getDb(){
00424                 return $this->mConn;
00425         }
00426 
00436         public function __construct( $server = false, $user = false,
00437                                                         $password = false,
00438                                                         $dbName = false, $flags = 0,
00439                                                         $schema = self::USE_GLOBAL )
00440         {
00441                 global $wgDBmwschema;
00442 
00443                 if ( $schema == self::USE_GLOBAL ) {
00444                         $this->mSchema = $wgDBmwschema;
00445                 } else {
00446                         $this->mSchema = $schema;
00447                 }
00448 
00449                 // configure the connection and statement objects
00450                 $this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER',
00451                         self::CONN_OPTION | self::STMT_OPTION );
00452                 $this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON',
00453                         self::STMT_OPTION );
00454                 $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON',
00455                         self::STMT_OPTION );
00456                 parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags );
00457         }
00458 
00465         private function setDB2Option( $name, $const, $type ) {
00466                 if ( defined( $const ) ) {
00467                         if ( $type & self::CONN_OPTION ) {
00468                                 $this->mConnOptions[$name] = constant( $const );
00469                         }
00470                         if ( $type & self::STMT_OPTION ) {
00471                                 $this->mStmtOptions[$name] = constant( $const );
00472                         }
00473                 } else {
00474                         $this->installPrint(
00475                                 "$const is not defined. ibm_db2 version is likely too low." );
00476                 }
00477         }
00478 
00483         private function installPrint( $string ) {
00484                 wfDebug( "$string\n" );
00485                 if ( $this->mMode == self::INSTALL_MODE ) {
00486                         print "<li><pre>$string</pre></li>";
00487                         flush();
00488                 }
00489         }
00490 
00501         public function open( $server, $user, $password, $dbName ) {
00502                 wfProfileIn( __METHOD__ );
00503 
00504                 # Load IBM DB2 driver if missing
00505                 wfDl( 'ibm_db2' );
00506 
00507                 # Test for IBM DB2 support, to avoid suppressed fatal error
00508                 if ( !function_exists( 'db2_connect' ) ) {
00509                         throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" );
00510                 }
00511 
00512                 global $wgDBport;
00513 
00514                 // Close existing connection
00515                 $this->close();
00516                 // Cache conn info
00517                 $this->mServer = $server;
00518                 $this->mPort = $port = $wgDBport;
00519                 $this->mUser = $user;
00520                 $this->mPassword = $password;
00521                 $this->mDBname = $dbName;
00522 
00523                 $this->openUncataloged( $dbName, $user, $password, $server, $port );
00524 
00525                 if ( !$this->mConn ) {
00526                         $this->installPrint( "DB connection error\n" );
00527                         $this->installPrint(
00528                                 "Server: $server, Database: $dbName, User: $user, Password: "
00529                                 . substr( $password, 0, 3 ) . "...\n" );
00530                         $this->installPrint( $this->lastError() . "\n" );
00531                         wfProfileOut( __METHOD__ );
00532                         wfDebug( "DB connection error\n" );
00533                         wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
00534                         wfDebug( $this->lastError() . "\n" );
00535                         throw new DBConnectionError( $this, $this->lastError() );
00536                 }
00537 
00538                 // Some MediaWiki code is still transaction-less (?).
00539                 // The strategy is to keep AutoCommit on for that code
00540                 //  but switch it off whenever a transaction is begun.
00541                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
00542 
00543                 $this->mOpened = true;
00544                 $this->applySchema();
00545 
00546                 wfProfileOut( __METHOD__ );
00547                 return $this->mConn;
00548         }
00549 
00553         protected function openCataloged( $dbName, $user, $password ) {
00554                 wfSuppressWarnings();
00555                 $this->mConn = db2_pconnect( $dbName, $user, $password );
00556                 wfRestoreWarnings();
00557         }
00558 
00562         protected function openUncataloged( $dbName, $user, $password, $server, $port )
00563         {
00564                 $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";
00565                 wfSuppressWarnings();
00566                 $this->mConn = db2_pconnect( $dsn, "", "", array() );
00567                 wfRestoreWarnings();
00568         }
00569 
00575         protected function closeConnection() {
00576                 return db2_close( $this->mConn );
00577         }
00578 
00584         public function lastError() {
00585                 $connerr = db2_conn_errormsg();
00586                 if ( $connerr ) {
00587                         //$this->rollback( __METHOD__ );
00588                         return $connerr;
00589                 }
00590                 $stmterr = db2_stmt_errormsg();
00591                 if ( $stmterr ) {
00592                         //$this->rollback( __METHOD__ );
00593                         return $stmterr;
00594                 }
00595 
00596                 return false;
00597         }
00598 
00604         public function lastErrno() {
00605                 $connerr = db2_conn_error();
00606                 if ( $connerr ) {
00607                         return $connerr;
00608                 }
00609                 $stmterr = db2_stmt_error();
00610                 if ( $stmterr ) {
00611                         return $stmterr;
00612                 }
00613                 return 0;
00614         }
00615 
00620         public function isOpen() { return $this->mOpened; }
00621 
00627         protected function doQuery( $sql ) {
00628                 $this->applySchema();
00629 
00630                 // Needed to handle any UTF-8 encoding issues in the raw sql
00631                 // Note that we fully support prepared statements for DB2
00632                 // prepare() and execute() should be used instead of doQuery() whenever possible
00633                 $sql = utf8_decode( $sql );
00634 
00635                 $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
00636                 if( $ret == false ) {
00637                         $error = db2_stmt_errormsg();
00638 
00639                         $this->installPrint( "<pre>$sql</pre>" );
00640                         $this->installPrint( $error );
00641                         throw new DBUnexpectedError( $this, 'SQL error: '
00642                                 . htmlspecialchars( $error ) );
00643                 }
00644                 $this->mLastResult = $ret;
00645                 $this->mAffectedRows = null; // Not calculated until asked for
00646                 return $ret;
00647         }
00648 
00652         public function getServerVersion() {
00653                 $info = db2_server_info( $this->mConn );
00654                 return $info->DBMS_VER;
00655         }
00656 
00661         public function tableExists( $table, $fname = __METHOD__ ) {
00662                 $schema = $this->mSchema;
00663 
00664                 $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" .
00665                         strtoupper( $table ) .
00666                         "' AND ST.CREATOR = '" .
00667                         strtoupper( $schema ) . "'";
00668                 $res = $this->query( $sql );
00669                 if ( !$res ) {
00670                         return false;
00671                 }
00672 
00673                 // If the table exists, there should be one of it
00674                 $row = $this->fetchRow( $res );
00675                 $count = $row[0];
00676                 if ( $count == '1' || $count == 1 ) {
00677                         return true;
00678                 }
00679 
00680                 return false;
00681         }
00682 
00692         public function fetchObject( $res ) {
00693                 if ( $res instanceof ResultWrapper ) {
00694                         $res = $res->result;
00695                 }
00696                 wfSuppressWarnings();
00697                 $row = db2_fetch_object( $res );
00698                 wfRestoreWarnings();
00699                 if( $this->lastErrno() ) {
00700                         throw new DBUnexpectedError( $this, 'Error in fetchObject(): '
00701                                 . htmlspecialchars( $this->lastError() ) );
00702                 }
00703                 return $row;
00704         }
00705 
00714         public function fetchRow( $res ) {
00715                 if ( $res instanceof ResultWrapper ) {
00716                         $res = $res->result;
00717                 }
00718                 if ( db2_num_rows( $res ) > 0) {
00719                         wfSuppressWarnings();
00720                         $row = db2_fetch_array( $res );
00721                         wfRestoreWarnings();
00722                         if ( $this->lastErrno() ) {
00723                                 throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
00724                                         . htmlspecialchars( $this->lastError() ) );
00725                         }
00726                         return $row;
00727                 }
00728                 return false;
00729         }
00730 
00738         public function addQuotes( $s ) {
00739                 //$this->installPrint( "DB2::addQuotes( $s )\n" );
00740                 if ( is_null( $s ) ) {
00741                         return 'NULL';
00742                 } elseif ( $s instanceof Blob ) {
00743                         return "'" . $s->fetch( $s ) . "'";
00744                 } elseif ( $s instanceof IBM_DB2Blob ) {
00745                         return "'" . $this->decodeBlob( $s ) . "'";
00746                 }
00747                 $s = $this->strencode( $s );
00748                 if ( is_numeric( $s ) ) {
00749                         return $s;
00750                 } else {
00751                         return "'$s'";
00752                 }
00753         }
00754 
00761         public function is_numeric_type( $type ) {
00762                 switch ( strtoupper( $type ) ) {
00763                         case 'SMALLINT':
00764                         case 'INTEGER':
00765                         case 'INT':
00766                         case 'BIGINT':
00767                         case 'DECIMAL':
00768                         case 'REAL':
00769                         case 'DOUBLE':
00770                         case 'DECFLOAT':
00771                                 return true;
00772                 }
00773                 return false;
00774         }
00775 
00781         public function strencode( $s ) {
00782                 // Bloody useless function
00783                 //  Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
00784                 //  But also necessary
00785                 $s = db2_escape_string( $s );
00786                 // Wide characters are evil -- some of them look like '
00787                 $s = utf8_encode( $s );
00788                 // Fix its stupidity
00789                 $from = array(  "\\\\", "\\'",  '\\n',  '\\t',  '\\"',  '\\r' );
00790                 $to = array(            "\\",           "''",           "\n",           "\t",           '"',            "\r" );
00791                 $s = str_replace( $from, $to, $s ); // DB2 expects '', not \' escaping
00792                 return $s;
00793         }
00794 
00798         protected function applySchema() {
00799                 if ( !( $this->mSchemaSet ) ) {
00800                         $this->mSchemaSet = true;
00801                         $this->begin( __METHOD__ );
00802                         $this->doQuery( "SET SCHEMA = $this->mSchema" );
00803                         $this->commit( __METHOD__ );
00804                 }
00805         }
00806 
00810         protected function doBegin( $fname = 'DatabaseIbm_db2::begin' ) {
00811                 // BEGIN is implicit for DB2
00812                 // However, it requires that AutoCommit be off.
00813 
00814                 // Some MediaWiki code is still transaction-less (?).
00815                 // The strategy is to keep AutoCommit on for that code
00816                 //  but switch it off whenever a transaction is begun.
00817                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_OFF );
00818 
00819                 $this->mTrxLevel = 1;
00820         }
00821 
00826         protected function doCommit( $fname = 'DatabaseIbm_db2::commit' ) {
00827                 db2_commit( $this->mConn );
00828 
00829                 // Some MediaWiki code is still transaction-less (?).
00830                 // The strategy is to keep AutoCommit on for that code
00831                 //  but switch it off whenever a transaction is begun.
00832                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
00833 
00834                 $this->mTrxLevel = 0;
00835         }
00836 
00840         protected function doRollback( $fname = 'DatabaseIbm_db2::rollback' ) {
00841                 db2_rollback( $this->mConn );
00842                 // turn auto-commit back on
00843                 // not sure if this is appropriate
00844                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
00845                 $this->mTrxLevel = 0;
00846         }
00847 
00859         function makeList( $a, $mode = LIST_COMMA ) {
00860                 if ( !is_array( $a ) ) {
00861                         throw new DBUnexpectedError( $this,
00862                                 'DatabaseIbm_db2::makeList called with incorrect parameters' );
00863                 }
00864 
00865                 // if this is for a prepared UPDATE statement
00866                 // (this should be promoted to the parent class
00867                 //  once other databases use prepared statements)
00868                 if ( $mode == LIST_SET_PREPARED ) {
00869                         $first = true;
00870                         $list = '';
00871                         foreach ( $a as $field => $value ) {
00872                                 if ( !$first ) {
00873                                         $list .= ", $field = ?";
00874                                 } else {
00875                                         $list .= "$field = ?";
00876                                         $first = false;
00877                                 }
00878                         }
00879                         $list .= '';
00880 
00881                         return $list;
00882                 }
00883 
00884                 // otherwise, call the usual function
00885                 return parent::makeList( $a, $mode );
00886         }
00887 
00897         public function limitResult( $sql, $limit, $offset=false ) {
00898                 if( !is_numeric( $limit ) ) {
00899                         throw new DBUnexpectedError( $this,
00900                                 "Invalid non-numeric limit passed to limitResult()\n" );
00901                 }
00902                 if( $offset ) {
00903                         if ( stripos( $sql, 'where' ) === false ) {
00904                                 return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )";
00905                         } else {
00906                                 return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )";
00907                         }
00908                 }
00909                 return "$sql FETCH FIRST $limit ROWS ONLY ";
00910         }
00911 
00919         public function tableName( $name, $format = 'quoted' ) {
00920                 // we want maximum compatibility with MySQL schema
00921                 return $name;
00922         }
00923 
00930         public function timestamp( $ts = 0 ) {
00931                 // TS_MW cannot be easily distinguished from an integer
00932                 return wfTimestamp( TS_DB2, $ts );
00933         }
00934 
00940         public function nextSequenceValue( $seqName ) {
00941                 // Not using sequences in the primary schema to allow for easier migration
00942                 //  from MySQL
00943                 // Emulating MySQL behaviour of using NULL to signal that sequences
00944                 // aren't used
00945                 /*
00946                 $safeseq = preg_replace( "/'/", "''", $seqName );
00947                 $res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
00948                 $row = $this->fetchRow( $res );
00949                 $this->mInsertId = $row[0];
00950                 return $this->mInsertId;
00951                 */
00952                 return null;
00953         }
00954 
00959         public function insertId() {
00960                 return $this->mInsertId;
00961         }
00962 
00972         private function calcInsertId( $table, $primaryKey, $stmt ) {
00973                 if ( $primaryKey ) {
00974                         $this->mInsertId = db2_last_insert_id( $this->mConn );
00975                 }
00976         }
00977 
00991         public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert',
00992                 $options = array() )
00993         {
00994                 if ( !count( $args ) ) {
00995                         return true;
00996                 }
00997                 // get database-specific table name (not used)
00998                 $table = $this->tableName( $table );
00999                 // format options as an array
01000                 $options = IBM_DB2Helper::makeArray( $options );
01001                 // format args as an array of arrays
01002                 if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) {
01003                         $args = array( $args );
01004                 }
01005 
01006                 // prevent insertion of NULL into primary key columns
01007                 list( $args, $primaryKeys ) = $this->removeNullPrimaryKeys( $table, $args );
01008                 // if there's only one primary key
01009                 // we'll be able to read its value after insertion
01010                 $primaryKey = false;
01011                 if ( count( $primaryKeys ) == 1 ) {
01012                         $primaryKey = $primaryKeys[0];
01013                 }
01014 
01015                 // get column names
01016                 $keys = array_keys( $args[0] );
01017                 $key_count = count( $keys );
01018 
01019                 // If IGNORE is set, we use savepoints to emulate mysql's behavior
01020                 $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
01021 
01022                 // assume success
01023                 $res = true;
01024                 // If we are not in a transaction, we need to be for savepoint trickery
01025                 if ( !$this->mTrxLevel ) {
01026                         $this->begin( __METHOD__ );
01027                 }
01028 
01029                 $sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES ';
01030                 if ( $key_count == 1 ) {
01031                         $sql .= '( ? )';
01032                 } else {
01033                         $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )';
01034                 }
01035                 $this->installPrint( "Preparing the following SQL:" );
01036                 $this->installPrint( "$sql" );
01037                 $this->installPrint( print_r( $args, true ));
01038                 $stmt = $this->prepare( $sql );
01039 
01040                 // start a transaction/enter transaction mode
01041                 $this->begin( __METHOD__ );
01042 
01043                 if ( !$ignore ) {
01044                         //$first = true;
01045                         foreach ( $args as $row ) {
01046                                 //$this->installPrint( "Inserting " . print_r( $row, true ));
01047                                 // insert each row into the database
01048                                 $res = $res & $this->execute( $stmt, $row );
01049                                 if ( !$res ) {
01050                                         $this->installPrint( 'Last error:' );
01051                                         $this->installPrint( $this->lastError() );
01052                                 }
01053                                 // get the last inserted value into a generated column
01054                                 $this->calcInsertId( $table, $primaryKey, $stmt );
01055                         }
01056                 } else {
01057                         $olde = error_reporting( 0 );
01058                         // For future use, we may want to track the number of actual inserts
01059                         // Right now, insert (all writes) simply return true/false
01060                         $numrowsinserted = 0;
01061 
01062                         // always return true
01063                         $res = true;
01064 
01065                         foreach ( $args as $row ) {
01066                                 $overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
01067                                 db2_exec( $this->mConn, $overhead, $this->mStmtOptions );
01068 
01069                                 $res2 = $this->execute( $stmt, $row );
01070 
01071                                 if ( !$res2 ) {
01072                                         $this->installPrint( 'Last error:' );
01073                                         $this->installPrint( $this->lastError() );
01074                                 }
01075                                 // get the last inserted value into a generated column
01076                                 $this->calcInsertId( $table, $primaryKey, $stmt );
01077 
01078                                 $errNum = $this->lastErrno();
01079                                 if ( $errNum ) {
01080                                         db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore",
01081                                                 $this->mStmtOptions );
01082                                 } else {
01083                                         db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore",
01084                                                 $this->mStmtOptions );
01085                                         $numrowsinserted++;
01086                                 }
01087                         }
01088 
01089                         $olde = error_reporting( $olde );
01090                         // Set the affected row count for the whole operation
01091                         $this->mAffectedRows = $numrowsinserted;
01092                 }
01093                 // commit either way
01094                 $this->commit( __METHOD__ );
01095                 $this->freePrepared( $stmt );
01096 
01097                 return $res;
01098         }
01099 
01108         private function removeNullPrimaryKeys( $table, $args ) {
01109                 $schema = $this->mSchema;
01110 
01111                 // find out the primary keys
01112                 $keyres = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '"
01113                   . strtoupper( $table )
01114                   . "' AND TBCREATOR = '"
01115                   . strtoupper( $schema )
01116                   . "' AND KEYSEQ > 0" );
01117 
01118                 $keys = array();
01119                 for (
01120                         $row = $this->fetchRow( $keyres );
01121                         $row != null;
01122                         $row = $this->fetchRow( $keyres )
01123                 )
01124                 {
01125                         $keys[] = strtolower( $row[0] );
01126                 }
01127                 // remove primary keys
01128                 foreach ( $args as $ai => $row ) {
01129                         foreach ( $keys as $key ) {
01130                                 if ( $row[$key] == null ) {
01131                                         unset( $row[$key] );
01132                                 }
01133                         }
01134                         $args[$ai] = $row;
01135                 }
01136                 // return modified hash
01137                 return array( $args, $keys );
01138         }
01139 
01152         public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update',
01153                 $options = array() )
01154         {
01155                 $table = $this->tableName( $table );
01156                 $opts = $this->makeUpdateOptions( $options );
01157                 $sql = "UPDATE $opts $table SET "
01158                         . $this->makeList( $values, LIST_SET_PREPARED );
01159                 if ( $conds != '*' ) {
01160                         $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01161                 }
01162                 $stmt = $this->prepare( $sql );
01163                 $this->installPrint( 'UPDATE: ' . print_r( $values, true ) );
01164                 // assuming for now that an array with string keys will work
01165                 // if not, convert to simple array first
01166                 $result = $this->execute( $stmt, $values );
01167                 $this->freePrepared( $stmt );
01168 
01169                 return $result;
01170         }
01171 
01178         public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
01179                 if ( !$conds ) {
01180                         throw new DBUnexpectedError( $this,
01181                                 'DatabaseIbm_db2::delete() called with no conditions' );
01182                 }
01183                 $table = $this->tableName( $table );
01184                 $sql = "DELETE FROM $table";
01185                 if ( $conds != '*' ) {
01186                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
01187                 }
01188                 $result = $this->query( $sql, $fname );
01189 
01190                 return $result;
01191         }
01192 
01197         public function affectedRows() {
01198                 if ( !is_null( $this->mAffectedRows ) ) {
01199                         // Forced result for simulated queries
01200                         return $this->mAffectedRows;
01201                 }
01202                 if( empty( $this->mLastResult ) ) {
01203                         return 0;
01204                 }
01205                 return db2_num_rows( $this->mLastResult );
01206         }
01207 
01214         public function numRows( $res ) {
01215                 if ( $res instanceof ResultWrapper ) {
01216                         $res = $res->result;
01217                 }
01218 
01219                 if ( $this->mNumRows ) {
01220                         return $this->mNumRows;
01221                 } else {
01222                         return 0;
01223                 }
01224         }
01225 
01232         public function dataSeek( $res, $row ) {
01233                 if ( $res instanceof ResultWrapper ) {
01234                         return $res = $res->result;
01235                 }
01236                 if ( $res instanceof IBM_DB2Result ) {
01237                         return $res->dataSeek( $row );
01238                 }
01239                 wfDebug( "dataSeek operation in DB2 database\n" );
01240                 return false;
01241         }
01242 
01243         ###
01244         # Fix notices in Block.php
01245         ###
01246 
01252         public function freeResult( $res ) {
01253                 if ( $res instanceof ResultWrapper ) {
01254                         $res = $res->result;
01255                 }
01256                 wfSuppressWarnings();
01257                 $ok = db2_free_result( $res );
01258                 wfRestoreWarnings();
01259                 if ( !$ok ) {
01260                         throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
01261                 }
01262         }
01263 
01269         public function numFields( $res ) {
01270                 if ( $res instanceof ResultWrapper ) {
01271                         $res = $res->result;
01272                 }
01273                 if ( $res instanceof IBM_DB2Result ) {
01274                         $res = $res->getResult();
01275                 }
01276                 return db2_num_fields( $res );
01277         }
01278 
01285         public function fieldName( $res, $n ) {
01286                 if ( $res instanceof ResultWrapper ) {
01287                         $res = $res->result;
01288                 }
01289                 if ( $res instanceof IBM_DB2Result ) {
01290                         $res = $res->getResult();
01291                 }
01292                 return db2_field_name( $res, $n );
01293         }
01294 
01313         public function select( $table, $vars, $conds = '', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
01314         {
01315                 $res = parent::select( $table, $vars, $conds, $fname, $options,
01316                         $join_conds );
01317                 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01318 
01319                 // We must adjust for offset
01320                 if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
01321                         $limit = $options['LIMIT'];
01322                         $offset = $options['OFFSET'];
01323                 }
01324 
01325                 // DB2 does not have a proper num_rows() function yet, so we must emulate
01326                 // DB2 9.5.4 and the corresponding ibm_db2 driver will introduce
01327                 //  a working one
01328                 // TODO: Yay!
01329 
01330                 // we want the count
01331                 $vars2 = array( 'count( * ) as num_rows' );
01332                 // respecting just the limit option
01333                 $options2 = array();
01334                 if ( isset( $options['LIMIT'] ) ) {
01335                         $options2['LIMIT'] = $options['LIMIT'];
01336                 }
01337                 // but don't try to emulate for GROUP BY
01338                 if ( isset( $options['GROUP BY'] ) ) {
01339                         return $res;
01340                 }
01341 
01342                 $res2 = parent::select( $table, $vars2, $conds, $fname, $options2,
01343                         $join_conds );
01344 
01345                 $obj = $this->fetchObject( $res2 );
01346                 $this->mNumRows = $obj->num_rows;
01347 
01348                 return new ResultWrapper( $this, new IBM_DB2Result( $this, $res, $obj->num_rows, $vars, $sql ) );
01349         }
01350 
01361         function makeSelectOptions( $options ) {
01362                 $preLimitTail = $postLimitTail = '';
01363                 $startOpts = '';
01364 
01365                 $noKeyOptions = array();
01366                 foreach ( $options as $key => $option ) {
01367                         if ( is_numeric( $key ) ) {
01368                                 $noKeyOptions[$option] = true;
01369                         }
01370                 }
01371 
01372                 if ( isset( $options['GROUP BY'] ) ) {
01373                         $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
01374                 }
01375                 if ( isset( $options['HAVING'] ) ) {
01376                         $preLimitTail .= " HAVING {$options['HAVING']}";
01377                 }
01378                 if ( isset( $options['ORDER BY'] ) ) {
01379                         $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
01380                 }
01381 
01382                 if ( isset( $noKeyOptions['DISTINCT'] )
01383                         || isset( $noKeyOptions['DISTINCTROW'] ) )
01384                 {
01385                         $startOpts .= 'DISTINCT';
01386                 }
01387 
01388                 return array( $startOpts, '', $preLimitTail, $postLimitTail );
01389         }
01390 
01395         public static function getSoftwareLink() {
01396                 return '[http://www.ibm.com/db2/express/ IBM DB2]';
01397         }
01398 
01405         public function getSearchEngine() {
01406                 return 'SearchIBM_DB2';
01407         }
01408 
01413         public function wasDeadlock() {
01414                 // get SQLSTATE
01415                 $err = $this->lastErrno();
01416                 switch( $err ) {
01417                         // This is literal port of the MySQL logic and may be wrong for DB2
01418                         case '40001':   // sql0911n, Deadlock or timeout, rollback
01419                         case '57011':   // sql0904n, Resource unavailable, no rollback
01420                         case '57033':   // sql0913n, Deadlock or timeout, no rollback
01421                         $this->installPrint( "In a deadlock because of SQLSTATE $err" );
01422                         return true;
01423                 }
01424                 return false;
01425         }
01426 
01432         public function ping() {
01433                 // db2_ping() doesn't exist
01434                 // Emulate
01435                 $this->close();
01436                 $this->openUncataloged( $this->mDBName, $this->mUser,
01437                         $this->mPassword, $this->mServer, $this->mPort );
01438 
01439                 return false;
01440         }
01441         ######################################
01442         # Unimplemented and not applicable
01443         ######################################
01444 
01449         public function fillPreparedArg( $matches ) {
01450                 $this->installPrint( 'Not useful for DB2: fillPreparedArg()' );
01451                 return '';
01452         }
01453 
01454         ######################################
01455         # Reflection
01456         ######################################
01457 
01466         public function indexInfo( $table, $index,
01467                 $fname = 'DatabaseIbm_db2::indexExists' )
01468         {
01469                 $table = $this->tableName( $table );
01470                 $sql = <<<SQL
01471 SELECT name as indexname
01472 FROM sysibm.sysindexes si
01473 WHERE si.name='$index' AND si.tbname='$table'
01474 AND sc.tbcreator='$this->mSchema'
01475 SQL;
01476                 $res = $this->query( $sql, $fname );
01477                 if ( !$res ) {
01478                         return null;
01479                 }
01480                 $row = $this->fetchObject( $res );
01481                 if ( $row != null ) {
01482                         return $row;
01483                 } else {
01484                         return false;
01485                 }
01486         }
01487 
01494         public function fieldInfo( $table, $field ) {
01495                 return IBM_DB2Field::fromText( $this, $table, $field );
01496         }
01497 
01504         public function fieldType( $res, $index ) {
01505                 if ( $res instanceof ResultWrapper ) {
01506                         $res = $res->result;
01507                 }
01508                 if ( $res instanceof IBM_DB2Result ) {
01509                         $res = $res->getResult();
01510                 }
01511                 return db2_field_type( $res, $index );
01512         }
01513 
01521         public function indexUnique ( $table, $index,
01522                 $fname = 'DatabaseIbm_db2::indexUnique' )
01523         {
01524                 $table = $this->tableName( $table );
01525                 $sql = <<<SQL
01526 SELECT si.name as indexname
01527 FROM sysibm.sysindexes si
01528 WHERE si.name='$index' AND si.tbname='$table'
01529 AND sc.tbcreator='$this->mSchema'
01530 AND si.uniquerule IN ( 'U', 'P' )
01531 SQL;
01532                 $res = $this->query( $sql, $fname );
01533                 if ( !$res ) {
01534                         return null;
01535                 }
01536                 if ( $this->fetchObject( $res ) ) {
01537                         return true;
01538                 }
01539                 return false;
01540 
01541         }
01542 
01549         public function textFieldSize( $table, $field ) {
01550                 $table = $this->tableName( $table );
01551                 $sql = <<<SQL
01552 SELECT length as size
01553 FROM sysibm.syscolumns sc
01554 WHERE sc.name='$field' AND sc.tbname='$table'
01555 AND sc.tbcreator='$this->mSchema'
01556 SQL;
01557                 $res = $this->query( $sql );
01558                 $row = $this->fetchObject( $res );
01559                 $size = $row->size;
01560                 return $size;
01561         }
01562 
01568         public function encodeBlob( $b ) {
01569                 return new IBM_DB2Blob( $b );
01570         }
01571 
01577         public function decodeBlob( $b ) {
01578                 return "$b";
01579         }
01580 
01587         public function buildConcat( $stringList ) {
01588                 // || is equivalent to CONCAT
01589                 // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz'
01590                 return implode( ' || ', $stringList );
01591         }
01592 
01598         public function extractUnixEpoch( $column ) {
01599                 // TODO
01600                 // see SpecialAncientpages
01601         }
01602 
01603         ######################################
01604         # Prepared statements
01605         ######################################
01606 
01619         public function prepare( $sql, $func = 'DB2::prepare' ) {
01620                 $stmt = db2_prepare( $this->mConn, $sql, $this->mStmtOptions );
01621                 return $stmt;
01622         }
01623 
01628         public function freePrepared( $prepared ) {
01629                 return db2_free_stmt( $prepared );
01630         }
01631 
01638         public function execute( $prepared, $args = null ) {
01639                 if( !is_array( $args ) ) {
01640                         # Pull the var args
01641                         $args = func_get_args();
01642                         array_shift( $args );
01643                 }
01644                 $res = db2_execute( $prepared, $args );
01645                 if ( !$res ) {
01646                         $this->installPrint( db2_stmt_errormsg() );
01647                 }
01648                 return $res;
01649         }
01650 
01658         public function fillPrepared( $preparedQuery, $args ) {
01659                 reset( $args );
01660                 $this->preparedArgs =& $args;
01661 
01662                 foreach ( $args as $i => $arg ) {
01663                         db2_bind_param( $preparedQuery, $i+1, $args[$i] );
01664                 }
01665 
01666                 return $preparedQuery;
01667         }
01668 
01673         public function setMode( $mode ) {
01674                 $old = $this->mMode;
01675                 $this->mMode = $mode;
01676                 return $old;
01677         }
01678 
01685         function bitNot( $field ) {
01686                 // expecting bit-fields smaller than 4bytes
01687                 return "BITNOT( $field )";
01688         }
01689 
01697         function bitAnd( $fieldLeft, $fieldRight ) {
01698                 return "BITAND( $fieldLeft, $fieldRight )";
01699         }
01700 
01708         function bitOr( $fieldLeft, $fieldRight ) {
01709                 return "BITOR( $fieldLeft, $fieldRight )";
01710         }
01711 }
01712 
01713 class IBM_DB2Helper {
01714         public static function makeArray( $maybeArray ) {
01715                 if ( !is_array( $maybeArray ) ) {
01716                         return array( $maybeArray );
01717                 }
01718 
01719                 return $maybeArray;
01720         }
01721 }