X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2Fdb%2FDatabaseIbm_db2.php;h=ed37939747797fa28e9dbd14001cb6c56f7f3122;hb=184d8beec750c47e2adad1faf3e9db0e95a304ea;hp=60cd817de936815c9e2a57a57941797400ef017c;hpb=e0cb9ef9b6171573c30abec3d30f782b5a2b0baa;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php index 60cd817de9..ed37939747 100644 --- a/includes/db/DatabaseIbm_db2.php +++ b/includes/db/DatabaseIbm_db2.php @@ -1,7 +1,8 @@ query(sprintf($q, - $db->addQuotes($wgDBmwschema), - $db->addQuotes($table), - $db->addQuotes($field))); - $row = $db->fetchObject($res); - if (!$row) + $res = $db->query( + sprintf( $q, + $db->addQuotes( $wgDBmwschema ), + $db->addQuotes( $table ), + $db->addQuotes( $field ) + ) + ); + $row = $db->fetchObject( $res ); + if ( !$row ) { return null; + } $n = new IBM_DB2Field; $n->type = $row->typname; - $n->nullable = ($row->attnotnull == 'N'); + $n->nullable = ( $row->attnotnull == 'N' ); $n->name = $field; $n->tablename = $table; $n->max_length = $row->attlen; @@ -70,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 @@ -85,18 +90,17 @@ SQL; class IBM_DB2Blob { private $mData; - public function __construct($data) { + public function __construct( $data ) { $this->mData = $data; } public function getData() { return $this->mData; } - - public function __toString() - { - return $this->mData; - } + + public function __toString() { + return $this->mData; + } } /** @@ -110,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; @@ -121,205 +124,50 @@ class DatabaseIbm_db2 extends DatabaseBase { protected $mFakeSlaveLag = null, $mFakeMaster = false; * */ - - /// Server port for uncataloged connections + + /** Database server port */ protected $mPort = null; - /// Whether connection is cataloged - protected $mCataloged = null; - /// Schema for tables, stored procedures, triggers + /** Schema for tables, stored procedures, triggers */ protected $mSchema = null; - /// Whether the schema has been applied in this session + /** Whether the schema has been applied in this session */ protected $mSchemaSet = false; - /// Result of last query + /** Result of last query */ protected $mLastResult = null; - /// Number of rows affected by last INSERT/UPDATE/DELETE + /** Number of rows affected by last INSERT/UPDATE/DELETE */ protected $mAffectedRows = null; - /// Number of rows returned by last SELECT + /** Number of rows returned by last SELECT */ protected $mNumRows = null; - - /// Connection config options - see constructor + + /** Connection config options - see constructor */ public $mConnOptions = array(); - /// Statement config options -- see constructor + /** Statement config options -- see constructor */ public $mStmtOptions = array(); - - - const CATALOGED = "cataloged"; - const UNCATALOGED = "uncataloged"; - const USE_GLOBAL = "get from global"; - + + /** Default schema */ + 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'; - - // Whether this is regular operation or the initial installation + + /** Controls the level of debug message output */ protected $mMode = self::REGULAR_MODE; - - /// Last sequence value used for a primary key + + /** Last sequence value used for a primary key */ protected $mInsertId = null; - - /* - * These can be safely inherited - * - * Getter/Setter: (18) - * failFunction - * bufferResults - * ignoreErrors - * trxLevel - * errorCount - * getLBInfo - * setLBInfo - * lastQuery - * isOpen - * setFlag - * clearFlag - * getFlag - * getProperty - * getDBname - * getServer - * tableNameCallback - * tablePrefix - * - * Administrative: (8) - * debug - * installErrorHandler - * restoreErrorHandler - * connectionErrorHandler - * reportConnectionError - * sourceFile - * sourceStream - * replaceVars - * - * Database: (5) - * query - * set - * selectField - * generalizeSQL - * update - * strreplace - * deadlockLoop - * - * Prepared Statement: 6 - * prepare - * freePrepared - * execute - * safeQuery - * fillPrepared - * fillPreparedArg - * - * Slave/Master: (4) - * masterPosWait - * getSlavePos - * getMasterPos - * getLag - * setFakeMaster - * - * Generation: (9) - * tableNames - * tableNamesN - * tableNamesWithUseIndexOrJOIN - * escapeLike - * delete - * insertSelect - * timestampOrNull - * resultObject - * aggregateValue - * selectSQLText - * selectRow - * makeUpdateOptions - * - * Reflection: (1) - * indexExists - */ - - /* - * These have been implemented - * - * Administrative: 7 / 7 - * constructor [Done] - * open [Done] - * openCataloged [Done] - * close [Done] - * newFromParams [Done] - * openUncataloged [Done] - * setup_database [Done] - * - * Getter/Setter: 13 / 13 - * cascadingDeletes [Done] - * cleanupTriggers [Done] - * strictIPs [Done] - * realTimestamps [Done] - * impliciGroupby [Done] - * implicitOrderby [Done] - * searchableIPs [Done] - * functionalIndexes [Done] - * getWikiID [Done] - * isOpen [Done] - * getServerVersion [Done] - * getSoftwareLink [Done] - * getSearchEngine [Done] - * - * Database driver wrapper: 23 / 23 - * lastError [Done] - * lastErrno [Done] - * doQuery [Done] - * tableExists [Done] - * fetchObject [Done] - * fetchRow [Done] - * freeResult [Done] - * numRows [Done] - * numFields [Done] - * fieldName [Done] - * insertId [Done] - * dataSeek [Done] - * affectedRows [Done] - * selectDB [Done] - * strencode [Done] - * conditional [Done] - * wasDeadlock [Done] - * ping [Done] - * getStatus [Done] - * setTimeout [Done] - * lock [Done] - * unlock [Done] - * insert [Done] - * select [Done] - * - * Slave/master: 2 / 2 - * setFakeSlaveLag [Done] - Where?? - * - * Reflection: 5 / 5 - * indexInfo [Done] - * fieldInfo [Done] - * fieldType [Done] - * indexUnique [Done] - * textFieldSize [Done] - * - * Generation: 16 / 16 - * tableName [Done] - * addQuotes [Done] - * makeList [Done] - * makeSelectOptions [Done] - * estimateRowCount [Done] - * nextSequenceValue [Done] - * useIndexClause [Done] - * replace [Done] - * deleteJoin [Done] - * lowPriorityOption [Done] - * limitResult [Done] - * limitResultForUpdate [Done] - * timestamp [Done] - * encodeBlob [Done] - * decodeBlob [Done] - * buildConcat [Done] - */ - + ###################################### # Getters and Setters ###################################### - + /** * Returns true if this database supports (and uses) cascading deletes */ @@ -328,20 +176,22 @@ class DatabaseIbm_db2 extends DatabaseBase { } /** - * Returns true if this database supports (and uses) triggers (e.g. on the page table) + * Returns true if this database supports (and uses) triggers (e.g. on the + * page table) */ function cleanupTriggers() { return true; } /** - * Returns true if this database is strict about what can be put into an IP field. + * Returns true if this database is strict about what can be put into an + * IP field. * Specifically, it uses a NULL value instead of an empty string. */ function strictIPs() { return true; } - + /** * Returns true if this database uses timestamps rather than integers */ @@ -357,7 +207,8 @@ class DatabaseIbm_db2 extends DatabaseBase { } /** - * Returns true if this database does an implicit order by when the column has an index + * Returns true if this database does an implicit order by when the column + * has an index * For example: SELECT page_title FROM page LIMIT 1 */ function implicitOrderby() { @@ -378,7 +229,7 @@ class DatabaseIbm_db2 extends DatabaseBase { function functionalIndexes() { return true; } - + /** * Returns a unique string representing the wiki on the server */ @@ -393,108 +244,99 @@ 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, $password = false, - $dbName = false, $failFunction = false, $flags = 0, + public function __construct( $server = false, $user = false, + $password = false, + $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('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); + /* + $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 ); + + 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 * @param $const String: name of the constant holding the right option value * @param $type Integer: whether this is a Connection or Statement otion */ - private function setDB2Option($name, $const, $type) { - if (defined($const)) { - if ($type & self::CONN_OPTION) $this->mConnOptions[$name] = constant($const); - if ($type & self::STMT_OPTION) $this->mStmtOptions[$name] = constant($const); - } - else { - $this->installPrint("$const is not defined. ibm_db2 version is likely too low."); + private function setDB2Option( $name, $const, $type ) { + if ( defined( $const ) ) { + if ( $type & self::CONN_OPTION ) { + $this->mConnOptions[$name] = constant( $const ); + } + if ( $type & self::STMT_OPTION ) { + $this->mStmtOptions[$name] = constant( $const ); + } + } 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 */ - private function installPrint($string) { - wfDebug("$string"); - if ($this->mMode == self::INSTALL_MODE) { - print "
  • $string
  • "; + private function installPrint( $string ) { + wfDebug( "$string\n" ); + 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, $wgDBcataloged; + 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 = "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?\n"; - $this->installPrint($error); - $this->reportConnectionError($error); + throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" ); } - if (!strlen($user)) { // Copied from Postgres - return null; - } - + global $wgDBport; + // Close existing connection $this->close(); // Cache conn info @@ -503,58 +345,52 @@ class DatabaseIbm_db2 extends DatabaseBase { $this->mUser = $user; $this->mPassword = $password; $this->mDBname = $dbName; - $this->mCataloged = $cataloged = $wgDBcataloged; - - if ( $cataloged == self::CATALOGED ) { - $this->openCataloged($dbName, $user, $password); - } - elseif ( $cataloged == self::UNCATALOGED ) { - $this->openUncataloged($dbName, $user, $password, $server, $port); - } - // Apply connection config - db2_set_option($this->mConn, $this->mConnOptions, 1); - // Not all MediaWiki code is transactional - // Rather, turn autocommit off in the begin function and turn on after a commit - db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON); + + $this->openUncataloged( $dbName, $user, $password, $server, $port ); 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; + $this->installPrint( + "Server: $server, Database: $dbName, User: $user, Password: " + . substr( $password, 0, 3 ) . "...\n" ); + $this->installPrint( $this->lastError() . "\n" ); + 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 ) - { - @$this->mConn = db2_connect($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;"; - if ($port) $str .= "PORT=$port;"; - $str .= "PROTOCOL=TCPIP;"; - $str .= "UID=$user;"; - $str .= "PWD=$password;"; - - @$this->mConn = db2_connect($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 @@ -562,51 +398,34 @@ class DatabaseIbm_db2 extends DatabaseBase { 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 */ public function lastError() { $connerr = db2_conn_errormsg(); - if ($connerr) { + if ( $connerr ) { //$this->rollback(); return $connerr; } $stmterr = db2_stmt_errormsg(); - if ($stmterr) { + if ( $stmterr ) { //$this->rollback(); return $stmterr; } - + return false; } - + /** * Get the last error number * Return 0 if no error @@ -614,43 +433,51 @@ class DatabaseIbm_db2 extends DatabaseBase { */ public function lastErrno() { $connerr = db2_conn_error(); - if ($connerr) return $connerr; + if ( $connerr ) { + return $connerr; + } $stmterr = db2_stmt_error(); - if ($stmterr) return $stmterr; + if ( $stmterr ) { + return $stmterr; + } 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. - * @return object Result object to feed to fetchObject, fetchRow, ...; or false on failure + * @return object Result object for fetch functions or false on failure * @access private */ /*private*/ public function doQuery( $sql ) { - //print "
  • $sql
  • "; - // Switch into the correct namespace $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 ) { - print "
    ";
    -			print $sql;
    -			print "

    "; + if( $ret == false ) { $error = db2_stmt_errormsg(); - throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( $error ) ); + + $this->installPrint( "
    $sql
    " ); + $this->installPrint( $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 */ @@ -658,30 +485,33 @@ class DatabaseIbm_db2 extends DatabaseBase { $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); + @$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 @@ -697,14 +527,15 @@ EOF; } @$row = db2_fetch_object( $res ); if( $this->lastErrno() ) { - throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) ); + throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' + . htmlspecialchars( $this->lastError() ) ); } return $row; } /** * 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 @@ -714,55 +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() { - // Timeout was being changed earlier due to mysterious crashes - // Changing it now may cause more problems than not changing it - //set_time_limit(240); 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 ) . ""; + if ( $res !== true ) { + print ' FAILED: ' . htmlspecialchars( $res ) . ''; } else { - print " done"; + print ' done'; } - $res = null; - + $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()) { - print "
  • Errors encountered during table creation -- rolled back
  • \n"; - print "
  • Please install again
  • \n"; + + if ( $this->lastError() ) { + $this->installPrint( + '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

    "; } } @@ -770,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"); + //$this->installPrint( "DB2::addQuotes( $s )\n" ); if ( is_null( $s ) ) { - return "NULL"; - } else if ($s instanceof Blob) { - return "'".$s->fetch($s)."'"; - } else if ($s instanceof IBM_DB2Blob) { - return "'".$this->decodeBlob($s)."'"; - } - $s = $this->strencode($s); - if ( is_numeric($s) ) { + return 'NULL'; + } elseif ( $s instanceof Blob ) { + return "'" . $s->fetch( $s ) . "'"; + } 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 @@ -818,178 +647,154 @@ 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); + $s = db2_escape_string( $s ); // Wide characters are evil -- some of them look like ' - $s = utf8_encode($s); + $s = utf8_encode( $s ); // Fix its stupidity - $from = array("\\\\", "\\'", '\\n', '\\t', '\\"', '\\r'); - $to = array("\\", "''", "\n", "\t", '"', "\r"); - $s = str_replace($from, $to, $s); // DB2 expects '', not \' escaping + $from = 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 */ protected function applySchema() { - if ( !($this->mSchemaSet) ) { + if ( !( $this->mSchemaSet ) ) { $this->mSchemaSet = true; $this->begin(); - $this->doQuery("SET SCHEMA = $this->mSchema"); + $this->doQuery( "SET SCHEMA = $this->mSchema" ); $this->commit(); - } + } } - + /** * Start a transaction (mandatory) */ public function begin( $fname = 'DatabaseIbm_db2::begin' ) { - // turn off auto-commit - db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF); + // 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); - // turn auto-commit back on - db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON); + 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 */ public function rollback( $fname = 'DatabaseIbm_db2::rollback' ) { - db2_rollback($this->mConn); + db2_rollback( $this->mConn ); // turn auto-commit back on // not sure if this is appropriate - db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON); + db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON ); $this->mTrxLevel = 0; } - + /** * Makes an encoded list of strings from an array * $mode: - * LIST_COMMA - comma separated, no field names - * LIST_AND - ANDed WHERE clause (without the WHERE) - * LIST_OR - ORed WHERE clause (without the WHERE) - * LIST_SET - comma separated with field names, like a SET clause - * LIST_NAMES - comma separated field names - */ - public function makeList( $a, $mode = LIST_COMMA ) { + * LIST_COMMA - comma separated, no field names + * LIST_AND - ANDed WHERE clause (without the WHERE) + * LIST_OR - ORed WHERE clause (without the WHERE) + * LIST_SET - comma separated with field names, like a SET clause + * LIST_NAMES - comma separated field names + * LIST_SET_PREPARED - like LIST_SET, except with ? tokens as values + */ + function makeList( $a, $mode = LIST_COMMA ) { if ( !is_array( $a ) ) { - throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' ); + throw new DBUnexpectedError( $this, + 'DatabaseIbm_db2::makeList called with incorrect parameters' ); } - $first = true; - $list = ''; - foreach ( $a as $field => $value ) { - if ( !$first ) { - if ( $mode == LIST_AND ) { - $list .= ' AND '; - } elseif($mode == LIST_OR) { - $list .= ' OR '; + // if this is for a prepared UPDATE statement + // (this should be promoted to the parent class + // once other databases use prepared statements) + if ( $mode == LIST_SET_PREPARED ) { + $first = true; + $list = ''; + foreach ( $a as $field => $value ) { + if ( !$first ) { + $list .= ", $field = ?"; } else { - $list .= ','; - } - } else { - $first = false; - } - if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) { - $list .= "($value)"; - } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) { - $list .= "$value"; - } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) { - if( count( $value ) == 0 ) { - throw new MWException( __METHOD__.': empty input' ); - } elseif( count( $value ) == 1 ) { - // Special-case single values, as IN isn't terribly efficient - // Don't necessarily assume the single key is 0; we don't - // enforce linear numeric ordering on other arrays here. - $value = array_values( $value ); - $list .= $field." = ".$this->addQuotes( $value[0] ); - } else { - $list .= $field." IN (".$this->makeList($value).") "; - } - } elseif( is_null($value) ) { - if ( $mode == LIST_AND || $mode == LIST_OR ) { - $list .= "$field IS "; - } elseif ( $mode == LIST_SET ) { - $list .= "$field = "; - } - $list .= 'NULL'; - } else { - if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { - $list .= "$field = "; - } - if ( $mode == LIST_NAMES ) { - $list .= $value; - } - // Leo: Can't insert quoted numbers into numeric columns - // (?) Might cause other problems. May have to check column type before insertion. - else if ( is_numeric($value) ) { - $list .= $value; - } - else { - $list .= $this->addQuotes( $value ); + $list .= "$field = ?"; + $first = false; } } + $list .= ''; + + return $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) */ - public function limitResult($sql, $limit, $offset=false) { - if( !is_numeric($limit) ) { - throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); + public function limitResult( $sql, $limit, $offset=false ) { + if( !is_numeric( $limit ) ) { + throw new DBUnexpectedError( $this, + "Invalid non-numeric limit passed to limitResult()\n" ); } if( $offset ) { - $this->installPrint("Offset parameter not supported in limitResult()\n"); + if ( stripos( $sql, 'where' ) === false ) { + return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )"; + } else { + return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )"; + } } - // TODO implement proper offset handling - // idea: get all the rows between 0 and offset, advance cursor to offset 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 ) { - # Replace reserved words with better ones -// switch( $name ) { -// case 'user': -// return 'mwuser'; -// case 'text': -// return 'pagecontent'; -// default: -// return $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); + return wfTimestamp( TS_DB2, $ts ); } /** @@ -998,8 +803,10 @@ EOF; * @return next value in that sequence */ public function nextSequenceValue( $seqName ) { - // Not using sequences in the primary schema to allow for easy third-party migration scripts - // Emulating MySQL behaviour of using NULL to signal that sequences aren't used + // Not using sequences in the primary schema to allow for easier migration + // from MySQL + // Emulating MySQL behaviour of using NULL to signal that sequences + // aren't used /* $safeseq = preg_replace( "/'/", "''", $seqName ); $res = $this->query( "VALUES NEXTVAL FOR $safeseq" ); @@ -1009,7 +816,7 @@ EOF; */ return null; } - + /** * This must be called after nextSequenceVal * @return Last sequence value used as a primary key @@ -1017,26 +824,27 @@ EOF; public function insertId() { return $this->mInsertId; } - + /** - * Updates the mInsertId property with the value of the last insert into a generated column + * 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 or a bool if this call is a do-nothing + * @param $primaryKey Mixed: string name of the primary key * @param $stmt Resource: prepared statement resource * of the SELECT primary_key FROM FINAL TABLE ( INSERT ... ) form */ - private function calcInsertId($table, $primaryKey, $stmt) { - if ($primaryKey) { - $id_row = $this->fetchRow($stmt); - $this->mInsertId = $id_row[0]; + private function calcInsertId( $table, $primaryKey, $stmt ) { + if ( $primaryKey ) { + $this->mInsertId = db2_last_insert_id( $this->mConn ); } } - + /** * INSERT wrapper, inserts an array into a table * - * $args may be a single associative array, or an array of these with numeric keys, - * for multi-row insert + * $args may be a single associative array, or an array of arrays + * with numeric keys, for multi-row insert * * @param $table String: Name of the table to insert to. * @param $args Array: Items to insert into the table. @@ -1045,30 +853,33 @@ EOF; * * @return bool Success of insert operation. IGNORE always returns true. */ - public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', $options = array() ) { + public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', + $options = array() ) + { if ( !count( $args ) ) { return true; } // get database-specific table name (not used) $table = $this->tableName( $table ); // format options as an array - if ( !is_array( $options ) ) $options = array( $options ); + $options = IBM_DB2Helper::makeArray( $options ); // format args as an array of arrays if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) { - $args = array($args); + $args = array( $args ); } + // prevent insertion of NULL into primary key columns - list($args, $primaryKeys) = $this->removeNullPrimaryKeys($table, $args); + list( $args, $primaryKeys ) = $this->removeNullPrimaryKeys( $table, $args ); // if there's only one primary key // we'll be able to read its value after insertion $primaryKey = false; - if (count($primaryKeys) == 1) { + if ( count( $primaryKeys ) == 1 ) { $primaryKey = $primaryKeys[0]; } - + // get column names $keys = array_keys( $args[0] ); - $key_count = count($keys); + $key_count = count( $keys ); // If IGNORE is set, we use savepoints to emulate mysql's behavior $ignore = in_array( 'IGNORE', $options ) ? 'mw' : ''; @@ -1076,141 +887,173 @@ 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(); } - $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES '; - switch($key_count) { - //case 0 impossible - case 1: - $sql .= '(?)'; - break; - default: - $sql .= '(?' . str_repeat(',?', $key_count-1) . ')'; - } - // add logic to read back the new primary key value - if ($primaryKey) { - $sql = "SELECT $primaryKey FROM FINAL TABLE($sql)"; + $sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES '; + if ( $key_count == 1 ) { + $sql .= '( ? )'; + } else { + $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )'; } - $stmt = $this->prepare($sql); - + $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(); if ( !$ignore ) { + //$first = true; foreach ( $args as $row ) { + //$this->installPrint( "Inserting " . print_r( $row, true )); // insert each row into the database - $res = $res & $this->execute($stmt, $row); + $res = $res & $this->execute( $stmt, $row ); + if ( !$res ) { + $this->installPrint( 'Last error:' ); + $this->installPrint( $this->lastError() ); + } // get the last inserted value into a generated column - $this->calcInsertId($table, $primaryKey, $stmt); + $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); + db2_exec( $this->mConn, $overhead, $this->mStmtOptions ); + + $res2 = $this->execute( $stmt, $row ); + + if ( !$res2 ) { + $this->installPrint( 'Last error:' ); + $this->installPrint( $this->lastError() ); + } // get the last inserted value into a generated column - $this->calcInsertId($table, $primaryKey, $stmt); - + $this->calcInsertId( $table, $primaryKey, $stmt ); + $errNum = $this->lastErrno(); - if ($errNum) { - db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore", $this->mStmtOptions ); - } - else { - db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore", $this->mStmtOptions ); + if ( $errNum ) { + db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore", + $this->mStmtOptions ); + } 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; } // 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 containing filtered array of columns, array of primary keys + * @return Array: tuple( filtered array of columns, array of primary keys ) */ - private function removeNullPrimaryKeys($table, $args) { + 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 != null; $row = $this->fetchRow($keyres)) { - $keys[] = strtolower($row->column_name); + for ( + $row = $this->fetchRow( $keyres ); + $row != null; + $row = $this->fetchRow( $keyres ) + ) + { + $keys[] = strtolower( $row[0] ); } // remove primary keys - foreach ($args as $ai => $row) { - foreach ($keys as $ki => $key) { - if ($row[$key] == null) { - unset($row[$key]); + foreach ( $args as $ai => $row ) { + foreach ( $keys as $key ) { + if ( $row[$key] == null ) { + unset( $row[$key] ); } } $args[$ai] = $row; } // return modified hash - return array($args, $keys); + return array( $args, $keys ); } - + /** * UPDATE wrapper, takes a condition array and a SET array * * @param $table String: The table to UPDATE * @param $values An array of values to SET - * @param $conds An array of conditions (WHERE). Use '*' to update all rows. + * @param $conds An array of conditions ( WHERE ). Use '*' to update all rows. * @param $fname String: The Class::Function calling this function - * (for the log) + * ( for the log ) * @param $options An array of UPDATE options, can be one or * more of IGNORE, LOW_PRIORITY * @return Boolean */ - public function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) { + public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update', + $options = array() ) + { $table = $this->tableName( $table ); $opts = $this->makeUpdateOptions( $options ); - $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET ); + $sql = "UPDATE $opts $table SET " + . $this->makeList( $values, LIST_SET_PREPARED ); if ( $conds != '*' ) { $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); } - return $this->query( $sql, $fname ); + $stmt = $this->prepare( $sql ); + $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' ); + throw new DBUnexpectedError( $this, + 'DatabaseIbm_db2::delete() called with no conditions' ); } $table = $this->tableName( $table ); $sql = "DELETE FROM $table"; if ( $conds != '*' ) { $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); } - return $this->query( $sql, $fname ); + $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 @@ -1220,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 @@ -1233,10 +1077,12 @@ EOF; * @param $fname String: name of the function for profiling * @return nothing */ - function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseIbm_db2::replace' ) { + function replace( $table, $uniqueIndexes, $rows, + $fname = 'DatabaseIbm_db2::replace' ) + { $table = $this->tableName( $table ); - if (count($rows)==0) { + if ( count( $rows )==0 ) { return; } @@ -1253,9 +1099,9 @@ EOF; foreach ( $uniqueIndexes as $index ) { if ( $first ) { $first = false; - $sql .= "("; + $sql .= '( '; } else { - $sql .= ') OR ('; + $sql .= ' ) OR ( '; } if ( is_array( $index ) ) { $first2 = true; @@ -1265,23 +1111,21 @@ EOF; } else { $sql .= ' AND '; } - $sql .= $col.'=' . $this->addQuotes( $row[$col] ); + $sql .= $col . '=' . $this->addQuotes( $row[$col] ); } } else { - $sql .= $index.'=' . $this->addQuotes( $row[$index] ); + $sql .= $index . '=' . $this->addQuotes( $row[$index] ); } } - $sql .= ')'; + $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 ); + $this->insert($table, $row); } } - + /** * Returns the number of rows in the result set * Has to be called right after the corresponding select query @@ -1292,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 @@ -1312,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 @@ -1327,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 @@ -1342,7 +1186,7 @@ EOF; } return db2_num_fields( $res ); } - + /** * Returns the nth column name * @param $res Object: statement resource @@ -1355,57 +1199,65 @@ EOF; } return db2_field_name( $res, $n ); } - + /** * SELECT wrapper * * @param $table Array or string, table name(s) (prefix auto-added) * @param $vars Array or string, field name(s) to be retrieved * @param $conds Array or string, condition(s) for WHERE - * @param $fname String: calling function name (use __METHOD__) for logs/profiling - * @param $options Associative array of options (e.g. array('GROUP BY' => 'page_title')), - * see Database::makeSelectOptions code for list of supported stuff + * @param $fname String: calling function name (use __METHOD__) + * for logs/profiling + * @param $options Associative array of options + * (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) - * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') ) - * @return Mixed: database result resource (feed to Database::fetchObject or whatever), or false on failure + * (e.g. array( 'page' => array('LEFT JOIN', + * 'page_latest=rev_id') ) + * @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 ); - + $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 it - // DB2 9.5.3/9.5.4 and the corresponding ibm_db2 driver will introduce a working one - // Yay! - + + // 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'); + $vars2 = array( 'count( * ) as num_rows' ); // respecting just the limit option $options2 = array(); - if ( isset( $options['LIMIT'] ) ) $options2['LIMIT'] = $options['LIMIT']; + if ( isset( $options['LIMIT'] ) ) { + $options2['LIMIT'] = $options['LIMIT']; + } // but don't try to emulate for GROUP BY - if ( isset( $options['GROUP BY'] ) ) return $res; - - $res2 = parent::select( $table, $vars2, $conds, $fname, $options2, $join_conds ); - $obj = $this->fetchObject($res2); + 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 @@ -1423,31 +1275,41 @@ EOF; } } - if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; - if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}"; - if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; - - if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; - + if ( isset( $options['GROUP BY'] ) ) { + $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; + } + if ( isset( $options['HAVING'] ) ) { + $preLimitTail .= " HAVING {$options['HAVING']}"; + } + 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/software/data/db2/express/?s_cmp=ECDDWW01&s_tact=MediaWiki 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'; } /** @@ -1457,16 +1319,17 @@ EOF; public function wasDeadlock() { // get SQLSTATE $err = $this->lastErrno(); - switch($err) { + switch( $err ) { + // This is literal port of the MySQL logic and may be wrong for DB2 case '40001': // sql0911n, Deadlock or timeout, rollback case '57011': // sql0904n, Resource unavailable, no rollback case '57033': // sql0913n, Deadlock or timeout, no rollback - $this->installPrint("In a deadlock because of SQLSTATE $err"); + $this->installPrint( "In a deadlock because of SQLSTATE $err" ); return true; } 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 @@ -1476,15 +1339,9 @@ EOF; // db2_ping() doesn't exist // Emulate $this->close(); - if ($this->mCataloged == null) { - return false; - } - else if ($this->mCataloged) { - $this->mConn = $this->openCataloged($this->mDBName, $this->mUser, $this->mPassword); - } - else if (!$this->mCataloged) { - $this->mConn = $this->openUncataloged($this->mDBName, $this->mUser, $this->mPassword, $this->mServer, $this->mPort); - } + $this->mConn = $this->openUncataloged( $this->mDBName, $this->mUser, + $this->mPassword, $this->mServer, $this->mPort ); + return false; } ###################################### @@ -1494,23 +1351,32 @@ 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; } - + */ + 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 */ - public function fillPreparedArg( $matches ) { $this->installPrint('Not useful for DB2: fillPreparedArg()'); return ''; } - + public function fillPreparedArg( $matches ) { + $this->installPrint( 'Not useful for DB2: fillPreparedArg()' ); + return ''; + } + ###################################### # Reflection ###################################### - + /** * Returns information about an index * If errors are explicitly ignored, returns NULL on failure @@ -1519,22 +1385,28 @@ EOF; * @param $fname String: function name for logging and profiling * @return Object query row in object form */ - public function indexInfo( $table, $index, $fname = 'DatabaseIbm_db2::indexExists' ) { + public function indexInfo( $table, $index, + $fname = 'DatabaseIbm_db2::indexExists' ) + { $table = $this->tableName( $table ); $sql = <<query( $sql, $fname ); if ( !$res ) { return null; } $row = $this->fetchObject( $res ); - if ($row != null) return $row; - else return false; + if ( $row != null ) { + return $row; + } else { + return false; + } } - + /** * Returns an information object on a table column * @param $table String: table name @@ -1542,9 +1414,9 @@ SQL; * @return IBM_DB2Field */ public function fieldInfo( $table, $field ) { - return IBM_DB2Field::fromText($this, $table, $field); + return IBM_DB2Field::fromText( $this, $table, $field ); } - + /** * db2_field_type() wrapper * @param $res Object: result of executed statement @@ -1557,7 +1429,7 @@ SQL; } return db2_field_type( $res, $index ); } - + /** * Verifies that an index was created as unique * @param $table String: table name @@ -1565,25 +1437,28 @@ SQL; * @param $fname function name for profiling * @return Bool */ - public function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) { + public function indexUnique ( $table, $index, + $fname = 'DatabaseIbm_db2::indexUnique' ) + { $table = $this->tableName( $table ); $sql = <<query( $sql, $fname ); if ( !$res ) { return null; } - if ($this->fetchObject( $res )) { + if ( $this->fetchObject( $res ) ) { return true; } return false; } - + /** * Returns the size of a text field, or -1 for "unlimited" * @param $table String: table name @@ -1595,14 +1470,15 @@ SQL; $sql = <<query($sql); - $row = $this->fetchObject($res); + $res = $this->query( $sql ); + $row = $this->fetchObject( $res ); $size = $row->size; return $size; } - + /** * DELETE where the condition is a join * @param $delTable String: deleting from this table @@ -1612,18 +1488,26 @@ SQL; * @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" ) { + public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, + $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 ); $joinTable = $this->tableName( $joinTable ); - $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; + $sql = <<makeList( $conds, LIST_AND ); } - $sql .= ')'; + $sql .= ' )'; $this->query( $sql, $fname ); } @@ -1633,22 +1517,23 @@ SQL; * @param $b Mixed: data to be encoded * @return IBM_DB2Blob */ - public function encodeBlob($b) { - return new IBM_DB2Blob($b); + 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 * @return mixed */ - public function decodeBlob($b) { - return $b->getData(); + public function decodeBlob( $b ) { + return "$b"; } - + /** * Convert into a list of string being concatenated - * @param $stringList Array: strings that need to be joined together by the SQL engine + * @param $stringList Array: strings that need to be joined together + * by the SQL engine * @return String: joined by the concatenation operator */ public function buildConcat( $stringList ) { @@ -1656,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 @@ -1666,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 @@ -1684,7 +1569,7 @@ SQL; * @return resource a prepared DB2 SQL statement */ public function prepare( $sql, $func = 'DB2::prepare' ) { - $stmt = db2_prepare($this->mConn, $sql, $this->mStmtOptions); + $stmt = db2_prepare( $this->mConn, $sql, $this->mStmtOptions ); return $stmt; } @@ -1693,7 +1578,7 @@ SQL; * @return Boolean success or failure */ public function freePrepared( $prepared ) { - return db2_free_stmt($prepared); + return db2_free_stmt( $prepared ); } /** @@ -1708,7 +1593,10 @@ SQL; $args = func_get_args(); array_shift( $args ); } - $res = db2_execute($prepared, $args); + $res = db2_execute( $prepared, $args ); + if ( !$res ) { + $this->installPrint( db2_stmt_errormsg() ); + } return $res; } @@ -1741,32 +1629,32 @@ SQL; public function fillPrepared( $preparedQuery, $args ) { reset( $args ); $this->preparedArgs =& $args; - - foreach ($args as $i => $arg) { - db2_bind_param($preparedQuery, $i+1, $args[$i]); + + 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; + public function setMode( $mode ) { + $old = $this->mMode; $this->mMode = $mode; return $old; } - + /** * Bitwise negation of a column or value in SQL * Same as (~field) in C * @param $field String * @return String */ - function bitNot($field) { - //expecting bit-fields smaller than 4bytes - return 'BITNOT('.$field.')'; + function bitNot( $field ) { + // expecting bit-fields smaller than 4bytes + return "BITNOT( $field )"; } /** @@ -1776,8 +1664,8 @@ SQL; * @param $fieldRight String * @return String */ - function bitAnd($fieldLeft, $fieldRight) { - return 'BITAND('.$fieldLeft.', '.$fieldRight.')'; + function bitAnd( $fieldLeft, $fieldRight ) { + return "BITAND( $fieldLeft, $fieldRight )"; } /** @@ -1787,7 +1675,17 @@ SQL; * @param $fieldRight String * @return String */ - function bitOr($fieldLeft, $fieldRight) { - return 'BITOR('.$fieldLeft.', '.$fieldRight.')'; + function bitOr( $fieldLeft, $fieldRight ) { + return "BITOR( $fieldLeft, $fieldRight )"; + } +} + +class IBM_DB2Helper { + public static function makeArray( $maybeArray ) { + if ( !is_array( $maybeArray ) ) { + return array( $maybeArray ); + } + + return $maybeArray; } }