MediaWiki  master
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 
00502         public function open( $server, $user, $password, $dbName ) {
00503                 wfProfileIn( __METHOD__ );
00504 
00505                 # Load IBM DB2 driver if missing
00506                 wfDl( 'ibm_db2' );
00507 
00508                 # Test for IBM DB2 support, to avoid suppressed fatal error
00509                 if ( !function_exists( 'db2_connect' ) ) {
00510                         throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" );
00511                 }
00512 
00513                 global $wgDBport;
00514 
00515                 // Close existing connection
00516                 $this->close();
00517                 // Cache conn info
00518                 $this->mServer = $server;
00519                 $this->mPort = $port = $wgDBport;
00520                 $this->mUser = $user;
00521                 $this->mPassword = $password;
00522                 $this->mDBname = $dbName;
00523 
00524                 $this->openUncataloged( $dbName, $user, $password, $server, $port );
00525 
00526                 if ( !$this->mConn ) {
00527                         $this->installPrint( "DB connection error\n" );
00528                         $this->installPrint(
00529                                 "Server: $server, Database: $dbName, User: $user, Password: "
00530                                 . substr( $password, 0, 3 ) . "...\n" );
00531                         $this->installPrint( $this->lastError() . "\n" );
00532                         wfProfileOut( __METHOD__ );
00533                         wfDebug( "DB connection error\n" );
00534                         wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
00535                         wfDebug( $this->lastError() . "\n" );
00536                         throw new DBConnectionError( $this, $this->lastError() );
00537                 }
00538 
00539                 // Some MediaWiki code is still transaction-less (?).
00540                 // The strategy is to keep AutoCommit on for that code
00541                 //  but switch it off whenever a transaction is begun.
00542                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
00543 
00544                 $this->mOpened = true;
00545                 $this->applySchema();
00546 
00547                 wfProfileOut( __METHOD__ );
00548                 return $this->mConn;
00549         }
00550 
00554         protected function openCataloged( $dbName, $user, $password ) {
00555                 wfSuppressWarnings();
00556                 $this->mConn = db2_pconnect( $dbName, $user, $password );
00557                 wfRestoreWarnings();
00558         }
00559 
00563         protected function openUncataloged( $dbName, $user, $password, $server, $port )
00564         {
00565                 $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";
00566                 wfSuppressWarnings();
00567                 $this->mConn = db2_pconnect( $dsn, "", "", array() );
00568                 wfRestoreWarnings();
00569         }
00570 
00576         protected function closeConnection() {
00577                 return db2_close( $this->mConn );
00578         }
00579 
00585         public function lastError() {
00586                 $connerr = db2_conn_errormsg();
00587                 if ( $connerr ) {
00588                         //$this->rollback( __METHOD__ );
00589                         return $connerr;
00590                 }
00591                 $stmterr = db2_stmt_errormsg();
00592                 if ( $stmterr ) {
00593                         //$this->rollback( __METHOD__ );
00594                         return $stmterr;
00595                 }
00596 
00597                 return false;
00598         }
00599 
00605         public function lastErrno() {
00606                 $connerr = db2_conn_error();
00607                 if ( $connerr ) {
00608                         return $connerr;
00609                 }
00610                 $stmterr = db2_stmt_error();
00611                 if ( $stmterr ) {
00612                         return $stmterr;
00613                 }
00614                 return 0;
00615         }
00616 
00621         public function isOpen() { return $this->mOpened; }
00622 
00629         protected function doQuery( $sql ) {
00630                 $this->applySchema();
00631 
00632                 // Needed to handle any UTF-8 encoding issues in the raw sql
00633                 // Note that we fully support prepared statements for DB2
00634                 // prepare() and execute() should be used instead of doQuery() whenever possible
00635                 $sql = utf8_decode( $sql );
00636 
00637                 $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
00638                 if( $ret == false ) {
00639                         $error = db2_stmt_errormsg();
00640 
00641                         $this->installPrint( "<pre>$sql</pre>" );
00642                         $this->installPrint( $error );
00643                         throw new DBUnexpectedError( $this, 'SQL error: '
00644                                 . htmlspecialchars( $error ) );
00645                 }
00646                 $this->mLastResult = $ret;
00647                 $this->mAffectedRows = null; // Not calculated until asked for
00648                 return $ret;
00649         }
00650 
00654         public function getServerVersion() {
00655                 $info = db2_server_info( $this->mConn );
00656                 return $info->DBMS_VER;
00657         }
00658 
00663         public function tableExists( $table, $fname = __METHOD__ ) {
00664                 $schema = $this->mSchema;
00665 
00666                 $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" .
00667                         strtoupper( $table ) .
00668                         "' AND ST.CREATOR = '" .
00669                         strtoupper( $schema ) . "'";
00670                 $res = $this->query( $sql );
00671                 if ( !$res ) {
00672                         return false;
00673                 }
00674 
00675                 // If the table exists, there should be one of it
00676                 $row = $this->fetchRow( $res );
00677                 $count = $row[0];
00678                 if ( $count == '1' || $count == 1 ) {
00679                         return true;
00680                 }
00681 
00682                 return false;
00683         }
00684 
00694         public function fetchObject( $res ) {
00695                 if ( $res instanceof ResultWrapper ) {
00696                         $res = $res->result;
00697                 }
00698                 wfSuppressWarnings();
00699                 $row = db2_fetch_object( $res );
00700                 wfRestoreWarnings();
00701                 if( $this->lastErrno() ) {
00702                         throw new DBUnexpectedError( $this, 'Error in fetchObject(): '
00703                                 . htmlspecialchars( $this->lastError() ) );
00704                 }
00705                 return $row;
00706         }
00707 
00716         public function fetchRow( $res ) {
00717                 if ( $res instanceof ResultWrapper ) {
00718                         $res = $res->result;
00719                 }
00720                 if ( db2_num_rows( $res ) > 0) {
00721                         wfSuppressWarnings();
00722                         $row = db2_fetch_array( $res );
00723                         wfRestoreWarnings();
00724                         if ( $this->lastErrno() ) {
00725                                 throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
00726                                         . htmlspecialchars( $this->lastError() ) );
00727                         }
00728                         return $row;
00729                 }
00730                 return false;
00731         }
00732 
00740         public function addQuotes( $s ) {
00741                 //$this->installPrint( "DB2::addQuotes( $s )\n" );
00742                 if ( is_null( $s ) ) {
00743                         return 'NULL';
00744                 } elseif ( $s instanceof Blob ) {
00745                         return "'" . $s->fetch( $s ) . "'";
00746                 } elseif ( $s instanceof IBM_DB2Blob ) {
00747                         return "'" . $this->decodeBlob( $s ) . "'";
00748                 }
00749                 $s = $this->strencode( $s );
00750                 if ( is_numeric( $s ) ) {
00751                         return $s;
00752                 } else {
00753                         return "'$s'";
00754                 }
00755         }
00756 
00763         public function is_numeric_type( $type ) {
00764                 switch ( strtoupper( $type ) ) {
00765                         case 'SMALLINT':
00766                         case 'INTEGER':
00767                         case 'INT':
00768                         case 'BIGINT':
00769                         case 'DECIMAL':
00770                         case 'REAL':
00771                         case 'DOUBLE':
00772                         case 'DECFLOAT':
00773                                 return true;
00774                 }
00775                 return false;
00776         }
00777 
00783         public function strencode( $s ) {
00784                 // Bloody useless function
00785                 //  Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
00786                 //  But also necessary
00787                 $s = db2_escape_string( $s );
00788                 // Wide characters are evil -- some of them look like '
00789                 $s = utf8_encode( $s );
00790                 // Fix its stupidity
00791                 $from = array(  "\\\\", "\\'",  '\\n',  '\\t',  '\\"',  '\\r' );
00792                 $to = array(            "\\",           "''",           "\n",           "\t",           '"',            "\r" );
00793                 $s = str_replace( $from, $to, $s ); // DB2 expects '', not \' escaping
00794                 return $s;
00795         }
00796 
00800         protected function applySchema() {
00801                 if ( !( $this->mSchemaSet ) ) {
00802                         $this->mSchemaSet = true;
00803                         $this->begin( __METHOD__ );
00804                         $this->doQuery( "SET SCHEMA = $this->mSchema" );
00805                         $this->commit( __METHOD__ );
00806                 }
00807         }
00808 
00812         protected function doBegin( $fname = 'DatabaseIbm_db2::begin' ) {
00813                 // BEGIN is implicit for DB2
00814                 // However, it requires that AutoCommit be off.
00815 
00816                 // Some MediaWiki code is still transaction-less (?).
00817                 // The strategy is to keep AutoCommit on for that code
00818                 //  but switch it off whenever a transaction is begun.
00819                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_OFF );
00820 
00821                 $this->mTrxLevel = 1;
00822         }
00823 
00828         protected function doCommit( $fname = 'DatabaseIbm_db2::commit' ) {
00829                 db2_commit( $this->mConn );
00830 
00831                 // Some MediaWiki code is still transaction-less (?).
00832                 // The strategy is to keep AutoCommit on for that code
00833                 //  but switch it off whenever a transaction is begun.
00834                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
00835 
00836                 $this->mTrxLevel = 0;
00837         }
00838 
00842         protected function doRollback( $fname = 'DatabaseIbm_db2::rollback' ) {
00843                 db2_rollback( $this->mConn );
00844                 // turn auto-commit back on
00845                 // not sure if this is appropriate
00846                 db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
00847                 $this->mTrxLevel = 0;
00848         }
00849 
00864         function makeList( $a, $mode = LIST_COMMA ) {
00865                 if ( !is_array( $a ) ) {
00866                         throw new DBUnexpectedError( $this,
00867                                 'DatabaseIbm_db2::makeList called with incorrect parameters' );
00868                 }
00869 
00870                 // if this is for a prepared UPDATE statement
00871                 // (this should be promoted to the parent class
00872                 //  once other databases use prepared statements)
00873                 if ( $mode == LIST_SET_PREPARED ) {
00874                         $first = true;
00875                         $list = '';
00876                         foreach ( $a as $field => $value ) {
00877                                 if ( !$first ) {
00878                                         $list .= ", $field = ?";
00879                                 } else {
00880                                         $list .= "$field = ?";
00881                                         $first = false;
00882                                 }
00883                         }
00884                         $list .= '';
00885 
00886                         return $list;
00887                 }
00888 
00889                 // otherwise, call the usual function
00890                 return parent::makeList( $a, $mode );
00891         }
00892 
00903         public function limitResult( $sql, $limit, $offset=false ) {
00904                 if( !is_numeric( $limit ) ) {
00905                         throw new DBUnexpectedError( $this,
00906                                 "Invalid non-numeric limit passed to limitResult()\n" );
00907                 }
00908                 if( $offset ) {
00909                         if ( stripos( $sql, 'where' ) === false ) {
00910                                 return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )";
00911                         } else {
00912                                 return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )";
00913                         }
00914                 }
00915                 return "$sql FETCH FIRST $limit ROWS ONLY ";
00916         }
00917 
00925         public function tableName( $name, $format = 'quoted' ) {
00926                 // we want maximum compatibility with MySQL schema
00927                 return $name;
00928         }
00929 
00936         public function timestamp( $ts = 0 ) {
00937                 // TS_MW cannot be easily distinguished from an integer
00938                 return wfTimestamp( TS_DB2, $ts );
00939         }
00940 
00946         public function nextSequenceValue( $seqName ) {
00947                 // Not using sequences in the primary schema to allow for easier migration
00948                 //  from MySQL
00949                 // Emulating MySQL behaviour of using NULL to signal that sequences
00950                 // aren't used
00951                 /*
00952                 $safeseq = preg_replace( "/'/", "''", $seqName );
00953                 $res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
00954                 $row = $this->fetchRow( $res );
00955                 $this->mInsertId = $row[0];
00956                 return $this->mInsertId;
00957                 */
00958                 return null;
00959         }
00960 
00965         public function insertId() {
00966                 return $this->mInsertId;
00967         }
00968 
00978         private function calcInsertId( $table, $primaryKey, $stmt ) {
00979                 if ( $primaryKey ) {
00980                         $this->mInsertId = db2_last_insert_id( $this->mConn );
00981                 }
00982         }
00983 
00997         public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert',
00998                 $options = array() )
00999         {
01000                 if ( !count( $args ) ) {
01001                         return true;
01002                 }
01003                 // get database-specific table name (not used)
01004                 $table = $this->tableName( $table );
01005                 // format options as an array
01006                 $options = IBM_DB2Helper::makeArray( $options );
01007                 // format args as an array of arrays
01008                 if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) {
01009                         $args = array( $args );
01010                 }
01011 
01012                 // prevent insertion of NULL into primary key columns
01013                 list( $args, $primaryKeys ) = $this->removeNullPrimaryKeys( $table, $args );
01014                 // if there's only one primary key
01015                 // we'll be able to read its value after insertion
01016                 $primaryKey = false;
01017                 if ( count( $primaryKeys ) == 1 ) {
01018                         $primaryKey = $primaryKeys[0];
01019                 }
01020 
01021                 // get column names
01022                 $keys = array_keys( $args[0] );
01023                 $key_count = count( $keys );
01024 
01025                 // If IGNORE is set, we use savepoints to emulate mysql's behavior
01026                 $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
01027 
01028                 // assume success
01029                 $res = true;
01030                 // If we are not in a transaction, we need to be for savepoint trickery
01031                 if ( !$this->mTrxLevel ) {
01032                         $this->begin( __METHOD__ );
01033                 }
01034 
01035                 $sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES ';
01036                 if ( $key_count == 1 ) {
01037                         $sql .= '( ? )';
01038                 } else {
01039                         $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )';
01040                 }
01041                 $this->installPrint( "Preparing the following SQL:" );
01042                 $this->installPrint( "$sql" );
01043                 $this->installPrint( print_r( $args, true ));
01044                 $stmt = $this->prepare( $sql );
01045 
01046                 // start a transaction/enter transaction mode
01047                 $this->begin( __METHOD__ );
01048 
01049                 if ( !$ignore ) {
01050                         //$first = true;
01051                         foreach ( $args as $row ) {
01052                                 //$this->installPrint( "Inserting " . print_r( $row, true ));
01053                                 // insert each row into the database
01054                                 $res = $res & $this->execute( $stmt, $row );
01055                                 if ( !$res ) {
01056                                         $this->installPrint( 'Last error:' );
01057                                         $this->installPrint( $this->lastError() );
01058                                 }
01059                                 // get the last inserted value into a generated column
01060                                 $this->calcInsertId( $table, $primaryKey, $stmt );
01061                         }
01062                 } else {
01063                         $olde = error_reporting( 0 );
01064                         // For future use, we may want to track the number of actual inserts
01065                         // Right now, insert (all writes) simply return true/false
01066                         $numrowsinserted = 0;
01067 
01068                         // always return true
01069                         $res = true;
01070 
01071                         foreach ( $args as $row ) {
01072                                 $overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
01073                                 db2_exec( $this->mConn, $overhead, $this->mStmtOptions );
01074 
01075                                 $res2 = $this->execute( $stmt, $row );
01076 
01077                                 if ( !$res2 ) {
01078                                         $this->installPrint( 'Last error:' );
01079                                         $this->installPrint( $this->lastError() );
01080                                 }
01081                                 // get the last inserted value into a generated column
01082                                 $this->calcInsertId( $table, $primaryKey, $stmt );
01083 
01084                                 $errNum = $this->lastErrno();
01085                                 if ( $errNum ) {
01086                                         db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore",
01087                                                 $this->mStmtOptions );
01088                                 } else {
01089                                         db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore",
01090                                                 $this->mStmtOptions );
01091                                         $numrowsinserted++;
01092                                 }
01093                         }
01094 
01095                         $olde = error_reporting( $olde );
01096                         // Set the affected row count for the whole operation
01097                         $this->mAffectedRows = $numrowsinserted;
01098                 }
01099                 // commit either way
01100                 $this->commit( __METHOD__ );
01101                 $this->freePrepared( $stmt );
01102 
01103                 return $res;
01104         }
01105 
01114         private function removeNullPrimaryKeys( $table, $args ) {
01115                 $schema = $this->mSchema;
01116 
01117                 // find out the primary keys
01118                 $keyres = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '"
01119                   . strtoupper( $table )
01120                   . "' AND TBCREATOR = '"
01121                   . strtoupper( $schema )
01122                   . "' AND KEYSEQ > 0" );
01123 
01124                 $keys = array();
01125                 for (
01126                         $row = $this->fetchRow( $keyres );
01127                         $row != null;
01128                         $row = $this->fetchRow( $keyres )
01129                 )
01130                 {
01131                         $keys[] = strtolower( $row[0] );
01132                 }
01133                 // remove primary keys
01134                 foreach ( $args as $ai => $row ) {
01135                         foreach ( $keys as $key ) {
01136                                 if ( $row[$key] == null ) {
01137                                         unset( $row[$key] );
01138                                 }
01139                         }
01140                         $args[$ai] = $row;
01141                 }
01142                 // return modified hash
01143                 return array( $args, $keys );
01144         }
01145 
01158         public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update',
01159                 $options = array() )
01160         {
01161                 $table = $this->tableName( $table );
01162                 $opts = $this->makeUpdateOptions( $options );
01163                 $sql = "UPDATE $opts $table SET "
01164                         . $this->makeList( $values, LIST_SET_PREPARED );
01165                 if ( $conds != '*' ) {
01166                         $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01167                 }
01168                 $stmt = $this->prepare( $sql );
01169                 $this->installPrint( 'UPDATE: ' . print_r( $values, true ) );
01170                 // assuming for now that an array with string keys will work
01171                 // if not, convert to simple array first
01172                 $result = $this->execute( $stmt, $values );
01173                 $this->freePrepared( $stmt );
01174 
01175                 return $result;
01176         }
01177 
01188         public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
01189                 if ( !$conds ) {
01190                         throw new DBUnexpectedError( $this,
01191                                 'DatabaseIbm_db2::delete() called with no conditions' );
01192                 }
01193                 $table = $this->tableName( $table );
01194                 $sql = "DELETE FROM $table";
01195                 if ( $conds != '*' ) {
01196                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
01197                 }
01198                 $result = $this->query( $sql, $fname );
01199 
01200                 return $result;
01201         }
01202 
01207         public function affectedRows() {
01208                 if ( !is_null( $this->mAffectedRows ) ) {
01209                         // Forced result for simulated queries
01210                         return $this->mAffectedRows;
01211                 }
01212                 if( empty( $this->mLastResult ) ) {
01213                         return 0;
01214                 }
01215                 return db2_num_rows( $this->mLastResult );
01216         }
01217 
01224         public function numRows( $res ) {
01225                 if ( $res instanceof ResultWrapper ) {
01226                         $res = $res->result;
01227                 }
01228 
01229                 if ( $this->mNumRows ) {
01230                         return $this->mNumRows;
01231                 } else {
01232                         return 0;
01233                 }
01234         }
01235 
01242         public function dataSeek( $res, $row ) {
01243                 if ( $res instanceof ResultWrapper ) {
01244                         return $res = $res->result;
01245                 }
01246                 if ( $res instanceof IBM_DB2Result ) {
01247                         return $res->dataSeek( $row );
01248                 }
01249                 wfDebug( "dataSeek operation in DB2 database\n" );
01250                 return false;
01251         }
01252 
01253         ###
01254         # Fix notices in Block.php
01255         ###
01256 
01263         public function freeResult( $res ) {
01264                 if ( $res instanceof ResultWrapper ) {
01265                         $res = $res->result;
01266                 }
01267                 wfSuppressWarnings();
01268                 $ok = db2_free_result( $res );
01269                 wfRestoreWarnings();
01270                 if ( !$ok ) {
01271                         throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
01272                 }
01273         }
01274 
01280         public function numFields( $res ) {
01281                 if ( $res instanceof ResultWrapper ) {
01282                         $res = $res->result;
01283                 }
01284                 if ( $res instanceof IBM_DB2Result ) {
01285                         $res = $res->getResult();
01286                 }
01287                 return db2_num_fields( $res );
01288         }
01289 
01296         public function fieldName( $res, $n ) {
01297                 if ( $res instanceof ResultWrapper ) {
01298                         $res = $res->result;
01299                 }
01300                 if ( $res instanceof IBM_DB2Result ) {
01301                         $res = $res->getResult();
01302                 }
01303                 return db2_field_name( $res, $n );
01304         }
01305 
01324         public function select( $table, $vars, $conds = '', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
01325         {
01326                 $res = parent::select( $table, $vars, $conds, $fname, $options,
01327                         $join_conds );
01328                 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01329 
01330                 // We must adjust for offset
01331                 if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
01332                         $limit = $options['LIMIT'];
01333                         $offset = $options['OFFSET'];
01334                 }
01335 
01336                 // DB2 does not have a proper num_rows() function yet, so we must emulate
01337                 // DB2 9.5.4 and the corresponding ibm_db2 driver will introduce
01338                 //  a working one
01339                 // TODO: Yay!
01340 
01341                 // we want the count
01342                 $vars2 = array( 'count( * ) as num_rows' );
01343                 // respecting just the limit option
01344                 $options2 = array();
01345                 if ( isset( $options['LIMIT'] ) ) {
01346                         $options2['LIMIT'] = $options['LIMIT'];
01347                 }
01348                 // but don't try to emulate for GROUP BY
01349                 if ( isset( $options['GROUP BY'] ) ) {
01350                         return $res;
01351                 }
01352 
01353                 $res2 = parent::select( $table, $vars2, $conds, $fname, $options2,
01354                         $join_conds );
01355 
01356                 $obj = $this->fetchObject( $res2 );
01357                 $this->mNumRows = $obj->num_rows;
01358 
01359                 return new ResultWrapper( $this, new IBM_DB2Result( $this, $res, $obj->num_rows, $vars, $sql ) );
01360         }
01361 
01372         function makeSelectOptions( $options ) {
01373                 $preLimitTail = $postLimitTail = '';
01374                 $startOpts = '';
01375 
01376                 $noKeyOptions = array();
01377                 foreach ( $options as $key => $option ) {
01378                         if ( is_numeric( $key ) ) {
01379                                 $noKeyOptions[$option] = true;
01380                         }
01381                 }
01382 
01383                 if ( isset( $options['GROUP BY'] ) ) {
01384                         $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
01385                 }
01386                 if ( isset( $options['HAVING'] ) ) {
01387                         $preLimitTail .= " HAVING {$options['HAVING']}";
01388                 }
01389                 if ( isset( $options['ORDER BY'] ) ) {
01390                         $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
01391                 }
01392 
01393                 if ( isset( $noKeyOptions['DISTINCT'] )
01394                         || isset( $noKeyOptions['DISTINCTROW'] ) )
01395                 {
01396                         $startOpts .= 'DISTINCT';
01397                 }
01398 
01399                 return array( $startOpts, '', $preLimitTail, $postLimitTail );
01400         }
01401 
01406         public static function getSoftwareLink() {
01407                 return '[http://www.ibm.com/db2/express/ IBM DB2]';
01408         }
01409 
01416         public function getSearchEngine() {
01417                 return 'SearchIBM_DB2';
01418         }
01419 
01424         public function wasDeadlock() {
01425                 // get SQLSTATE
01426                 $err = $this->lastErrno();
01427                 switch( $err ) {
01428                         // This is literal port of the MySQL logic and may be wrong for DB2
01429                         case '40001':   // sql0911n, Deadlock or timeout, rollback
01430                         case '57011':   // sql0904n, Resource unavailable, no rollback
01431                         case '57033':   // sql0913n, Deadlock or timeout, no rollback
01432                         $this->installPrint( "In a deadlock because of SQLSTATE $err" );
01433                         return true;
01434                 }
01435                 return false;
01436         }
01437 
01443         public function ping() {
01444                 // db2_ping() doesn't exist
01445                 // Emulate
01446                 $this->close();
01447                 $this->openUncataloged( $this->mDBName, $this->mUser,
01448                         $this->mPassword, $this->mServer, $this->mPort );
01449 
01450                 return false;
01451         }
01452         ######################################
01453         # Unimplemented and not applicable
01454         ######################################
01455 
01460         public function fillPreparedArg( $matches ) {
01461                 $this->installPrint( 'Not useful for DB2: fillPreparedArg()' );
01462                 return '';
01463         }
01464 
01465         ######################################
01466         # Reflection
01467         ######################################
01468 
01477         public function indexInfo( $table, $index,
01478                 $fname = 'DatabaseIbm_db2::indexExists' )
01479         {
01480                 $table = $this->tableName( $table );
01481                 $sql = <<<SQL
01482 SELECT name as indexname
01483 FROM sysibm.sysindexes si
01484 WHERE si.name='$index' AND si.tbname='$table'
01485 AND sc.tbcreator='$this->mSchema'
01486 SQL;
01487                 $res = $this->query( $sql, $fname );
01488                 if ( !$res ) {
01489                         return null;
01490                 }
01491                 $row = $this->fetchObject( $res );
01492                 if ( $row != null ) {
01493                         return $row;
01494                 } else {
01495                         return false;
01496                 }
01497         }
01498 
01505         public function fieldInfo( $table, $field ) {
01506                 return IBM_DB2Field::fromText( $this, $table, $field );
01507         }
01508 
01515         public function fieldType( $res, $index ) {
01516                 if ( $res instanceof ResultWrapper ) {
01517                         $res = $res->result;
01518                 }
01519                 if ( $res instanceof IBM_DB2Result ) {
01520                         $res = $res->getResult();
01521                 }
01522                 return db2_field_type( $res, $index );
01523         }
01524 
01532         public function indexUnique ( $table, $index,
01533                 $fname = 'DatabaseIbm_db2::indexUnique' )
01534         {
01535                 $table = $this->tableName( $table );
01536                 $sql = <<<SQL
01537 SELECT si.name as indexname
01538 FROM sysibm.sysindexes si
01539 WHERE si.name='$index' AND si.tbname='$table'
01540 AND sc.tbcreator='$this->mSchema'
01541 AND si.uniquerule IN ( 'U', 'P' )
01542 SQL;
01543                 $res = $this->query( $sql, $fname );
01544                 if ( !$res ) {
01545                         return null;
01546                 }
01547                 if ( $this->fetchObject( $res ) ) {
01548                         return true;
01549                 }
01550                 return false;
01551 
01552         }
01553 
01560         public function textFieldSize( $table, $field ) {
01561                 $table = $this->tableName( $table );
01562                 $sql = <<<SQL
01563 SELECT length as size
01564 FROM sysibm.syscolumns sc
01565 WHERE sc.name='$field' AND sc.tbname='$table'
01566 AND sc.tbcreator='$this->mSchema'
01567 SQL;
01568                 $res = $this->query( $sql );
01569                 $row = $this->fetchObject( $res );
01570                 $size = $row->size;
01571                 return $size;
01572         }
01573 
01579         public function encodeBlob( $b ) {
01580                 return new IBM_DB2Blob( $b );
01581         }
01582 
01588         public function decodeBlob( $b ) {
01589                 return "$b";
01590         }
01591 
01598         public function buildConcat( $stringList ) {
01599                 // || is equivalent to CONCAT
01600                 // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz'
01601                 return implode( ' || ', $stringList );
01602         }
01603 
01609         public function extractUnixEpoch( $column ) {
01610                 // TODO
01611                 // see SpecialAncientpages
01612         }
01613 
01614         ######################################
01615         # Prepared statements
01616         ######################################
01617 
01630         public function prepare( $sql, $func = 'DB2::prepare' ) {
01631                 $stmt = db2_prepare( $this->mConn, $sql, $this->mStmtOptions );
01632                 return $stmt;
01633         }
01634 
01639         public function freePrepared( $prepared ) {
01640                 return db2_free_stmt( $prepared );
01641         }
01642 
01649         public function execute( $prepared, $args = null ) {
01650                 if( !is_array( $args ) ) {
01651                         # Pull the var args
01652                         $args = func_get_args();
01653                         array_shift( $args );
01654                 }
01655                 $res = db2_execute( $prepared, $args );
01656                 if ( !$res ) {
01657                         $this->installPrint( db2_stmt_errormsg() );
01658                 }
01659                 return $res;
01660         }
01661 
01669         public function fillPrepared( $preparedQuery, $args ) {
01670                 reset( $args );
01671                 $this->preparedArgs =& $args;
01672 
01673                 foreach ( $args as $i => $arg ) {
01674                         db2_bind_param( $preparedQuery, $i+1, $args[$i] );
01675                 }
01676 
01677                 return $preparedQuery;
01678         }
01679 
01684         public function setMode( $mode ) {
01685                 $old = $this->mMode;
01686                 $this->mMode = $mode;
01687                 return $old;
01688         }
01689 
01696         function bitNot( $field ) {
01697                 // expecting bit-fields smaller than 4bytes
01698                 return "BITNOT( $field )";
01699         }
01700 
01708         function bitAnd( $fieldLeft, $fieldRight ) {
01709                 return "BITAND( $fieldLeft, $fieldRight )";
01710         }
01711 
01719         function bitOr( $fieldLeft, $fieldRight ) {
01720                 return "BITOR( $fieldLeft, $fieldRight )";
01721         }
01722 }
01723 
01724 class IBM_DB2Helper {
01725         public static function makeArray( $maybeArray ) {
01726                 if ( !is_array( $maybeArray ) ) {
01727                         return array( $maybeArray );
01728                 }
01729 
01730                 return $maybeArray;
01731         }
01732 }