MediaWiki
REL1_20
|
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 }