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