X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2Fdb%2FDatabaseIbm_db2.php;h=ed37939747797fa28e9dbd14001cb6c56f7f3122;hb=184d8beec750c47e2adad1faf3e9db0e95a304ea;hp=ba618c66559ca7ed4a12d78191972e153752f725;hpb=f2c2aca3b4cafd196c3399b45054678ee174c9fb;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php index ba618c6655..ed37939747 100644 --- a/includes/db/DatabaseIbm_db2.php +++ b/includes/db/DatabaseIbm_db2.php @@ -2,7 +2,7 @@ /** * This is the IBM DB2 database abstraction layer. * See maintenance/ibm_db2/README for development notes - * and other specific information + * and other specific information * * @file * @ingroup Database @@ -13,7 +13,7 @@ * This represents a column in a DB2 database * @ingroup Database */ -class IBM_DB2Field { +class IBM_DB2Field implements Field { private $name = ''; private $tablename = ''; private $type = ''; @@ -21,7 +21,7 @@ class IBM_DB2Field { private $max_length = 0; /** - * Builder method for the class + * Builder method for the class * @param $db DatabaseIbm_db2: Database interface * @param $table String: table name * @param $field String: column name @@ -37,13 +37,17 @@ nulls AS attnotnull, length AS attlen FROM sysibm.syscolumns WHERE tbcreator=%s AND tbname=%s AND name=%s; SQL; - $res = $db->query( sprintf( $q, + $res = $db->query( + sprintf( $q, $db->addQuotes( $wgDBmwschema ), $db->addQuotes( $table ), - $db->addQuotes( $field )) ); + $db->addQuotes( $field ) + ) + ); $row = $db->fetchObject( $res ); - if ( !$row ) + if ( !$row ) { return null; + } $n = new IBM_DB2Field; $n->type = $row->typname; $n->nullable = ( $row->attnotnull == 'N' ); @@ -71,7 +75,7 @@ SQL; * Can column be null? * @return bool true or false */ - function nullable() { return $this->nullable; } + function isNullable() { return $this->nullable; } /** * How much can you fit in the column per row? * @return int length @@ -93,11 +97,10 @@ class IBM_DB2Blob { public function getData() { return $this->mData; } - - public function __toString() - { - return $this->mData; - } + + public function __toString() { + return $this->mData; + } } /** @@ -111,9 +114,8 @@ class DatabaseIbm_db2 extends DatabaseBase { protected $mPHPError = false; protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname; - protected $mOut, $mOpened = false; + protected $mOpened = false; - protected $mFailFunction; protected $mTablePrefix; protected $mFlags; protected $mTrxLevel = 0; @@ -122,7 +124,7 @@ class DatabaseIbm_db2 extends DatabaseBase { protected $mFakeSlaveLag = null, $mFakeMaster = false; * */ - + /** Database server port */ protected $mPort = null; /** Schema for tables, stored procedures, triggers */ @@ -135,37 +137,37 @@ class DatabaseIbm_db2 extends DatabaseBase { protected $mAffectedRows = null; /** Number of rows returned by last SELECT */ protected $mNumRows = null; - + /** Connection config options - see constructor */ public $mConnOptions = array(); /** Statement config options -- see constructor */ public $mStmtOptions = array(); - + /** Default schema */ - const USE_GLOBAL = "mediawiki"; - + const USE_GLOBAL = 'get from global'; + /** Option that applies to nothing */ const NONE_OPTION = 0x00; /** Option that applies to connection objects */ const CONN_OPTION = 0x01; /** Option that applies to statement objects */ const STMT_OPTION = 0x02; - + /** Regular operation mode -- minimal debug messages */ const REGULAR_MODE = 'regular'; /** Installation mode -- lots of debug messages */ const INSTALL_MODE = 'install'; - + /** Controls the level of debug message output */ protected $mMode = self::REGULAR_MODE; - + /** Last sequence value used for a primary key */ protected $mInsertId = null; - + ###################################### # Getters and Setters ###################################### - + /** * Returns true if this database supports (and uses) cascading deletes */ @@ -189,7 +191,7 @@ class DatabaseIbm_db2 extends DatabaseBase { function strictIPs() { return true; } - + /** * Returns true if this database uses timestamps rather than integers */ @@ -227,7 +229,7 @@ class DatabaseIbm_db2 extends DatabaseBase { function functionalIndexes() { return true; } - + /** * Returns a unique string representing the wiki on the server */ @@ -242,55 +244,44 @@ class DatabaseIbm_db2 extends DatabaseBase { function getType() { return 'ibm_db2'; } - - ###################################### - # Setup - ###################################### - - + /** - * + * * @param $server String: hostname of database server * @param $user String: username * @param $password String: password * @param $dbName String: database name on the server - * @param $failFunction Callback (optional) * @param $flags Integer: database behaviour flags (optional, unused) * @param $schema String */ - public function DatabaseIbm_db2( $server = false, $user = false, + public function __construct( $server = false, $user = false, $password = false, - $dbName = false, $failFunction = false, $flags = 0, + $dbName = false, $flags = 0, $schema = self::USE_GLOBAL ) { + global $wgDBmwschema; - global $wgOut, $wgDBmwschema; - # Can't get a reference if it hasn't been set yet - if ( !isset( $wgOut ) ) { - $wgOut = null; - } - $this->mOut =& $wgOut; - $this->mFailFunction = $failFunction; - $this->mFlags = DBO_TRX | $flags; - if ( $schema == self::USE_GLOBAL ) { $this->mSchema = $wgDBmwschema; - } - else { + } else { $this->mSchema = $schema; } - + // configure the connection and statement objects + /* + $this->setDB2Option( 'cursor', 'DB2_SCROLLABLE', + self::CONN_OPTION | self::STMT_OPTION ); + */ $this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER', self::CONN_OPTION | self::STMT_OPTION ); $this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON', self::STMT_OPTION ); $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON', self::STMT_OPTION ); - - $this->open( $server, $user, $password, $dbName ); + + parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags ); } - + /** * Enables options only if the ibm_db2 extension version supports them * @param $name String: name of the option in the options array @@ -298,20 +289,19 @@ class DatabaseIbm_db2 extends DatabaseBase { * @param $type Integer: whether this is a Connection or Statement otion */ private function setDB2Option( $name, $const, $type ) { - if ( defined( $const )) { + if ( defined( $const ) ) { if ( $type & self::CONN_OPTION ) { $this->mConnOptions[$name] = constant( $const ); } if ( $type & self::STMT_OPTION ) { $this->mStmtOptions[$name] = constant( $const ); } - } - else { + } else { $this->installPrint( "$const is not defined. ibm_db2 version is likely too low." ); } } - + /** * Outputs debug information in the appropriate place * @param $string String: the relevant debug message @@ -321,41 +311,32 @@ class DatabaseIbm_db2 extends DatabaseBase { if ( $this->mMode == self::INSTALL_MODE ) { print "
  • $string
  • "; flush(); - } + } } - + /** * Opens a database connection and returns it * Closes any existing connection - * @return a fresh connection + * * @param $server String: hostname * @param $user String * @param $password String * @param $dbName String: database name + * @return a fresh connection */ - public function open( $server, $user, $password, $dbName ) - { - // Load the port number - global $wgDBport; + public function open( $server, $user, $password, $dbName ) { 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(); // Cache conn info @@ -364,15 +345,8 @@ ERROR; $this->mUser = $user; $this->mPassword = $password; $this->mDBname = $dbName; - + $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" ); @@ -380,41 +354,43 @@ ERROR; "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() ); } + // 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 ); + $this->mOpened = true; $this->applySchema(); - + wfProfileOut( __METHOD__ ); return $this->mConn; } - + /** * Opens a cataloged database connection, sets mConn */ - protected function openCataloged( $dbName, $user, $password ) - { + protected function openCataloged( $dbName, $user, $password ) { @$this->mConn = db2_pconnect( $dbName, $user, $password ); } - + /** * Opens an uncataloged database connection, sets mConn */ 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;"; + @$this->mConn = db2_pconnect($dsn, "", "", array()); } - + /** * Closes a database connection, if it is open * Returns success, true if already closed @@ -422,34 +398,15 @@ ERROR; public function close() { $this->mOpened = false; if ( $this->mConn ) { - if ($this->trxLevel() > 0) { + if ( $this->trxLevel() > 0 ) { $this->commit(); } return db2_close( $this->mConn ); - } - else { + } else { return true; } } - - /** - * Returns a fresh instance of this class - * - * @param $server String: hostname of database server - * @param $user String: username - * @param $password String - * @param $dbName String: database name on the server - * @param $failFunction Callback (optional) - * @param $flags Integer: database behaviour flags (optional, unused) - * @return DatabaseIbm_db2 object - */ - static function newFromParams( $server, $user, $password, $dbName, - $failFunction = false, $flags = 0 ) - { - return new DatabaseIbm_db2( $server, $user, $password, $dbName, - $failFunction, $flags ); - } - + /** * Retrieves the most current database error * Forces a database rollback @@ -465,10 +422,10 @@ ERROR; //$this->rollback(); return $stmterr; } - + return false; } - + /** * Get the last error number * Return 0 if no error @@ -485,13 +442,13 @@ ERROR; } return 0; } - + /** * Is a database connection open? - * @return + * @return */ public function isOpen() { return $this->mOpened; } - + /** * The DBMS-dependent part of query() * @param $sql String: SQL query. @@ -502,19 +459,25 @@ ERROR; public 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 ) { + if( $ret == false ) { $error = db2_stmt_errormsg(); + $this->installPrint( "
    $sql
    " ); $this->installPrint( $error ); - throw new DBUnexpectedError( $this, 'SQL error: ' + throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( $error ) ); } $this->mLastResult = $ret; - $this->mAffectedRows = null; // Not calculated until asked for + $this->mAffectedRows = null; // Not calculated until asked for return $ret; } - + /** * @return string Version information from the database */ @@ -522,30 +485,33 @@ ERROR; $info = db2_server_info( $this->mConn ); return $info->DBMS_VER; } - + /** * Queries whether a given table exists * @return boolean */ public function tableExists( $table ) { $schema = $this->mSchema; - $sql = <<< EOF -SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST -WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema' -EOF; - $res = $this->query( $sql ); - if ( !$res ) return false; + $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 ); $count = $row[0]; - if ( $count == '1' or $count == 1 ) { + if ( $count == '1' || $count == 1 ) { return true; } - + return false; } - + /** * Fetch the next row from the given result object, in object form. * Fields can be retrieved with $row->fieldname, with fields acting like @@ -569,7 +535,7 @@ EOF; /** * Fetch the next row from the given result object, in associative array - * form. Fields are retrieved with $row['fieldname']. + * form. Fields are retrieved with $row['fieldname']. * * @param $res SQL result object as returned from Database::query(), etc. * @return DB2 row object @@ -579,60 +545,52 @@ 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() ) ); + if ( db2_num_rows( $res ) > 0) { + @$row = db2_fetch_array( $res ); + if ( $this->lastErrno() ) { + throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' + . htmlspecialchars( $this->lastError() ) ); + } + return $row; } - return $row; - } - - /** - * Override if introduced to base Database class - */ - public function initial_setup() { - // do nothing + return false; } - + /** * 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 ) . ""; + print ' FAILED: ' . htmlspecialchars( $res ) . ''; } else { - print " done"; + print ' done'; } $res = $this->sourceFile( "../maintenance/ibm_db2/foreignkeys.sql" ); if ( $res !== true ) { - print " FAILED: " . htmlspecialchars( $res ) . ""; + print ' FAILED: ' . htmlspecialchars( $res ) . ''; } else { - print "
  • Foreign keys done
  • "; + print '
  • Foreign keys done
  • '; } - $res = null; - + // TODO: populate interwiki links - + if ( $this->lastError() ) { $this->installPrint( - "Errors encountered during table creation -- rolled back" ); - $this->installPrint( "Please install again" ); + 'Errors encountered during table creation -- rolled back' ); + $this->installPrint( 'Please install again' ); $this->rollback(); - } - else { + } else { $this->commit(); } - } - catch ( MWException $mwe ) - { + } catch ( MWException $mwe ) { print "
    $mwe

    "; } } @@ -640,47 +598,48 @@ EOF; /** * Escapes strings * Doesn't escape numbers + * * @param $s String: string to escape * @return escaped string */ public function addQuotes( $s ) { //$this->installPrint( "DB2::addQuotes( $s )\n" ); if ( is_null( $s ) ) { - return "NULL"; - } else if ( $s instanceof Blob ) { + return 'NULL'; + } elseif ( $s instanceof Blob ) { return "'" . $s->fetch( $s ) . "'"; - } else if ( $s instanceof IBM_DB2Blob ) { + } elseif ( $s instanceof IBM_DB2Blob ) { return "'" . $this->decodeBlob( $s ) . "'"; } $s = $this->strencode( $s ); if ( is_numeric( $s ) ) { return $s; - } - else { + } else { return "'$s'"; } } - + /** * Verifies that a DB2 column/field type is numeric - * @return bool true if numeric + * * @param $type String: DB2 column type + * @return Boolean: true if numeric */ public function is_numeric_type( $type ) { - switch ( strtoupper( $type )) { - case 'SMALLINT': - case 'INTEGER': - case 'INT': - case 'BIGINT': - case 'DECIMAL': - case 'REAL': - case 'DOUBLE': - case 'DECFLOAT': - return true; + switch ( strtoupper( $type ) ) { + case 'SMALLINT': + case 'INTEGER': + case 'INT': + case 'BIGINT': + case 'DECIMAL': + case 'REAL': + case 'DOUBLE': + case 'DECFLOAT': + return true; } return false; } - + /** * Alias for addQuotes() * @param $s String: string to escape @@ -688,18 +647,18 @@ EOF; */ public function strencode( $s ) { // Bloody useless function - // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a. + // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a. // But also necessary $s = db2_escape_string( $s ); // Wide characters are evil -- some of them look like ' $s = utf8_encode( $s ); // Fix its stupidity $from = array( "\\\\", "\\'", '\\n', '\\t', '\\"', '\\r' ); - $to = array( "\\", "''", "\n", "\t", '"', "\r" ); + $to = array( "\\", "''", "\n", "\t", '"', "\r" ); $s = str_replace( $from, $to, $s ); // DB2 expects '', not \' escaping return $s; } - + /** * Switch into the database schema */ @@ -709,39 +668,39 @@ EOF; $this->begin(); $this->doQuery( "SET SCHEMA = $this->mSchema" ); $this->commit(); - } + } } - + /** * Start a transaction (mandatory) */ public function begin( $fname = 'DatabaseIbm_db2::begin' ) { // BEGIN is implicit for DB2 // However, it requires that AutoCommit be off. - + // 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_OFF ); - + $this->mTrxLevel = 1; } - + /** * End a transaction * Must have a preceding begin() */ public function commit( $fname = 'DatabaseIbm_db2::commit' ) { db2_commit( $this->mConn ); - + // 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->mTrxLevel = 0; } - + /** * Cancel a transaction */ @@ -752,7 +711,7 @@ EOF; db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON ); $this->mTrxLevel = 0; } - + /** * Makes an encoded list of strings from an array * $mode: @@ -766,9 +725,9 @@ EOF; function makeList( $a, $mode = LIST_COMMA ) { if ( !is_array( $a ) ) { throw new DBUnexpectedError( $this, - 'DatabaseBase::makeList called with incorrect parameters' ); + 'DatabaseIbm_db2::makeList called with incorrect parameters' ); } - + // if this is for a prepared UPDATE statement // (this should be promoted to the parent class // once other databases use prepared statements) @@ -778,24 +737,24 @@ EOF; foreach ( $a as $field => $value ) { if ( !$first ) { $list .= ", $field = ?"; - } - else { + } else { $list .= "$field = ?"; $first = false; } } $list .= ''; - + return $list; } - + // otherwise, call the usual function return parent::makeList( $a, $mode ); } - + /** * Construct a LIMIT query with optional offset * This is used for query pages + * * @param $sql string SQL query we will append the limit too * @param $limit integer the SQL limit * @param $offset integer the SQL offset (default false) @@ -808,30 +767,32 @@ EOF; if( $offset ) { if ( stripos( $sql, 'where' ) === false ) { return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )"; - } - else { + } else { return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )"; } } return "$sql FETCH FIRST $limit ROWS ONLY "; } - + /** * Handle reserved keyword replacement in table names - * @return + * * @param $name Object + * @param $name Boolean + * @return String */ - public function tableName( $name ) { + public function tableName( $name, $quoted = true ) { // we want maximum compatibility with MySQL schema return $name; } - + /** * Generates a timestamp in an insertable format - * @return string timestamp value + * * @param $ts timestamp + * @return String: timestamp value */ - public function timestamp( $ts=0 ) { + public function timestamp( $ts = 0 ) { // TS_MW cannot be easily distinguished from an integer return wfTimestamp( TS_DB2, $ts ); } @@ -855,7 +816,7 @@ EOF; */ return null; } - + /** * This must be called after nextSequenceVal * @return Last sequence value used as a primary key @@ -863,10 +824,11 @@ EOF; public function insertId() { return $this->mInsertId; } - + /** * Updates the mInsertId property with the value of the last insert * into a generated column + * * @param $table String: sanitized table name * @param $primaryKey Mixed: string name of the primary key * @param $stmt Resource: prepared statement resource @@ -877,7 +839,7 @@ EOF; $this->mInsertId = db2_last_insert_id( $this->mConn ); } } - + /** * INSERT wrapper, inserts an array into a table * @@ -905,7 +867,7 @@ EOF; if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) { $args = array( $args ); } - + // prevent insertion of NULL into primary key columns list( $args, $primaryKeys ) = $this->removeNullPrimaryKeys( $table, $args ); // if there's only one primary key @@ -914,7 +876,7 @@ EOF; if ( count( $primaryKeys ) == 1 ) { $primaryKey = $primaryKeys[0]; } - + // get column names $keys = array_keys( $args[0] ); $key_count = count( $keys ); @@ -925,7 +887,7 @@ EOF; // assume success $res = true; // If we are not in a transaction, we need to be for savepoint trickery - if ( ! $this->mTrxLevel ) { + if ( !$this->mTrxLevel ) { $this->begin(); } @@ -935,11 +897,11 @@ 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 $this->begin(); @@ -950,48 +912,45 @@ EOF; // insert each row into the database $res = $res & $this->execute( $stmt, $row ); if ( !$res ) { - $this->installPrint( "Last error:" ); + $this->installPrint( 'Last error:' ); $this->installPrint( $this->lastError() ); } // get the last inserted value into a generated column $this->calcInsertId( $table, $primaryKey, $stmt ); } - } - else { + } else { $olde = error_reporting( 0 ); // For future use, we may want to track the number of actual inserts // Right now, insert (all writes) simply return true/false $numrowsinserted = 0; - + // always return true $res = true; - + foreach ( $args as $row ) { $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:" ); + $this->installPrint( 'Last error:' ); $this->installPrint( $this->lastError() ); } // get the last inserted value into a generated column $this->calcInsertId( $table, $primaryKey, $stmt ); - + $errNum = $this->lastErrno(); if ( $errNum ) { db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore", $this->mStmtOptions ); - } - else { + } else { db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore", $this->mStmtOptions ); $numrowsinserted++; } } - + $olde = error_reporting( $olde ); // Set the affected row count for the whole operation $this->mAffectedRows = $numrowsinserted; @@ -999,34 +958,40 @@ EOF; // commit either way $this->commit(); $this->freePrepared( $stmt ); - + return $res; } - + /** * Given a table name and a hash of columns with values * Removes primary key columns from the hash where the value is NULL - * + * * @param $table String: name of the table * @param $args Array of hashes of column names with values * @return Array: tuple( filtered array of columns, array of primary keys ) */ 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 ) { - foreach ( $keys as $ki => $key ) { + foreach ( $keys as $key ) { if ( $row[$key] == null ) { unset( $row[$key] ); } @@ -1036,7 +1001,7 @@ EOF; // return modified hash return array( $args, $keys ); } - + /** * UPDATE wrapper, takes a condition array and a SET array * @@ -1049,7 +1014,7 @@ EOF; * more of IGNORE, LOW_PRIORITY * @return Boolean */ - public function update( $table, $values, $conds, $fname = 'Database::update', + public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update', $options = array() ) { $table = $this->tableName( $table ); @@ -1060,24 +1025,24 @@ EOF; $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); } $stmt = $this->prepare( $sql ); - $this->installPrint( "UPDATE: " . print_r( $values, TRUE )); + $this->installPrint( 'UPDATE: ' . print_r( $values, true ) ); // assuming for now that an array with string keys will work // if not, convert to simple array first $result = $this->execute( $stmt, $values ); $this->freePrepared( $stmt ); - + return $result; } - + /** * DELETE query wrapper * * Use $conds == "*" to delete all rows */ - public function delete( $table, $conds, $fname = 'Database::delete' ) { + public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) { if ( !$conds ) { throw new DBUnexpectedError( $this, - 'Database::delete() called with no conditions' ); + 'DatabaseIbm_db2::delete() called with no conditions' ); } $table = $this->tableName( $table ); $sql = "DELETE FROM $table"; @@ -1085,10 +1050,10 @@ EOF; $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); } $result = $this->query( $sql, $fname ); - + return $result; } - + /** * Returns the number of rows affected by the last query or 0 * @return Integer: the number of rows affected by the last query @@ -1098,11 +1063,12 @@ EOF; // Forced result for simulated queries return $this->mAffectedRows; } - if( empty( $this->mLastResult ) ) + if( empty( $this->mLastResult ) ) { return 0; + } return db2_num_rows( $this->mLastResult ); } - + /** * Simulates REPLACE with a DELETE followed by INSERT * @param $table Object @@ -1133,7 +1099,7 @@ EOF; foreach ( $uniqueIndexes as $index ) { if ( $first ) { $first = false; - $sql .= "( "; + $sql .= '( '; } else { $sql .= ' ) OR ( '; } @@ -1156,13 +1122,10 @@ EOF; } # 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 ); + $this->insert($table, $row); } } - + /** * Returns the number of rows in the result set * Has to be called right after the corresponding select query @@ -1173,14 +1136,14 @@ EOF; if ( $res instanceof ResultWrapper ) { $res = $res->result; } + if ( $this->mNumRows ) { return $this->mNumRows; - } - else { + } else { return 0; } } - + /** * Moves the row pointer of the result set * @param $res Object: result set @@ -1193,11 +1156,11 @@ EOF; } return db2_fetch_row( $res, $row ); } - + ### - # Fix notices in Block.php + # Fix notices in Block.php ### - + /** * Frees memory associated with a statement resource * @param $res Object: statement resource to free @@ -1208,10 +1171,10 @@ EOF; $res = $res->result; } if ( !@db2_free_result( $res ) ) { - throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" ); + throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" ); } } - + /** * Returns the number of columns in a resource * @param $res Object: statement resource @@ -1223,7 +1186,7 @@ EOF; } return db2_num_fields( $res ); } - + /** * Returns the nth column name * @param $res Object: statement resource @@ -1236,7 +1199,7 @@ EOF; } return db2_field_name( $res, $n ); } - + /** * SELECT wrapper * @@ -1255,25 +1218,22 @@ EOF; * @return Mixed: database result resource for fetch functions or false * on failure */ - public function select( $table, $vars, $conds='', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() ) + public function select( $table, $vars, $conds = '', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() ) { $res = parent::select( $table, $vars, $conds, $fname, $options, $join_conds ); - + // We must adjust for offset - if ( isset( $options['LIMIT'] ) ) { - if ( isset ( $options['OFFSET'] ) ) { - $limit = $options['LIMIT']; - $offset = $options['OFFSET']; - } + if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) { + $limit = $options['LIMIT']; + $offset = $options['OFFSET']; } - - + // DB2 does not have a proper num_rows() function yet, so we must emulate // DB2 9.5.4 and the corresponding ibm_db2 driver will introduce // a working one // TODO: Yay! - + // we want the count $vars2 = array( 'count( * ) as num_rows' ); // respecting just the limit option @@ -1285,20 +1245,19 @@ EOF; if ( isset( $options['GROUP BY'] ) ) { return $res; } - + $res2 = parent::select( $table, $vars2, $conds, $fname, $options2, $join_conds ); $obj = $this->fetchObject( $res2 ); $this->mNumRows = $obj->num_rows; - - + return $res; } - + /** * Handles ordering, grouping, and having options ('GROUP BY' => colname) * Has limited support for per-column options (colnum => 'DISTINCT') - * + * * @private * * @param $options Associative array of options to be turned into @@ -1325,32 +1284,32 @@ EOF; if ( isset( $options['ORDER BY'] ) ) { $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; } - + if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { $startOpts .= 'DISTINCT'; } - + return array( $startOpts, '', $preLimitTail, $postLimitTail ); } - + /** * Returns link to IBM DB2 free download - * @return string wikitext of a link to the server software's web site + * @return String: wikitext of a link to the server software's web site */ public static function getSoftwareLink() { - return "[http://www.ibm.com/db2/express/ IBM DB2]"; + return '[http://www.ibm.com/db2/express/ IBM DB2]'; } - + /** * Get search engine class. All subclasses of this * need to implement this if they wish to use searching. - * + * * @return String */ public function getSearchEngine() { - return "SearchIBM_DB2"; + return 'SearchIBM_DB2'; } /** @@ -1370,7 +1329,7 @@ EOF; } return false; } - + /** * Ping the server and try to reconnect if it there is no connection * The connection may be closed and reopened while this happens @@ -1382,7 +1341,7 @@ EOF; $this->close(); $this->mConn = $this->openUncataloged( $this->mDBName, $this->mUser, $this->mPassword, $this->mServer, $this->mPort ); - + return false; } ###################################### @@ -1392,19 +1351,19 @@ EOF; * Not implemented * @return string '' */ - public function getStatus( $which="%" ) { - $this->installPrint( 'Not implemented for DB2: getStatus()' ); - return ''; + public function getStatus( $which = '%' ) { + $this->installPrint( 'Not implemented for DB2: getStatus()' ); + return ''; } /** * Not implemented * @return string $sql - */ + */ public function limitResultForUpdate( $sql, $num ) { $this->installPrint( 'Not implemented for DB2: limitResultForUpdate()' ); return $sql; } - + /** * Only useful with fake prepare like in base Database class * @return string @@ -1413,11 +1372,11 @@ EOF; $this->installPrint( 'Not useful for DB2: fillPreparedArg()' ); return ''; } - + ###################################### # Reflection ###################################### - + /** * Returns information about an index * If errors are explicitly ignored, returns NULL on failure @@ -1443,10 +1402,11 @@ SQL; $row = $this->fetchObject( $res ); if ( $row != null ) { return $row; + } else { + return false; } - else return false; } - + /** * Returns an information object on a table column * @param $table String: table name @@ -1456,7 +1416,7 @@ SQL; public function fieldInfo( $table, $field ) { return IBM_DB2Field::fromText( $this, $table, $field ); } - + /** * db2_field_type() wrapper * @param $res Object: result of executed statement @@ -1469,7 +1429,7 @@ SQL; } return db2_field_type( $res, $index ); } - + /** * Verifies that an index was created as unique * @param $table String: table name @@ -1478,7 +1438,7 @@ SQL; * @return Bool */ public function indexUnique ( $table, $index, - $fname = 'Database::indexUnique' ) + $fname = 'DatabaseIbm_db2::indexUnique' ) { $table = $this->tableName( $table ); $sql = <<size; return $size; } - + /** * DELETE where the condition is a join * @param $delTable String: deleting from this table @@ -1532,8 +1492,8 @@ SQL; $conds, $fname = "DatabaseIbm_db2::deleteJoin" ) { if ( !$conds ) { - throw new DBUnexpectedError( $this, - 'Database::deleteJoin() called with empty $conds' ); + throw new DBUnexpectedError( $this, + 'DatabaseIbm_db2::deleteJoin() called with empty $conds' ); } $delTable = $this->tableName( $delTable ); @@ -1541,8 +1501,8 @@ SQL; $sql = <<makeList( $conds, LIST_AND ); @@ -1560,7 +1520,7 @@ SQL; public function encodeBlob( $b ) { return new IBM_DB2Blob( $b ); } - + /** * Description is left as an exercise for the reader * @param $b IBM_DB2Blob: data to be decoded @@ -1569,7 +1529,7 @@ SQL; public function decodeBlob( $b ) { return "$b"; } - + /** * Convert into a list of string being concatenated * @param $stringList Array: strings that need to be joined together @@ -1581,7 +1541,7 @@ SQL; // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz' return implode( ' || ', $stringList ); } - + /** * Generates the SQL required to convert a DB2 timestamp into a Unix epoch * @param $column String: name of timestamp column @@ -1591,11 +1551,11 @@ SQL; // TODO // see SpecialAncientpages } - + ###################################### # Prepared statements ###################################### - + /** * Intended to be compatible with the PEAR::DB wrapper functions. * http://pear.php.net/manual/en/package.database.db.intro-execute.php @@ -1669,23 +1629,23 @@ SQL; public function fillPrepared( $preparedQuery, $args ) { reset( $args ); $this->preparedArgs =& $args; - + foreach ( $args as $i => $arg ) { db2_bind_param( $preparedQuery, $i+1, $args[$i] ); } - + return $preparedQuery; } - + /** * Switches module between regular and install modes */ public function setMode( $mode ) { - $old = $this->mMode; + $old = $this->mMode; $this->mMode = $mode; return $old; } - + /** * Bitwise negation of a column or value in SQL * Same as (~field) in C @@ -1693,7 +1653,7 @@ SQL; * @return String */ function bitNot( $field ) { - //expecting bit-fields smaller than 4bytes + // expecting bit-fields smaller than 4bytes return "BITNOT( $field )"; } @@ -1725,7 +1685,7 @@ class IBM_DB2Helper { if ( !is_array( $maybeArray ) ) { return array( $maybeArray ); } - + return $maybeArray; } }