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