X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fdb%2FDatabaseIbm_db2.php;h=fed3b12e1177ea5d5cb73229cdf4cd138da8a07a;hb=fc6bc233be34a7e401d4beb74233b8d363dfc78e;hp=a2a2be0b4e87de28d26729599592a5f27643670d;hpb=6950195767856c518d82a31c52c758d1350dc259;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php index a2a2be0b4e..fed3b12e11 100644 --- a/includes/db/DatabaseIbm_db2.php +++ b/includes/db/DatabaseIbm_db2.php @@ -103,6 +103,147 @@ class IBM_DB2Blob { } } +/** + * Wrapper to address lack of certain operations in the DB2 driver + * ( seek, num_rows ) + * @ingroup Database + * @since 1.19 + */ +class IBM_DB2Result{ + private $db; + private $result; + private $num_rows; + private $current_pos; + private $columns = array(); + private $sql; + + private $resultSet = array(); + private $loadedLines = 0; + + /** + * Construct and initialize a wrapper for DB2 query results + * @param $db Database + * @param $result Object + * @param $num_rows Integer + * @param $sql String + * @param $columns Array + */ + public function __construct( $db, $result, $num_rows, $sql, $columns ){ + $this->db = $db; + + if( $result instanceof ResultWrapper ){ + $this->result = $result->result; + } + else{ + $this->result = $result; + } + + $this->num_rows = $num_rows; + $this->current_pos = 0; + if ( $this->num_rows > 0 ) { + // Make a lower-case list of the column names + // By default, DB2 column names are capitalized + // while MySQL column names are lowercase + + // Is there a reasonable maximum value for $i? + // Setting to 2048 to prevent an infinite loop + for( $i = 0; $i < 2048; $i++ ) { + $name = db2_field_name( $this->result, $i ); + if ( $name != false ) { + continue; + } + else { + return false; + } + + $this->columns[$i] = strtolower( $name ); + } + } + + $this->sql = $sql; + } + + /** + * Unwrap the DB2 query results + * @return mixed Object on success, false on failure + */ + public function getResult() { + if ( $this->result ) { + return $this->result; + } + else return false; + } + + /** + * Get the number of rows in the result set + * @return integer + */ + public function getNum_rows() { + return $this->num_rows; + } + + /** + * Return a row from the result set in object format + * @return mixed Object on success, false on failure. + */ + public function fetchObject() { + if ( $this->result + && $this->num_rows > 0 + && $this->current_pos >= 0 + && $this->current_pos < $this->num_rows ) + { + $row = $this->fetchRow(); + $ret = new stdClass(); + + foreach ( $row as $k => $v ) { + $lc = $this->columns[$k]; + $ret->$lc = $v; + } + return $ret; + } + return false; + } + + /** + * Return a row form the result set in array format + * @return mixed Array on success, false on failure + * @throws DBUnexpectedError + */ + public function fetchRow(){ + if ( $this->result + && $this->num_rows > 0 + && $this->current_pos >= 0 + && $this->current_pos < $this->num_rows ) + { + if ( $this->loadedLines <= $this->current_pos ) { + $row = db2_fetch_array( $this->result ); + $this->resultSet[$this->loadedLines++] = $row; + if ( $this->db->lastErrno() ) { + throw new DBUnexpectedError( $this->db, 'Error in fetchRow(): ' + . htmlspecialchars( $this->db->lastError() ) ); + } + } + + if ( $this->loadedLines > $this->current_pos ){ + return $this->resultSet[$this->current_pos++]; + } + + } + return false; + } + + /** + * Free a DB2 result object + * @throws DBUnexpectedError + */ + public function freeResult(){ + unset( $this->resultSet ); + if ( !@db2_free_result( $this->result ) ) { + throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" ); + } + } +} + /** * Primary database interface * @ingroup Database @@ -137,6 +278,8 @@ class DatabaseIbm_db2 extends DatabaseBase { protected $mAffectedRows = null; /** Number of rows returned by last SELECT */ protected $mNumRows = null; + /** Current row number on the cursor of the last SELECT */ + protected $currentRow = 0; /** Connection config options - see constructor */ public $mConnOptions = array(); @@ -144,7 +287,7 @@ class DatabaseIbm_db2 extends DatabaseBase { public $mStmtOptions = array(); /** Default schema */ - const USE_GLOBAL = 'mediawiki'; + const USE_GLOBAL = 'get from global'; /** Option that applies to nothing */ const NONE_OPTION = 0x00; @@ -233,7 +376,7 @@ class DatabaseIbm_db2 extends DatabaseBase { /** * Returns a unique string representing the wiki on the server */ - function getWikiID() { + public function getWikiID() { if( $this->mSchema ) { return "{$this->mDBname}-{$this->mSchema}"; } else { @@ -241,14 +384,21 @@ class DatabaseIbm_db2 extends DatabaseBase { } } - function getType() { + /** + * Returns the database software identifieir + * @return string + */ + public function getType() { return 'ibm_db2'; } - ###################################### - # Setup - ###################################### - + /** + * Returns the database connection object + * @return Object + */ + public function getDb(){ + return $this->mConn; + } /** * @@ -279,8 +429,7 @@ class DatabaseIbm_db2 extends DatabaseBase { self::STMT_OPTION ); $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON', self::STMT_OPTION ); - - parent::__construct( $server, $user, $password, $dbName, $flags ); + parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags ); } /** @@ -326,26 +475,17 @@ class DatabaseIbm_db2 extends DatabaseBase { * @return a fresh connection */ public function open( $server, $user, $password, $dbName ) { - // Load the port number - global $wgDBport; wfProfileIn( __METHOD__ ); - // Load IBM DB2 driver if missing + # Load IBM DB2 driver if missing wfDl( 'ibm_db2' ); - // Test for IBM DB2 support, to avoid suppressed fatal error + # Test for IBM DB2 support, to avoid suppressed fatal error if ( !function_exists( 'db2_connect' ) ) { - $error = <<installPrint( $error ); - $this->reportConnectionError( $error ); + throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" ); } - if ( strlen( $user ) < 1 ) { - return null; - } + global $wgDBport; // Close existing connection $this->close(); @@ -358,22 +498,24 @@ ERROR; $this->openUncataloged( $dbName, $user, $password, $server, $port ); - // Apply connection config - db2_set_option( $this->mConn, $this->mConnOptions, 1 ); - // Some MediaWiki code is still transaction-less (?). - // The strategy is to keep AutoCommit on for that code - // but switch it off whenever a transaction is begun. - db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON ); - if ( !$this->mConn ) { $this->installPrint( "DB connection error\n" ); $this->installPrint( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" ); $this->installPrint( $this->lastError() . "\n" ); - return null; + wfProfileOut( __METHOD__ ); + wfDebug( "DB connection error\n" ); + wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" ); + wfDebug( $this->lastError() . "\n" ); + throw new DBConnectionError( $this, $this->lastError() ); } + // Some MediaWiki code is still transaction-less (?). + // The strategy is to keep AutoCommit on for that code + // but switch it off whenever a transaction is begun. + db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON ); + $this->mOpened = true; $this->applySchema(); @@ -385,7 +527,9 @@ ERROR; * Opens a cataloged database connection, sets mConn */ protected function openCataloged( $dbName, $user, $password ) { - @$this->mConn = db2_pconnect( $dbName, $user, $password ); + wfSuppressWarnings(); + $this->mConn = db2_pconnect( $dbName, $user, $password ); + wfRestoreWarnings(); } /** @@ -393,16 +537,10 @@ ERROR; */ protected function openUncataloged( $dbName, $user, $password, $server, $port ) { - $str = "DRIVER={IBM DB2 ODBC DRIVER};"; - $str .= "DATABASE=$dbName;"; - $str .= "HOSTNAME=$server;"; - // port was formerly validated to not be 0 - $str .= "PORT=$port;"; - $str .= "PROTOCOL=TCPIP;"; - $str .= "UID=$user;"; - $str .= "PWD=$password;"; - - @$this->mConn = db2_pconnect( $str, $user, $password ); + $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;"; + wfSuppressWarnings(); + $this->mConn = db2_pconnect( $dsn, "", "", array() ); + wfRestoreWarnings(); } /** @@ -467,15 +605,19 @@ ERROR; * The DBMS-dependent part of query() * @param $sql String: SQL query. * @return object Result object for fetch functions or false on failure - * @access private */ - /*private*/ - public function doQuery( $sql ) { + protected function doQuery( $sql ) { $this->applySchema(); + // Needed to handle any UTF-8 encoding issues in the raw sql + // Note that we fully support prepared statements for DB2 + // prepare() and execute() should be used instead of doQuery() whenever possible + $sql = utf8_decode( $sql ); + $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions ); if( $ret == false ) { $error = db2_stmt_errormsg(); + $this->installPrint( "
$sql
" ); $this->installPrint( $error ); throw new DBUnexpectedError( $this, 'SQL error: ' @@ -498,19 +640,20 @@ ERROR; * Queries whether a given table exists * @return boolean */ - public function tableExists( $table ) { + public function tableExists( $table, $fname = __METHOD__ ) { $schema = $this->mSchema; - $sql = <<< EOF -SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST -WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema' -EOF; + + $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" . + strtoupper( $table ) . + "' AND ST.CREATOR = '" . + strtoupper( $schema ) . "'"; $res = $this->query( $sql ); if ( !$res ) { return false; } // If the table exists, there should be one of it - @$row = $this->fetchRow( $res ); + $row = $this->fetchRow( $res ); $count = $row[0]; if ( $count == '1' || $count == 1 ) { return true; @@ -532,7 +675,9 @@ EOF; if ( $res instanceof ResultWrapper ) { $res = $res->result; } - @$row = db2_fetch_object( $res ); + wfSuppressWarnings(); + $row = db2_fetch_object( $res ); + wfRestoreWarnings(); if( $this->lastErrno() ) { throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) ); @@ -552,51 +697,17 @@ EOF; if ( $res instanceof ResultWrapper ) { $res = $res->result; } - @$row = db2_fetch_array( $res ); - if ( $this->lastErrno() ) { - throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' - . htmlspecialchars( $this->lastError() ) ); - } - return $row; - } - - /** - * Create tables, stored procedures, and so on - */ - public function setup_database() { - try { - // TODO: switch to root login if available - - // Switch into the correct namespace - $this->applySchema(); - $this->begin(); - - $res = $this->sourceFile( "../maintenance/ibm_db2/tables.sql" ); - if ( $res !== true ) { - print ' FAILED: ' . htmlspecialchars( $res ) . ''; - } else { - print ' done'; + if ( db2_num_rows( $res ) > 0) { + wfSuppressWarnings(); + $row = db2_fetch_array( $res ); + wfRestoreWarnings(); + if ( $this->lastErrno() ) { + throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' + . htmlspecialchars( $this->lastError() ) ); } - $res = $this->sourceFile( "../maintenance/ibm_db2/foreignkeys.sql" ); - if ( $res !== true ) { - print ' FAILED: ' . htmlspecialchars( $res ) . ''; - } else { - print '
  • Foreign keys done
  • '; - } - - // TODO: populate interwiki links - - if ( $this->lastError() ) { - $this->installPrint( - 'Errors encountered during table creation -- rolled back' ); - $this->installPrint( 'Please install again' ); - $this->rollback(); - } else { - $this->commit(); - } - } catch ( MWException $mwe ) { - print "
    $mwe

    "; + return $row; } + return false; } /** @@ -782,9 +893,10 @@ EOF; * Handle reserved keyword replacement in table names * * @param $name Object + * @param $format String Ignored parameter Default 'quoted'Boolean * @return String */ - public function tableName( $name ) { + public function tableName( $name, $format = 'quoted' ) { // we want maximum compatibility with MySQL schema return $name; } @@ -900,9 +1012,9 @@ EOF; } else { $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )'; } - //$this->installPrint( "Preparing the following SQL:" ); - //$this->installPrint( "$sql" ); - //$this->installPrint( print_r( $args, true )); + $this->installPrint( "Preparing the following SQL:" ); + $this->installPrint( "$sql" ); + $this->installPrint( print_r( $args, true )); $stmt = $this->prepare( $sql ); // start a transaction/enter transaction mode @@ -934,7 +1046,7 @@ EOF; $overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS"; db2_exec( $this->mConn, $overhead, $this->mStmtOptions ); - $this->execute( $stmt, $row ); + $res2 = $this->execute( $stmt, $row ); if ( !$res2 ) { $this->installPrint( 'Last error:' ); @@ -975,18 +1087,22 @@ EOF; */ private function removeNullPrimaryKeys( $table, $args ) { $schema = $this->mSchema; + // find out the primary keys - $keyres = db2_primary_keys( $this->mConn, null, strtoupper( $schema ), - strtoupper( $table ) - ); + $keyres = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '" + . strtoupper( $table ) + . "' AND TBCREATOR = '" + . strtoupper( $schema ) + . "' AND KEYSEQ > 0" ); + $keys = array(); for ( - $row = $this->fetchObject( $keyres ); + $row = $this->fetchRow( $keyres ); $row != null; - $row = $this->fetchObject( $keyres ) + $row = $this->fetchRow( $keyres ) ) { - $keys[] = strtolower( $row->column_name ); + $keys[] = strtolower( $row[0] ); } // remove primary keys foreach ( $args as $ai => $row ) { @@ -1068,66 +1184,6 @@ EOF; return db2_num_rows( $this->mLastResult ); } - /** - * Simulates REPLACE with a DELETE followed by INSERT - * @param $table Object - * @param $uniqueIndexes Array consisting of indexes and arrays of indexes - * @param $rows Array: rows to insert - * @param $fname String: name of the function for profiling - * @return nothing - */ - function replace( $table, $uniqueIndexes, $rows, - $fname = 'DatabaseIbm_db2::replace' ) - { - $table = $this->tableName( $table ); - - if ( count( $rows )==0 ) { - return; - } - - # Single row case - if ( !is_array( reset( $rows ) ) ) { - $rows = array( $rows ); - } - - foreach( $rows as $row ) { - # Delete rows which collide - if ( $uniqueIndexes ) { - $sql = "DELETE FROM $table WHERE "; - $first = true; - foreach ( $uniqueIndexes as $index ) { - if ( $first ) { - $first = false; - $sql .= '( '; - } else { - $sql .= ' ) OR ( '; - } - if ( is_array( $index ) ) { - $first2 = true; - foreach ( $index as $col ) { - if ( $first2 ) { - $first2 = false; - } else { - $sql .= ' AND '; - } - $sql .= $col . '=' . $this->addQuotes( $row[$col] ); - } - } else { - $sql .= $index . '=' . $this->addQuotes( $row[$index] ); - } - } - $sql .= ' )'; - $this->query( $sql, $fname ); - } - - # Now insert the row - $sql = "INSERT INTO $table ( " - . $this->makeList( array_keys( $row ), LIST_NAMES ) - .' ) VALUES ( ' . $this->makeList( $row, LIST_COMMA ) . ' )'; - $this->query( $sql, $fname ); - } - } - /** * Returns the number of rows in the result set * Has to be called right after the corresponding select query @@ -1138,6 +1194,7 @@ EOF; if ( $res instanceof ResultWrapper ) { $res = $res->result; } + if ( $this->mNumRows ) { return $this->mNumRows; } else { @@ -1153,9 +1210,13 @@ EOF; */ public function dataSeek( $res, $row ) { if ( $res instanceof ResultWrapper ) { - $res = $res->result; + return $res = $res->result; } - return db2_fetch_row( $res, $row ); + if ( $res instanceof IBM_DB2Result ) { + return $res->dataSeek( $row ); + } + wfDebug( "dataSeek operation in DB2 database\n" ); + return false; } ### @@ -1171,7 +1232,10 @@ EOF; if ( $res instanceof ResultWrapper ) { $res = $res->result; } - if ( !@db2_free_result( $res ) ) { + wfSuppressWarnings(); + $ok = db2_free_result( $res ); + wfRestoreWarnings(); + if ( !$ok ) { throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" ); } } @@ -1185,6 +1249,9 @@ EOF; if ( $res instanceof ResultWrapper ) { $res = $res->result; } + if ( $res instanceof IBM_DB2Result ) { + $res = $res->getResult(); + } return db2_num_fields( $res ); } @@ -1198,6 +1265,9 @@ EOF; if ( $res instanceof ResultWrapper ) { $res = $res->result; } + if ( $res instanceof IBM_DB2Result ) { + $res = $res->getResult(); + } return db2_field_name( $res, $n ); } @@ -1210,7 +1280,7 @@ EOF; * @param $fname String: calling function name (use __METHOD__) * for logs/profiling * @param $options Associative array of options - * (e.g. array('GROUP BY' => 'page_title')), + * (e.g. array( 'GROUP BY' => 'page_title' )), * see Database::makeSelectOptions code for list of * supported stuff * @param $join_conds Associative array of table join conditions (optional) @@ -1223,6 +1293,7 @@ EOF; { $res = parent::select( $table, $vars, $conds, $fname, $options, $join_conds ); + $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); // We must adjust for offset if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) { @@ -1249,10 +1320,11 @@ EOF; $res2 = parent::select( $table, $vars2, $conds, $fname, $options2, $join_conds ); + $obj = $this->fetchObject( $res2 ); $this->mNumRows = $obj->num_rows; - - return $res; + + return new ResultWrapper( $this, new IBM_DB2Result( $this, $res, $obj->num_rows, $vars, $sql ) ); } /** @@ -1348,14 +1420,6 @@ EOF; ###################################### # Unimplemented and not applicable ###################################### - /** - * Not implemented - * @return string '' - */ - public function getStatus( $which = '%' ) { - $this->installPrint( 'Not implemented for DB2: getStatus()' ); - return ''; - } /** * Not implemented * @return string $sql @@ -1428,6 +1492,9 @@ SQL; if ( $res instanceof ResultWrapper ) { $res = $res->result; } + if ( $res instanceof IBM_DB2Result ) { + $res = $res->getResult(); + } return db2_field_type( $res, $index ); } @@ -1480,39 +1547,6 @@ SQL; return $size; } - /** - * DELETE where the condition is a join - * @param $delTable String: deleting from this table - * @param $joinTable String: using data from this table - * @param $delVar String: variable in deleteable table - * @param $joinVar String: variable in data table - * @param $conds Array: conditionals for join table - * @param $fname String: function name for profiling - */ - public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, - $conds, $fname = "DatabaseIbm_db2::deleteJoin" ) - { - if ( !$conds ) { - throw new DBUnexpectedError( $this, - 'DatabaseIbm_db2::deleteJoin() called with empty $conds' ); - } - - $delTable = $this->tableName( $delTable ); - $joinTable = $this->tableName( $joinTable ); - $sql = <<makeList( $conds, LIST_AND ); - } - $sql .= ' )'; - - $this->query( $sql, $fname ); - } - /** * Description is left as an exercise for the reader * @param $b Mixed: data to be encoded