From 08aa4418c30cfc18ccc69a0f0f9cb9e17be6c196 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Mon, 12 Aug 2013 09:28:15 +0200 Subject: Update to MediaWiki 1.21.1 --- includes/db/CloneDatabase.php | 23 +- includes/db/Database.php | 625 +++++++++----- includes/db/DatabaseError.php | 22 +- includes/db/DatabaseIbm_db2.php | 1721 -------------------------------------- includes/db/DatabaseMssql.php | 105 +-- includes/db/DatabaseMysql.php | 61 +- includes/db/DatabaseOracle.php | 76 +- includes/db/DatabasePostgres.php | 156 ++-- includes/db/DatabaseSqlite.php | 47 +- includes/db/DatabaseUtility.php | 13 +- includes/db/IORMRow.php | 5 +- includes/db/IORMTable.php | 91 +- includes/db/LBFactory.php | 22 +- includes/db/LBFactory_Multi.php | 11 +- includes/db/LBFactory_Single.php | 2 +- includes/db/LoadBalancer.php | 150 ++-- includes/db/LoadMonitor.php | 9 +- includes/db/ORMIterator.php | 4 +- includes/db/ORMResult.php | 2 +- includes/db/ORMRow.php | 47 +- includes/db/ORMTable.php | 295 ++++++- 21 files changed, 1151 insertions(+), 2336 deletions(-) delete mode 100644 includes/db/DatabaseIbm_db2.php (limited to 'includes/db') diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php index 4e43642f..4e443741 100644 --- a/includes/db/CloneDatabase.php +++ b/includes/db/CloneDatabase.php @@ -60,9 +60,9 @@ class CloneDatabase { * Constructor * * @param $db DatabaseBase A database subclass - * @param $tablesToClone Array An array of tables to clone, unprefixed - * @param $newTablePrefix String Prefix to assign to the tables - * @param $oldTablePrefix String Prefix on current tables, if not $wgDBprefix + * @param array $tablesToClone An array of tables to clone, unprefixed + * @param string $newTablePrefix Prefix to assign to the tables + * @param string $oldTablePrefix Prefix on current tables, if not $wgDBprefix * @param $dropCurrentTables bool */ public function __construct( DatabaseBase $db, array $tablesToClone, @@ -77,7 +77,7 @@ class CloneDatabase { /** * Set whether to use temporary tables or not - * @param $u Bool Use temporary tables when cloning the structure + * @param bool $u Use temporary tables when cloning the structure */ public function useTemporaryTables( $u = true ) { $this->useTemporaryTables = $u; @@ -87,35 +87,32 @@ class CloneDatabase { * Clone the table structure */ public function cloneTableStructure() { - foreach( $this->tablesToClone as $tbl ) { # Clean up from previous aborted run. So that table escaping # works correctly across DB engines, we need to change the pre- # fix back and forth so tableName() works right. - + self::changePrefix( $this->oldTablePrefix ); $oldTableName = $this->db->tableName( $tbl, 'raw' ); - + self::changePrefix( $this->newTablePrefix ); $newTableName = $this->db->tableName( $tbl, 'raw' ); - + if( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) ) ) { $this->db->dropTable( $tbl, __METHOD__ ); - wfDebug( __METHOD__." dropping {$newTableName}\n", true); + wfDebug( __METHOD__ . " dropping {$newTableName}\n", true ); //Dropping the oldTable because the prefix was changed } # Create new table - wfDebug( __METHOD__." duplicating $oldTableName to $newTableName\n", true ); + wfDebug( __METHOD__ . " duplicating $oldTableName to $newTableName\n", true ); $this->db->duplicateTableStructure( $oldTableName, $newTableName, $this->useTemporaryTables ); - } - } /** * Change the prefix back to the original. - * @param $dropTables bool Optionally drop the tables we created + * @param bool $dropTables Optionally drop the tables we created */ public function destroy( $dropTables = false ) { if( $dropTables ) { diff --git a/includes/db/Database.php b/includes/db/Database.php index 5f10b97d..65a74abf 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -49,10 +49,10 @@ interface DatabaseType { /** * Open a connection to the database. Usually aborts on failure * - * @param $server String: database server host - * @param $user String: database user name - * @param $password String: database user password - * @param $dbName String: database name + * @param string $server database server host + * @param string $user database user name + * @param string $password database user password + * @param string $dbName database name * @return bool * @throws DBConnectionError */ @@ -62,9 +62,10 @@ interface DatabaseType { * Fetch the next row from the given result object, in object form. * Fields can be retrieved with $row->fieldname, with fields acting like * member variables. + * If no more rows are available, false is returned. * * @param $res ResultWrapper|object as returned from DatabaseBase::query(), etc. - * @return Row object + * @return object|bool * @throws DBUnexpectedError Thrown if the database returns an error */ function fetchObject( $res ); @@ -72,9 +73,10 @@ interface DatabaseType { /** * Fetch the next row from the given result object, in associative array * form. Fields are retrieved with $row['fieldname']. + * If no more rows are available, false is returned. * * @param $res ResultWrapper result object as returned from DatabaseBase::query(), etc. - * @return Row object + * @return array|bool * @throws DBUnexpectedError Thrown if the database returns an error */ function fetchRow( $res ); @@ -112,8 +114,8 @@ interface DatabaseType { * The value inserted should be fetched from nextSequenceValue() * * Example: - * $id = $dbw->nextSequenceValue('page_page_id_seq'); - * $dbw->insert('page',array('page_id' => $id)); + * $id = $dbw->nextSequenceValue( 'page_page_id_seq' ); + * $dbw->insert( 'page', array( 'page_id' => $id ) ); * $id = $dbw->insertId(); * * @return int @@ -149,8 +151,8 @@ interface DatabaseType { * mysql_fetch_field() wrapper * Returns false if the field doesn't exist * - * @param $table string: table name - * @param $field string: field name + * @param string $table table name + * @param string $field field name * * @return Field */ @@ -158,9 +160,9 @@ interface DatabaseType { /** * Get information about an index into an object - * @param $table string: Table name - * @param $index string: Index name - * @param $fname string: Calling function name + * @param string $table Table name + * @param string $index Index name + * @param string $fname Calling function name * @return Mixed: Database-specific index description class or false if the index does not exist */ function indexInfo( $table, $index, $fname = 'Database::indexInfo' ); @@ -176,7 +178,7 @@ interface DatabaseType { /** * Wrapper for addslashes() * - * @param $s string: to be slashed. + * @param string $s to be slashed. * @return string: slashed string. */ function strencode( $s ); @@ -249,6 +251,37 @@ abstract class DatabaseBase implements DatabaseType { protected $delimiter = ';'; + /** + * Remembers the function name given for starting the most recent transaction via begin(). + * Used to provide additional context for error reporting. + * + * @var String + * @see DatabaseBase::mTrxLevel + */ + private $mTrxFname = null; + + /** + * Record if possible write queries were done in the last transaction started + * + * @var Bool + * @see DatabaseBase::mTrxLevel + */ + private $mTrxDoneWrites = false; + + /** + * Record if the current transaction was started implicitly due to DBO_TRX being set. + * + * @var Bool + * @see DatabaseBase::mTrxLevel + */ + private $mTrxAutomatic = false; + + /** + * @since 1.21 + * @var file handle for upgrade + */ + protected $fileHandle = null; + # ------------------------------------------------------------------------------ # Accessors # ------------------------------------------------------------------------------ @@ -265,6 +298,13 @@ abstract class DatabaseBase implements DatabaseType { return $this->getServerVersion(); } + /** + * @return string: command delimiter used by this database engine + */ + public function getDelimiter() { + return $this->delimiter; + } + /** * Boolean, controls output of large amounts of debug information. * @param $debug bool|null @@ -329,7 +369,7 @@ abstract class DatabaseBase implements DatabaseType { * Historically, transactions were allowed to be "nested". This is no * longer supported, so this function really only returns a boolean. * - * @param $level int An integer (0 or 1), or omitted to leave it unchanged. + * @param int $level An integer (0 or 1), or omitted to leave it unchanged. * @return int The previous value */ public function trxLevel( $level = null ) { @@ -338,7 +378,7 @@ abstract class DatabaseBase implements DatabaseType { /** * Get/set the number of errors logged. Only useful when errors are ignored - * @param $count int The count to set, or omitted to leave it unchanged. + * @param int $count The count to set, or omitted to leave it unchanged. * @return int The error count */ public function errorCount( $count = null ) { @@ -347,18 +387,27 @@ abstract class DatabaseBase implements DatabaseType { /** * Get/set the table prefix. - * @param $prefix string The table prefix to set, or omitted to leave it unchanged. + * @param string $prefix The table prefix to set, or omitted to leave it unchanged. * @return string The previous table prefix. */ public function tablePrefix( $prefix = null ) { return wfSetVar( $this->mTablePrefix, $prefix ); } + /** + * Set the filehandle to copy write statements to. + * + * @param $fh filehandle + */ + public function setFileHandle( $fh ) { + $this->fileHandle = $fh; + } + /** * Get properties passed down from the server info array of the load * balancer. * - * @param $name string The entry of the info array to get, or null to get the + * @param string $name The entry of the info array to get, or null to get the * whole array * * @return LoadBalancer|null @@ -441,7 +490,7 @@ abstract class DatabaseBase implements DatabaseType { * Returns true if this database uses timestamps rather than integers * * @return bool - */ + */ public function realTimestamps() { return false; } @@ -509,7 +558,7 @@ abstract class DatabaseBase implements DatabaseType { * @return bool */ public function writesOrCallbacksPending() { - return $this->mTrxLevel && ( $this->mDoneWrites || $this->mTrxIdleCallbacks ); + return $this->mTrxLevel && ( $this->mTrxDoneWrites || $this->mTrxIdleCallbacks ); } /** @@ -536,7 +585,7 @@ abstract class DatabaseBase implements DatabaseType { global $wgDebugDBTransactions; $this->mFlags |= $flag; if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) { - wfDebug("Implicit transactions are now disabled.\n"); + wfDebug( "Implicit transactions are now disabled.\n" ); } } @@ -549,7 +598,7 @@ abstract class DatabaseBase implements DatabaseType { global $wgDebugDBTransactions; $this->mFlags &= ~$flag; if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { - wfDebug("Implicit transactions are now disabled.\n"); + wfDebug( "Implicit transactions are now disabled.\n" ); } } @@ -605,12 +654,12 @@ abstract class DatabaseBase implements DatabaseType { /** * Constructor. - * @param $server String: database server host - * @param $user String: database user name - * @param $password String: database user password - * @param $dbName String: database name + * @param string $server database server host + * @param string $user database user name + * @param string $password database user password + * @param string $dbName database name * @param $flags - * @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php + * @param string $tablePrefix database table prefixes. By default use the prefix gave in LocalSettings.php */ function __construct( $server = false, $user = false, $password = false, $dbName = false, $flags = 0, $tablePrefix = 'get from global' @@ -623,12 +672,12 @@ abstract class DatabaseBase implements DatabaseType { if ( $wgCommandLineMode ) { $this->mFlags &= ~DBO_TRX; if ( $wgDebugDBTransactions ) { - wfDebug("Implicit transaction open disabled.\n"); + wfDebug( "Implicit transaction open disabled.\n" ); } } else { $this->mFlags |= DBO_TRX; if ( $wgDebugDBTransactions ) { - wfDebug("Implicit transaction open enabled.\n"); + wfDebug( "Implicit transaction open enabled.\n" ); } } } @@ -671,14 +720,14 @@ abstract class DatabaseBase implements DatabaseType { * * @since 1.18 * - * @param $dbType String A possible DB type - * @param $p Array An array of options to pass to the constructor. + * @param string $dbType A possible DB type + * @param array $p An array of options to pass to the constructor. * Valid options are: host, user, password, dbname, flags, tablePrefix * @return DatabaseBase subclass or null */ - public final static function factory( $dbType, $p = array() ) { + final public static function factory( $dbType, $p = array() ) { $canonicalDBTypes = array( - 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2' + 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql' ); $dbType = strtolower( $dbType ); $class = 'Database' . ucfirst( $dbType ); @@ -724,7 +773,7 @@ abstract class DatabaseBase implements DatabaseType { * @param $errno * @param $errstr */ - protected function connectionErrorHandler( $errno, $errstr ) { + protected function connectionErrorHandler( $errno, $errstr ) { $this->mPHPError = $errstr; } @@ -732,6 +781,7 @@ abstract class DatabaseBase implements DatabaseType { * Closes a database connection. * if it is open : commits any open transactions * + * @throws MWException * @return Bool operation success. true if already closed. */ public function close() { @@ -741,8 +791,14 @@ abstract class DatabaseBase implements DatabaseType { $this->mOpened = false; if ( $this->mConn ) { if ( $this->trxLevel() ) { - $this->commit( __METHOD__ ); + if ( !$this->mTrxAutomatic ) { + wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " . + " performing implicit commit before closing connection!" ); + } + + $this->commit( __METHOD__, 'flush' ); } + $ret = $this->closeConnection(); $this->mConn = false; return $ret; @@ -756,10 +812,11 @@ abstract class DatabaseBase implements DatabaseType { * @since 1.20 * @return bool: Whether connection was closed successfully */ - protected abstract function closeConnection(); + abstract protected function closeConnection(); /** - * @param $error String: fallback error message, used if none is given by DB + * @param string $error fallback error message, used if none is given by DB + * @throws DBConnectionError */ function reportConnectionError( $error = 'Unknown error' ) { $myError = $this->lastError(); @@ -777,7 +834,7 @@ abstract class DatabaseBase implements DatabaseType { * @param $sql String: SQL query. * @return ResultWrapper Result object to feed to fetchObject, fetchRow, ...; or false on failure */ - protected abstract function doQuery( $sql ); + abstract protected function doQuery( $sql ); /** * Determine whether a query writes to the DB. @@ -809,9 +866,9 @@ abstract class DatabaseBase implements DatabaseType { * comment (you can use __METHOD__ or add some extra info) * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors... * maybe best to catch the exception instead? + * @throws MWException * @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object * for a successful read query, or false on failure if $tempIgnore set - * @throws DBQueryError Thrown when the database returns an error of any kind */ public function query( $sql, $fname = '', $tempIgnore = false ) { $isMaster = !is_null( $this->getLBInfo( 'master' ) ); @@ -849,24 +906,34 @@ abstract class DatabaseBase implements DatabaseType { } else { $userName = ''; } - $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 ); + + // Add trace comment to the begin of the sql string, right after the operator. + // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598) + $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 ); # If DBO_TRX is set, start a transaction - if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() && - $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) { - # avoid establishing transactions for SHOW and SET statements too - + if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel && + $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) + { + # Avoid establishing transactions for SHOW and SET statements too - # that would delay transaction initializations to once connection # is really used by application $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm) if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) { global $wgDebugDBTransactions; if ( $wgDebugDBTransactions ) { - wfDebug("Implicit transaction start.\n"); + wfDebug( "Implicit transaction start.\n" ); } $this->begin( __METHOD__ . " ($fname)" ); + $this->mTrxAutomatic = true; } } + # Keep track of whether the transaction has write queries pending + if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) { + $this->mTrxDoneWrites = true; + } + if ( $this->debug() ) { static $cnt = 0; @@ -933,6 +1000,7 @@ abstract class DatabaseBase implements DatabaseType { * @param $sql String * @param $fname String * @param $tempIgnore Boolean + * @throws DBQueryError */ public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { # Ignore errors during error handling to avoid infinite recursion @@ -981,7 +1049,7 @@ abstract class DatabaseBase implements DatabaseType { /** * Execute a prepared query with the various arguments - * @param $prepared String: the prepared sql + * @param string $prepared the prepared sql * @param $args Mixed: Either an array here, or put scalars as varargs * * @return ResultWrapper @@ -1001,8 +1069,8 @@ abstract class DatabaseBase implements DatabaseType { /** * For faking prepared SQL statements on DBs that don't support it directly. * - * @param $preparedQuery String: a 'preparable' SQL statement - * @param $args Array of arguments to fill it with + * @param string $preparedQuery a 'preparable' SQL statement + * @param array $args of arguments to fill it with * @return string executable SQL */ public function fillPrepared( $preparedQuery, $args ) { @@ -1019,6 +1087,7 @@ abstract class DatabaseBase implements DatabaseType { * while we're doing this. * * @param $matches Array + * @throws DBUnexpectedError * @return String */ protected function fillPreparedArg( $matches ) { @@ -1028,7 +1097,7 @@ abstract class DatabaseBase implements DatabaseType { case '\\&': return '&'; } - list( /* $n */ , $arg ) = each( $this->preparedArgs ); + list( /* $n */, $arg ) = each( $this->preparedArgs ); switch( $matches[1] ) { case '?': return $this->addQuotes( $arg ); @@ -1058,12 +1127,12 @@ abstract class DatabaseBase implements DatabaseType { * * If no result rows are returned from the query, false is returned. * - * @param $table string|array Table name. See DatabaseBase::select() for details. - * @param $var string The field name to select. This must be a valid SQL + * @param string|array $table Table name. See DatabaseBase::select() for details. + * @param string $var The field name to select. This must be a valid SQL * fragment: do not use unvalidated user input. - * @param $cond string|array The condition array. See DatabaseBase::select() for details. - * @param $fname string The function name of the caller. - * @param $options string|array The query options. See DatabaseBase::select() for details. + * @param string|array $cond The condition array. See DatabaseBase::select() for details. + * @param string $fname The function name of the caller. + * @param string|array $options The query options. See DatabaseBase::select() for details. * * @return bool|mixed The value from the field, or false on failure. */ @@ -1095,7 +1164,7 @@ abstract class DatabaseBase implements DatabaseType { * Returns an optional USE INDEX clause to go after the table, and a * string to go at the end of the query. * - * @param $options Array: associative array of options to be turned into + * @param array $options associative array of options to be turned into * an SQL query, valid keys are listed in the function. * @return Array * @see DatabaseBase::select() @@ -1112,26 +1181,9 @@ abstract class DatabaseBase implements DatabaseType { } } - if ( isset( $options['GROUP BY'] ) ) { - $gb = is_array( $options['GROUP BY'] ) - ? implode( ',', $options['GROUP BY'] ) - : $options['GROUP BY']; - $preLimitTail .= " GROUP BY {$gb}"; - } + $preLimitTail .= $this->makeGroupByWithHaving( $options ); - if ( isset( $options['HAVING'] ) ) { - $having = is_array( $options['HAVING'] ) - ? $this->makeList( $options['HAVING'], LIST_AND ) - : $options['HAVING']; - $preLimitTail .= " HAVING {$having}"; - } - - if ( isset( $options['ORDER BY'] ) ) { - $ob = is_array( $options['ORDER BY'] ) - ? implode( ',', $options['ORDER BY'] ) - : $options['ORDER BY']; - $preLimitTail .= " ORDER BY {$ob}"; - } + $preLimitTail .= $this->makeOrderBy( $options ); // if (isset($options['LIMIT'])) { // $tailOpts .= $this->limitResult('', $options['LIMIT'], @@ -1193,15 +1245,58 @@ abstract class DatabaseBase implements DatabaseType { return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); } + /** + * Returns an optional GROUP BY with an optional HAVING + * + * @param array $options associative array of options + * @return string + * @see DatabaseBase::select() + * @since 1.21 + */ + public function makeGroupByWithHaving( $options ) { + $sql = ''; + if ( isset( $options['GROUP BY'] ) ) { + $gb = is_array( $options['GROUP BY'] ) + ? implode( ',', $options['GROUP BY'] ) + : $options['GROUP BY']; + $sql .= ' GROUP BY ' . $gb; + } + if ( isset( $options['HAVING'] ) ) { + $having = is_array( $options['HAVING'] ) + ? $this->makeList( $options['HAVING'], LIST_AND ) + : $options['HAVING']; + $sql .= ' HAVING ' . $having; + } + return $sql; + } + + /** + * Returns an optional ORDER BY + * + * @param array $options associative array of options + * @return string + * @see DatabaseBase::select() + * @since 1.21 + */ + public function makeOrderBy( $options ) { + if ( isset( $options['ORDER BY'] ) ) { + $ob = is_array( $options['ORDER BY'] ) + ? implode( ',', $options['ORDER BY'] ) + : $options['ORDER BY']; + return ' ORDER BY ' . $ob; + } + return ''; + } + /** * Execute a SELECT query constructed using the various parameters provided. * See below for full details of the parameters. * - * @param $table String|Array Table name - * @param $vars String|Array Field names - * @param $conds String|Array Conditions - * @param $fname String Caller function name - * @param $options Array Query options + * @param string|array $table Table name + * @param string|array $vars Field names + * @param string|array $conds Conditions + * @param string $fname Caller function name + * @param array $options Query options * @param $join_conds Array Join conditions * * @param $table string|array @@ -1325,7 +1420,7 @@ abstract class DatabaseBase implements DatabaseType { * join, the second is an SQL fragment giving the join condition for that * table. For example: * - * array( 'page' => array('LEFT JOIN','page_latest=rev_id') ) + * array( 'page' => array( 'LEFT JOIN', 'page_latest=rev_id' ) ) * * @return ResultWrapper. If the query returned no rows, a ResultWrapper * with no rows in it will be returned. If there was a query error, a @@ -1345,11 +1440,11 @@ abstract class DatabaseBase implements DatabaseType { * doing UNION queries, where the SQL text of each query is needed. In general, * however, callers outside of Database classes should just use select(). * - * @param $table string|array Table name - * @param $vars string|array Field names - * @param $conds string|array Conditions - * @param $fname string Caller function name - * @param $options string|array Query options + * @param string|array $table Table name + * @param string|array $vars Field names + * @param string|array $conds Conditions + * @param string $fname Caller function name + * @param string|array $options Query options * @param $join_conds string|array Join conditions * * @return string SQL query string. @@ -1413,11 +1508,11 @@ abstract class DatabaseBase implements DatabaseType { * that a single row object is returned. If the query returns no rows, * false is returned. * - * @param $table string|array Table name - * @param $vars string|array Field names - * @param $conds array Conditions - * @param $fname string Caller function name - * @param $options string|array Query options + * @param string|array $table Table name + * @param string|array $vars Field names + * @param array $conds Conditions + * @param string $fname Caller function name + * @param string|array $options Query options * @param $join_conds array|string Join conditions * * @return object|bool @@ -1455,11 +1550,11 @@ abstract class DatabaseBase implements DatabaseType { * * Takes the same arguments as DatabaseBase::select(). * - * @param $table String: table name - * @param Array|string $vars : unused - * @param Array|string $conds : filters on the table - * @param $fname String: function name for profiling - * @param $options Array: options for select + * @param string $table table name + * @param array|string $vars : unused + * @param array|string $conds : filters on the table + * @param string $fname function name for profiling + * @param array $options options for select * @return Integer: row count */ public function estimateRowCount( $table, $vars = '*', $conds = '', @@ -1480,7 +1575,7 @@ abstract class DatabaseBase implements DatabaseType { * Removes most variables from an SQL query and replaces them with X or N for numbers. * It's only slightly flawed. Don't use for anything important. * - * @param $sql String A SQL Query + * @param string $sql A SQL Query * * @return string */ @@ -1507,9 +1602,9 @@ abstract class DatabaseBase implements DatabaseType { /** * Determines whether a field exists in a table * - * @param $table String: table name - * @param $field String: filed to check on that table - * @param $fname String: calling function name (optional) + * @param string $table table name + * @param string $field filed to check on that table + * @param string $fname calling function name (optional) * @return Boolean: whether $table has filed $field */ public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) { @@ -1530,6 +1625,10 @@ abstract class DatabaseBase implements DatabaseType { * @return bool|null */ public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) { + if( !$this->tableExists( $table ) ) { + return null; + } + $info = $this->indexInfo( $table, $index, $fname ); if ( is_null( $info ) ) { return null; @@ -1626,7 +1725,7 @@ abstract class DatabaseBase implements DatabaseType { * DatabaseBase::tableName(). * @param $a Array of rows to insert * @param $fname String Calling function name (use __METHOD__) for logs/profiling - * @param $options Array of options + * @param array $options of options * * @return bool */ @@ -1642,6 +1741,10 @@ abstract class DatabaseBase implements DatabaseType { $options = array( $options ); } + $fh = null; + if ( isset( $options['fileHandle'] ) ) { + $fh = $options['fileHandle']; + } $options = $this->makeInsertOptions( $options ); if ( isset( $a[0] ) && is_array( $a[0] ) ) { @@ -1669,13 +1772,19 @@ abstract class DatabaseBase implements DatabaseType { $sql .= '(' . $this->makeList( $a ) . ')'; } + if ( $fh !== null && false === fwrite( $fh, $sql ) ) { + return false; + } elseif ( $fh !== null ) { + return true; + } + return (bool)$this->query( $sql, $fname ); } /** * Make UPDATE options for the DatabaseBase::update function * - * @param $options Array: The options passed to DatabaseBase::update + * @param array $options The options passed to DatabaseBase::update * @return string */ protected function makeUpdateOptions( $options ) { @@ -1702,7 +1811,7 @@ abstract class DatabaseBase implements DatabaseType { * @param $table String name of the table to UPDATE. This will be passed through * DatabaseBase::tableName(). * - * @param $values Array: An array of values to SET. For each array element, + * @param array $values An array of values to SET. For each array element, * the key gives the field name, and the value gives the data * to set that field to. The data will be quoted by * DatabaseBase::addQuotes(). @@ -1714,7 +1823,7 @@ abstract class DatabaseBase implements DatabaseType { * @param $fname String: The function name of the caller (from __METHOD__), * for logging and profiling. * - * @param $options Array: An array of UPDATE options, can be: + * @param array $options An array of UPDATE options, can be: * - IGNORE: Ignore unique key conflicts * - LOW_PRIORITY: MySQL-specific, see MySQL manual. * @return Boolean @@ -1733,8 +1842,8 @@ abstract class DatabaseBase implements DatabaseType { /** * Makes an encoded list of strings from an array - * @param $a Array containing the data - * @param $mode int Constant + * @param array $a containing the data + * @param int $mode Constant * - LIST_COMMA: comma separated, no field names * - LIST_AND: ANDed WHERE clause (without the WHERE). See * the documentation for $conds in DatabaseBase::select(). @@ -1742,6 +1851,7 @@ abstract class DatabaseBase implements DatabaseType { * - LIST_SET: comma separated with field names, like a SET clause * - LIST_NAMES: comma separated field names * + * @throws MWException|DBUnexpectedError * @return string */ public function makeList( $a, $mode = LIST_COMMA ) { @@ -1771,7 +1881,7 @@ abstract class DatabaseBase implements DatabaseType { $list .= "$value"; } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) { if ( count( $value ) == 0 ) { - throw new MWException( __METHOD__ . ': empty input' ); + throw new MWException( __METHOD__ . ": empty input for field $field" ); } 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 @@ -1803,10 +1913,10 @@ abstract class DatabaseBase implements DatabaseType { * Build a partial where clause from a 2-d array such as used for LinkBatch. * The keys on each level may be either integers or strings. * - * @param $data Array: organized as 2-d + * @param array $data organized as 2-d * array(baseKeyVal => array(subKeyVal => [ignored], ...), ...) - * @param $baseKey String: field name to match the base-level keys to (eg 'pl_namespace') - * @param $subKey String: field name to match the sub-level keys to (eg 'pl_title') + * @param string $baseKey field name to match the base-level keys to (eg 'pl_namespace') + * @param string $subKey field name to match the sub-level keys to (eg 'pl_title') * @return Mixed: string SQL fragment, or false if no items in array. */ public function makeWhereFrom2d( $data, $baseKey, $subKey ) { @@ -1868,7 +1978,7 @@ abstract class DatabaseBase implements DatabaseType { /** * Build a concatenation list to feed into a SQL query - * @param $stringList Array: list of raw SQL expressions; caller is responsible for any quoting + * @param array $stringList list of raw SQL expressions; caller is responsible for any quoting * @return String */ public function buildConcat( $stringList ) { @@ -1916,8 +2026,8 @@ abstract class DatabaseBase implements DatabaseType { * themselves. Pass the canonical name to such functions. This is only needed * when calling query() directly. * - * @param $name String: database table name - * @param $format String One of: + * @param string $name database table name + * @param string $format One of: * quoted - Automatically pass the table name through addIdentifierQuotes() * so that it can be used in a query. * raw - Do not add identifier quotes to the table name @@ -1947,47 +2057,39 @@ abstract class DatabaseBase implements DatabaseType { # Split database and table into proper variables. # We reverse the explode so that database.table and table both output # the correct table. - $dbDetails = array_reverse( explode( '.', $name, 2 ) ); - if ( isset( $dbDetails[1] ) ) { - list( $table, $database ) = $dbDetails; + $dbDetails = explode( '.', $name, 2 ); + if ( count( $dbDetails ) == 2 ) { + list( $database, $table ) = $dbDetails; + # We don't want any prefix added in this case + $prefix = ''; } else { list( $table ) = $dbDetails; - } - $prefix = $this->mTablePrefix; # Default prefix - - # A database name has been specified in input. We don't want any - # prefixes added. - if ( isset( $database ) ) { - $prefix = ''; + if ( $wgSharedDB !== null # We have a shared database + && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`' + && in_array( $table, $wgSharedTables ) # A shared table is selected + ) { + $database = $wgSharedDB; + $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix; + } else { + $database = null; + $prefix = $this->mTablePrefix; # Default prefix + } } - # Note that we use the long format because php will complain in in_array if - # the input is not an array, and will complain in is_array if it is not set. - if ( !isset( $database ) # Don't use shared database if pre selected. - && isset( $wgSharedDB ) # We have a shared database - && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`' - && isset( $wgSharedTables ) - && is_array( $wgSharedTables ) - && in_array( $table, $wgSharedTables ) ) { # A shared table is selected - $database = $wgSharedDB; - $prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix; + # Quote $table and apply the prefix if not quoted. + $tableName = "{$prefix}{$table}"; + if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) ) { + $tableName = $this->addIdentifierQuotes( $tableName ); } - # Quote the $database and $table and apply the prefix if not quoted. - if ( isset( $database ) ) { + # Quote $database and merge it with the table name if needed + if ( $database !== null ) { if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { $database = $this->addIdentifierQuotes( $database ); } + $tableName = $database . '.' . $tableName; } - $table = "{$prefix}{$table}"; - if ( $format == 'quoted' && !$this->isQuotedIdentifier( $table ) ) { - $table = $this->addIdentifierQuotes( "{$table}" ); - } - - # Merge our database and table into our final table name. - $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" ); - return $tableName; } @@ -1996,7 +2098,7 @@ abstract class DatabaseBase implements DatabaseType { * This is handy when you need to construct SQL for joins * * Example: - * extract($dbr->tableNames('user','watchlist')); + * extract( $dbr->tableNames( 'user', 'watchlist' ) ); * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; * @@ -2018,7 +2120,7 @@ abstract class DatabaseBase implements DatabaseType { * This is handy when you need to construct SQL for joins * * Example: - * list( $user, $watchlist ) = $dbr->tableNamesN('user','watchlist'); + * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' ); * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; * @@ -2039,8 +2141,8 @@ abstract class DatabaseBase implements DatabaseType { * Get an aliased table name * e.g. tableName AS newTableName * - * @param $name string Table name, see tableName() - * @param $alias string|bool Alias (optional) + * @param string $name Table name, see tableName() + * @param string|bool $alias Alias (optional) * @return string SQL name for aliased table. Will not alias a table to its own name */ public function tableNameWithAlias( $name, $alias = false ) { @@ -2072,8 +2174,8 @@ abstract class DatabaseBase implements DatabaseType { * Get an aliased field name * e.g. fieldName AS newFieldName * - * @param $name string Field name - * @param $alias string|bool Alias (optional) + * @param string $name Field name + * @param string|bool $alias Alias (optional) * @return string SQL name for aliased field. Will not alias a field to its own name */ public function fieldNameWithAlias( $name, $alias = false ) { @@ -2105,7 +2207,7 @@ abstract class DatabaseBase implements DatabaseType { * Get the aliased table name clause for a FROM clause * which might have a JOIN and/or USE INDEX clause * - * @param $tables array ( [alias] => table ) + * @param array $tables ( [alias] => table ) * @param $use_index array Same as for select() * @param $join_conds array Same as for select() * @return string @@ -2336,12 +2438,12 @@ abstract class DatabaseBase implements DatabaseType { * to collide. However if you do this, you run the risk of encountering * errors which wouldn't have occurred in MySQL. * - * @param $table String: The table to replace the row(s) in. - * @param $rows array Can be either a single row to insert, or multiple rows, + * @param string $table The table to replace the row(s) in. + * @param array $rows Can be either a single row to insert, or multiple rows, * in the same format as for DatabaseBase::insert() - * @param $uniqueIndexes array is an array of indexes. Each element may be either + * @param array $uniqueIndexes is an array of indexes. Each element may be either * a field name or an array of field names - * @param $fname String: Calling function name (use __METHOD__) for logs/profiling + * @param string $fname Calling function name (use __METHOD__) for logs/profiling */ public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) { $quotedTable = $this->tableName( $table ); @@ -2394,9 +2496,9 @@ abstract class DatabaseBase implements DatabaseType { * REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE * statement. * - * @param $table string Table name - * @param $rows array Rows to insert - * @param $fname string Caller function name + * @param string $table Table name + * @param array $rows Rows to insert + * @param string $fname Caller function name * * @return ResultWrapper */ @@ -2443,6 +2545,7 @@ abstract class DatabaseBase implements DatabaseType { * ANDed together in the WHERE clause * @param $fname String: Calling function name (use __METHOD__) for * logs/profiling + * @throws DBUnexpectedError */ public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) @@ -2503,12 +2606,13 @@ abstract class DatabaseBase implements DatabaseType { /** * DELETE query wrapper. * - * @param $table Array Table name - * @param $conds String|Array of conditions. See $conds in DatabaseBase::select() for + * @param array $table Table name + * @param string|array $conds of conditions. See $conds in DatabaseBase::select() for * the format. Use $conds == "*" to delete all rows - * @param $fname String name of the calling function + * @param string $fname name of the calling function * - * @return bool + * @throws DBUnexpectedError + * @return bool|ResultWrapper */ public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) { if ( !$conds ) { @@ -2529,24 +2633,24 @@ abstract class DatabaseBase implements DatabaseType { * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it * into another table. * - * @param $destTable string The table name to insert into - * @param $srcTable string|array May be either a table name, or an array of table names + * @param string $destTable The table name to insert into + * @param string|array $srcTable May be either a table name, or an array of table names * to include in a join. * - * @param $varMap array must be an associative array of the form + * @param array $varMap must be an associative array of the form * array( 'dest1' => 'source1', ...). Source items may be literals * rather than field names, but strings should be quoted with * DatabaseBase::addQuotes() * - * @param $conds array Condition array. See $conds in DatabaseBase::select() for + * @param array $conds Condition array. See $conds in DatabaseBase::select() for * the details of the format of condition arrays. May be "*" to copy the * whole table. * - * @param $fname string The function name of the caller, from __METHOD__ + * @param string $fname The function name of the caller, from __METHOD__ * - * @param $insertOptions array Options for the INSERT part of the query, see + * @param array $insertOptions Options for the INSERT part of the query, see * DatabaseBase::insert() for details. - * @param $selectOptions array Options for the SELECT part of the query, see + * @param array $selectOptions Options for the SELECT part of the query, see * DatabaseBase::select() for details. * * @return ResultWrapper @@ -2568,7 +2672,7 @@ abstract class DatabaseBase implements DatabaseType { list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); if ( is_array( $srcTable ) ) { - $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); + $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); } else { $srcTable = $this->tableName( $srcTable ); } @@ -2602,10 +2706,11 @@ abstract class DatabaseBase implements DatabaseType { * The version provided by default works in MySQL and SQLite. It will very * likely need to be overridden for most other DBMSes. * - * @param $sql String SQL query we will append the limit too + * @param string $sql SQL query we will append the limit too * @param $limit Integer the SQL limit * @param $offset Integer|bool the SQL offset (default false) * + * @throws DBUnexpectedError * @return string */ public function limitResult( $sql, $limit, $offset = false ) { @@ -2630,7 +2735,7 @@ abstract class DatabaseBase implements DatabaseType { * Construct a UNION query * This is used for providing overload point for other DB abstractions * not compatible with the MySQL syntax. - * @param $sqls Array: SQL statements to combine + * @param array $sqls SQL statements to combine * @param $all Boolean: use UNION ALL * @return String: SQL fragment */ @@ -2643,9 +2748,9 @@ abstract class DatabaseBase implements DatabaseType { * Returns an SQL expression for a simple conditional. This doesn't need * to be overridden unless CASE isn't supported in your DBMS. * - * @param $cond string|array SQL expression which will result in a boolean value - * @param $trueVal String: SQL expression to return if true - * @param $falseVal String: SQL expression to return if false + * @param string|array $cond SQL expression which will result in a boolean value + * @param string $trueVal SQL expression to return if true + * @param string $falseVal SQL expression to return if false * @return String: SQL fragment */ public function conditional( $cond, $trueVal, $falseVal ) { @@ -2659,9 +2764,9 @@ abstract class DatabaseBase implements DatabaseType { * Returns a comand for str_replace function in SQL query. * Uses REPLACE() in MySQL * - * @param $orig String: column to modify - * @param $old String: column to seek - * @param $new String: column to replace with + * @param string $orig column to modify + * @param string $old column to seek + * @param string $new column to replace with * * @return string */ @@ -2854,8 +2959,9 @@ abstract class DatabaseBase implements DatabaseType { * * This is useful for updates to different systems or separate transactions are needed. * + * @since 1.20 + * * @param Closure $callback - * @return void */ final public function onTransactionIdle( Closure $callback ) { if ( $this->mTrxLevel ) { @@ -2866,7 +2972,9 @@ abstract class DatabaseBase implements DatabaseType { } /** - * Actually run the "on transaction idle" callbacks + * Actually run the "on transaction idle" callbacks. + * + * @since 1.20 */ protected function runOnTransactionIdleCallbacks() { $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? @@ -2890,19 +2998,50 @@ abstract class DatabaseBase implements DatabaseType { } /** - * Begin a transaction + * Begin a transaction. If a transaction is already in progress, that transaction will be committed before the + * new transaction is started. + * + * Note that when the DBO_TRX flag is set (which is usually the case for web requests, but not for maintenance scripts), + * any previous database query will have started a transaction automatically. + * + * Nesting of transactions is not supported. Attempts to nest transactions will cause a warning, unless the current + * transaction was started automatically because of the DBO_TRX flag. * * @param $fname string */ final public function begin( $fname = 'DatabaseBase::begin' ) { + global $wgDebugDBTransactions; + if ( $this->mTrxLevel ) { // implicit commit + if ( !$this->mTrxAutomatic ) { + // We want to warn about inadvertently nested begin/commit pairs, but not about + // auto-committing implicit transactions that were started by query() via DBO_TRX + $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " . + " performing implicit commit!"; + wfWarn( $msg ); + wfLogDBError( $msg ); + } else { + // if the transaction was automatic and has done write operations, + // log it if $wgDebugDBTransactions is enabled. + if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) { + wfDebug( "$fname: Automatic transaction with writes in progress" . + " (from {$this->mTrxFname}), performing implicit commit!\n" ); + } + } + $this->doCommit( $fname ); $this->runOnTransactionIdleCallbacks(); } + $this->doBegin( $fname ); + $this->mTrxFname = $fname; + $this->mTrxDoneWrites = false; + $this->mTrxAutomatic = false; } /** + * Issues the BEGIN command to the database server. + * * @see DatabaseBase::begin() * @param type $fname */ @@ -2912,16 +3051,39 @@ abstract class DatabaseBase implements DatabaseType { } /** - * End a transaction + * Commits a transaction previously started using begin(). + * If no transaction is in progress, a warning is issued. + * + * Nesting of transactions is not supported. * * @param $fname string - */ - final public function commit( $fname = 'DatabaseBase::commit' ) { + * @param string $flush Flush flag, set to 'flush' to disable warnings about explicitly committing implicit + * transactions, or calling commit when no transaction is in progress. + * This will silently break any ongoing explicit transaction. Only set the flush flag if you are sure + * that it is safe to ignore these warnings in your context. + */ + final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) { + if ( $flush != 'flush' ) { + if ( !$this->mTrxLevel ) { + wfWarn( "$fname: No transaction to commit, something got out of sync!" ); + } elseif( $this->mTrxAutomatic ) { + wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" ); + } + } else { + if ( !$this->mTrxLevel ) { + return; // nothing to do + } elseif( !$this->mTrxAutomatic ) { + wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); + } + } + $this->doCommit( $fname ); $this->runOnTransactionIdleCallbacks(); } /** + * Issues the COMMIT command to the database server. + * * @see DatabaseBase::commit() * @param type $fname */ @@ -2933,17 +3095,24 @@ abstract class DatabaseBase implements DatabaseType { } /** - * Rollback a transaction. + * Rollback a transaction previously started using begin(). + * If no transaction is in progress, a warning is issued. + * * No-op on non-transactional databases. * * @param $fname string */ final public function rollback( $fname = 'DatabaseBase::rollback' ) { + if ( !$this->mTrxLevel ) { + wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); + } $this->doRollback( $fname ); $this->mTrxIdleCallbacks = array(); // cancel } /** + * Issues the ROLLBACK command to the database server. + * * @see DatabaseBase::rollback() * @param type $fname */ @@ -2962,10 +3131,11 @@ abstract class DatabaseBase implements DatabaseType { * The table names passed to this function shall not be quoted (this * function calls addIdentifierQuotes when needed). * - * @param $oldName String: name of table whose structure should be copied - * @param $newName String: name of table to be created + * @param string $oldName name of table whose structure should be copied + * @param string $newName name of table to be created * @param $temporary Boolean: whether the new table should be temporary - * @param $fname String: calling function name + * @param string $fname calling function name + * @throws MWException * @return Boolean: true if operation was successful */ public function duplicateTableStructure( $oldName, $newName, $temporary = false, @@ -2978,8 +3148,9 @@ abstract class DatabaseBase implements DatabaseType { /** * List all tables on the database * - * @param $prefix string Only show tables with this prefix, e.g. mw_ - * @param $fname String: calling function name + * @param string $prefix Only show tables with this prefix, e.g. mw_ + * @param string $fname calling function name + * @throws MWException */ function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) { throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); @@ -3122,15 +3293,18 @@ abstract class DatabaseBase implements DatabaseType { * Returns true on success, error string or exception on failure (depending * on object's error ignore settings). * - * @param $filename String: File name to open - * @param $lineCallback Callback: Optional function called before reading each line - * @param $resultCallback Callback: Optional function called for each MySQL result - * @param $fname String: Calling function name or false if name should be + * @param string $filename File name to open + * @param bool|callable $lineCallback Optional function called before reading each line + * @param bool|callable $resultCallback Optional function called for each MySQL result + * @param bool|string $fname Calling function name or false if name should be * generated dynamically using $filename + * @param bool|callable $inputCallback Callback: Optional function called for each complete line sent + * @throws MWException + * @throws Exception|MWException * @return bool|string */ public function sourceFile( - $filename, $lineCallback = false, $resultCallback = false, $fname = false + $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false ) { wfSuppressWarnings(); $fp = fopen( $filename, 'r' ); @@ -3145,7 +3319,7 @@ abstract class DatabaseBase implements DatabaseType { } try { - $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname ); + $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback ); } catch ( MWException $e ) { fclose( $fp ); @@ -3162,7 +3336,7 @@ abstract class DatabaseBase implements DatabaseType { * from updaters.inc. Keep in mind this always returns a patch, as * it fails back to MySQL if no DB-specific patch can be found * - * @param $patch String The name of the patch, like patch-something.sql + * @param string $patch The name of the patch, like patch-something.sql * @return String Full path to patch file */ public function patchPath( $patch ) { @@ -3181,7 +3355,7 @@ abstract class DatabaseBase implements DatabaseType { * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at * all. If it's set to false, $GLOBALS will be used. * - * @param $vars bool|array mapping variable name to value. + * @param bool|array $vars mapping variable name to value. */ public function setSchemaVars( $vars ) { $this->mSchemaVars = $vars; @@ -3194,10 +3368,10 @@ abstract class DatabaseBase implements DatabaseType { * on object's error ignore settings). * * @param $fp Resource: File handle - * @param $lineCallback Callback: Optional function called before reading each line + * @param $lineCallback Callback: Optional function called before reading each query * @param $resultCallback Callback: Optional function called for each MySQL result - * @param $fname String: Calling function name - * @param $inputCallback Callback: Optional function called for each complete line (ended with ;) sent + * @param string $fname Calling function name + * @param $inputCallback Callback: Optional function called for each complete query sent * @return bool|string */ public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, @@ -3230,20 +3404,19 @@ abstract class DatabaseBase implements DatabaseType { if ( $done || feof( $fp ) ) { $cmd = $this->replaceVars( $cmd ); - if ( $inputCallback ) { - call_user_func( $inputCallback, $cmd ); - } - $res = $this->query( $cmd, $fname ); - if ( $resultCallback ) { - call_user_func( $resultCallback, $res, $this ); - } + if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) { + $res = $this->query( $cmd, $fname ); - if ( false === $res ) { - $err = $this->lastError(); - return "Query \"{$cmd}\" failed with error code \"$err\".\n"; - } + if ( $resultCallback ) { + call_user_func( $resultCallback, $res, $this ); + } + if ( false === $res ) { + $err = $this->lastError(); + return "Query \"{$cmd}\" failed with error code \"$err\".\n"; + } + } $cmd = ''; } } @@ -3254,8 +3427,8 @@ abstract class DatabaseBase implements DatabaseType { /** * Called by sourceStream() to check if we've reached a statement end * - * @param $sql String SQL assembled so far - * @param $newLine String New line about to be added to $sql + * @param string $sql SQL assembled so far + * @param string $newLine New line about to be added to $sql * @return Bool Whether $newLine contains end of the statement */ public function streamStatementEnd( &$sql, &$newLine ) { @@ -3283,7 +3456,7 @@ abstract class DatabaseBase implements DatabaseType { * - / *$var* / is just encoded, besides traditional table prefix and * table options its use should be avoided. * - * @param $ins String: SQL statement to replace variables in + * @param string $ins SQL statement to replace variables in * @return String The new SQL statement with variables replaced */ protected function replaceSchemaVars( $ins ) { @@ -3294,7 +3467,7 @@ abstract class DatabaseBase implements DatabaseType { // replace `{$var}` $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins ); // replace /*$var*/ - $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ) , $ins ); + $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins ); } return $ins; } @@ -3371,8 +3544,8 @@ abstract class DatabaseBase implements DatabaseType { /** * Check to see if a named lock is available. This is non-blocking. * - * @param $lockName String: name of lock to poll - * @param $method String: name of method calling us + * @param string $lockName name of lock to poll + * @param string $method name of method calling us * @return Boolean * @since 1.20 */ @@ -3386,8 +3559,8 @@ abstract class DatabaseBase implements DatabaseType { * Abstracted from Filestore::lock() so child classes can implement for * their own needs. * - * @param $lockName String: name of lock to aquire - * @param $method String: name of method calling us + * @param string $lockName name of lock to aquire + * @param string $method name of method calling us * @param $timeout Integer: timeout * @return Boolean */ @@ -3398,8 +3571,8 @@ abstract class DatabaseBase implements DatabaseType { /** * Release a lock. * - * @param $lockName String: Name of lock to release - * @param $method String: Name of method calling us + * @param string $lockName Name of lock to release + * @param string $method Name of method calling us * * @return int Returns 1 if the lock was released, 0 if the lock was not established * by this thread (in which case the lock is not released), and NULL if the named @@ -3412,10 +3585,10 @@ abstract class DatabaseBase implements DatabaseType { /** * Lock specific tables * - * @param $read Array of tables to lock for read access - * @param $write Array of tables to lock for write access - * @param $method String name of caller - * @param $lowPriority bool Whether to indicate writes to be LOW PRIORITY + * @param array $read of tables to lock for read access + * @param array $write of tables to lock for write access + * @param string $method name of caller + * @param bool $lowPriority Whether to indicate writes to be LOW PRIORITY * * @return bool */ @@ -3426,7 +3599,7 @@ abstract class DatabaseBase implements DatabaseType { /** * Unlock specific tables * - * @param $method String the caller + * @param string $method the caller * * @return bool */ @@ -3476,7 +3649,7 @@ abstract class DatabaseBase implements DatabaseType { /** * Encode an expiry time into the DBMS dependent format * - * @param $expiry String: timestamp for expiry, or the 'infinity' string + * @param string $expiry timestamp for expiry, or the 'infinity' string * @return String */ public function encodeExpiry( $expiry ) { @@ -3488,7 +3661,7 @@ abstract class DatabaseBase implements DatabaseType { /** * Decode an expiry time into a DBMS independent format * - * @param $expiry String: DB timestamp field value for expiry + * @param string $expiry DB timestamp field value for expiry * @param $format integer: TS_* constant, defaults to TS_MW * @return String */ diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php index a53a6747..628a2afc 100644 --- a/includes/db/DatabaseError.php +++ b/includes/db/DatabaseError.php @@ -35,9 +35,9 @@ class DBError extends MWException { /** * Construct a database error * @param $db DatabaseBase object which threw the error - * @param $error String A simple error message to be used for debugging + * @param string $error A simple error message to be used for debugging */ - function __construct( DatabaseBase &$db, $error ) { + function __construct( DatabaseBase $db = null, $error ) { $this->db = $db; parent::__construct( $error ); } @@ -91,7 +91,7 @@ class DBError extends MWException { class DBConnectionError extends DBError { public $error; - function __construct( DatabaseBase &$db, $error = 'unknown error' ) { + function __construct( DatabaseBase $db = null, $error = 'unknown error' ) { $msg = 'DB connection error'; if ( trim( $error ) != '' ) { @@ -153,12 +153,12 @@ class DBConnectionError extends DBError { $sorry = htmlspecialchars( $this->msg( 'dberr-problems', 'Sorry! This site is experiencing technical difficulties.' ) ); $again = htmlspecialchars( $this->msg( 'dberr-again', 'Try waiting a few minutes and reloading.' ) ); - $info = htmlspecialchars( $this->msg( 'dberr-info', '(Can\'t contact the database server: $1)' ) ); + $info = htmlspecialchars( $this->msg( 'dberr-info', '(Can\'t contact the database server: $1)' ) ); # No database access MessageCache::singleton()->disable(); - if ( trim( $this->error ) == '' ) { + if ( trim( $this->error ) == '' && $this->db ) { $this->error = $this->db->getProperty( 'mServer' ); } @@ -176,7 +176,7 @@ class DBConnectionError extends DBError { return "$text
$extra"; } - public function reportHTML(){ + public function reportHTML() { global $wgUseFileCache; # Check whether we can serve a file-cached copy of the page with the error underneath @@ -288,11 +288,11 @@ class DBQueryError extends DBError { * @param $sql string * @param $fname string */ - function __construct( DatabaseBase &$db, $error, $errno, $sql, $fname ) { - $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; + function __construct( DatabaseBase $db, $error, $errno, $sql, $fname ) { + $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" . + "Query: $sql\n" . + "Function: $fname\n" . + "Error: $errno $error\n"; parent::__construct( $db, $message ); $this->error = $error; diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php deleted file mode 100644 index f1f6dfca..00000000 --- a/includes/db/DatabaseIbm_db2.php +++ /dev/null @@ -1,1721 +0,0 @@ -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->name = $field; - $n->tablename = $table; - $n->max_length = $row->attlen; - return $n; - } - /** - * Get column name - * @return string column name - */ - function name() { return $this->name; } - /** - * Get table name - * @return string table name - */ - function tableName() { return $this->tablename; } - /** - * Get column type - * @return string column type - */ - function type() { return $this->type; } - /** - * Can column be null? - * @return bool true or false - */ - function isNullable() { return $this->nullable; } - /** - * How much can you fit in the column per row? - * @return int length - */ - function maxLength() { return $this->max_length; } -} - -/** - * Wrapper around binary large objects - * @ingroup Database - */ -class IBM_DB2Blob { - private $mData; - - public function __construct( $data ) { - $this->mData = $data; - } - - public function getData() { - return $this->mData; - } - - public function __toString() { - return $this->mData; - } -} - -/** - * Wrapper to address lack of certain operations in the DB2 driver - * ( seek, num_rows ) - * @ingroup Database - * @since 1.19 - */ -class IBM_DB2Result{ - private $db; - private $result; - private $num_rows; - private $current_pos; - private $columns = array(); - private $sql; - - private $resultSet = array(); - private $loadedLines = 0; - - /** - * Construct and initialize a wrapper for DB2 query results - * @param $db DatabaseBase - * @param $result Object - * @param $num_rows Integer - * @param $sql String - * @param $columns Array - */ - public function __construct( $db, $result, $num_rows, $sql, $columns ){ - $this->db = $db; - - if( $result instanceof ResultWrapper ){ - $this->result = $result->result; - } - else{ - $this->result = $result; - } - - $this->num_rows = $num_rows; - $this->current_pos = 0; - if ( $this->num_rows > 0 ) { - // Make a lower-case list of the column names - // By default, DB2 column names are capitalized - // while MySQL column names are lowercase - - // Is there a reasonable maximum value for $i? - // Setting to 2048 to prevent an infinite loop - for( $i = 0; $i < 2048; $i++ ) { - $name = db2_field_name( $this->result, $i ); - if ( $name != false ) { - continue; - } - else { - return false; - } - - $this->columns[$i] = strtolower( $name ); - } - } - - $this->sql = $sql; - } - - /** - * Unwrap the DB2 query results - * @return mixed Object on success, false on failure - */ - public function getResult() { - if ( $this->result ) { - return $this->result; - } - else return false; - } - - /** - * Get the number of rows in the result set - * @return integer - */ - public function getNum_rows() { - return $this->num_rows; - } - - /** - * Return a row from the result set in object format - * @return mixed Object on success, false on failure. - */ - public function fetchObject() { - if ( $this->result - && $this->num_rows > 0 - && $this->current_pos >= 0 - && $this->current_pos < $this->num_rows ) - { - $row = $this->fetchRow(); - $ret = new stdClass(); - - foreach ( $row as $k => $v ) { - $lc = $this->columns[$k]; - $ret->$lc = $v; - } - return $ret; - } - return false; - } - - /** - * Return a row form the result set in array format - * @return mixed Array on success, false on failure - * @throws DBUnexpectedError - */ - public function fetchRow(){ - if ( $this->result - && $this->num_rows > 0 - && $this->current_pos >= 0 - && $this->current_pos < $this->num_rows ) - { - if ( $this->loadedLines <= $this->current_pos ) { - $row = db2_fetch_array( $this->result ); - $this->resultSet[$this->loadedLines++] = $row; - if ( $this->db->lastErrno() ) { - throw new DBUnexpectedError( $this->db, 'Error in fetchRow(): ' - . htmlspecialchars( $this->db->lastError() ) ); - } - } - - if ( $this->loadedLines > $this->current_pos ){ - return $this->resultSet[$this->current_pos++]; - } - - } - return false; - } - - /** - * Free a DB2 result object - * @throws DBUnexpectedError - */ - public function freeResult(){ - unset( $this->resultSet ); - if ( !@db2_free_result( $this->result ) ) { - throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" ); - } - } -} - -/** - * Primary database interface - * @ingroup Database - */ -class DatabaseIbm_db2 extends DatabaseBase { - /* - * Inherited members - protected $mLastQuery = ''; - protected $mPHPError = false; - - protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname; - protected $mOpened = false; - - protected $mTablePrefix; - protected $mFlags; - protected $mTrxLevel = 0; - protected $mErrorCount = 0; - protected $mLBInfo = array(); - protected $mFakeSlaveLag = null, $mFakeMaster = false; - * - */ - - /** Database server port */ - protected $mPort = null; - /** Schema for tables, stored procedures, triggers */ - protected $mSchema = null; - /** Whether the schema has been applied in this session */ - protected $mSchemaSet = false; - /** Result of last query */ - protected $mLastResult = null; - /** Number of rows affected by last INSERT/UPDATE/DELETE */ - protected $mAffectedRows = null; - /** Number of rows returned by last SELECT */ - protected $mNumRows = null; - /** Current row number on the cursor of the last SELECT */ - protected $currentRow = 0; - - /** Connection config options - see constructor */ - public $mConnOptions = array(); - /** Statement config options -- see constructor */ - public $mStmtOptions = array(); - - /** 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'; - - /** 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 - * @return bool - */ - function cascadingDeletes() { - return true; - } - - /** - * Returns true if this database supports (and uses) triggers (e.g. on the - * page table) - * @return bool - */ - function cleanupTriggers() { - return true; - } - - /** - * 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. - * @return bool - */ - function strictIPs() { - return true; - } - - /** - * Returns true if this database uses timestamps rather than integers - * @return bool - */ - function realTimestamps() { - return true; - } - - /** - * Returns true if this database does an implicit sort when doing GROUP BY - * @return bool - */ - function implicitGroupby() { - return false; - } - - /** - * 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 - * @return bool - */ - function implicitOrderby() { - return false; - } - - /** - * Returns true if this database can do a native search on IP columns - * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32'; - * @return bool - */ - function searchableIPs() { - return true; - } - - /** - * Returns true if this database can use functional indexes - * @return bool - */ - function functionalIndexes() { - return true; - } - - /** - * Returns a unique string representing the wiki on the server - * @return string - */ - public function getWikiID() { - if( $this->mSchema ) { - return "{$this->mDBname}-{$this->mSchema}"; - } else { - return $this->mDBname; - } - } - - /** - * Returns the database software identifieir - * @return string - */ - public function getType() { - return 'ibm_db2'; - } - - /** - * Returns the database connection object - * @return Object - */ - public function getDb(){ - return $this->mConn; - } - - /** - * - * @param $server String: hostname of database server - * @param $user String: username - * @param $password String: password - * @param $dbName String: database name on the server - * @param $flags Integer: database behaviour flags (optional, unused) - * @param $schema String - */ - public function __construct( $server = false, $user = false, - $password = false, - $dbName = false, $flags = 0, - $schema = self::USE_GLOBAL ) - { - global $wgDBmwschema; - - if ( $schema == self::USE_GLOBAL ) { - $this->mSchema = $wgDBmwschema; - } 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 ); - 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." ); - } - } - - /** - * Outputs debug information in the appropriate place - * @param $string String: the relevant debug message - */ - 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 - * - * @param $server String: hostname - * @param $user String - * @param $password String - * @param $dbName String: database name - * @return DatabaseBase a fresh connection - */ - public function open( $server, $user, $password, $dbName ) { - wfProfileIn( __METHOD__ ); - - # Load IBM DB2 driver if missing - wfDl( 'ibm_db2' ); - - # Test for IBM DB2 support, to avoid suppressed fatal error - if ( !function_exists( 'db2_connect' ) ) { - throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" ); - } - - global $wgDBport; - - // Close existing connection - $this->close(); - // Cache conn info - $this->mServer = $server; - $this->mPort = $port = $wgDBport; - $this->mUser = $user; - $this->mPassword = $password; - $this->mDBname = $dbName; - - $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" ); - wfProfileOut( __METHOD__ ); - wfDebug( "DB connection error\n" ); - wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" ); - wfDebug( $this->lastError() . "\n" ); - throw new DBConnectionError( $this, $this->lastError() ); - } - - // Some MediaWiki code is still transaction-less (?). - // The strategy is to keep AutoCommit on for that code - // but switch it off whenever a transaction is begun. - db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON ); - - $this->mOpened = true; - $this->applySchema(); - - wfProfileOut( __METHOD__ ); - return $this->mConn; - } - - /** - * Opens a cataloged database connection, sets mConn - */ - protected function openCataloged( $dbName, $user, $password ) { - wfSuppressWarnings(); - $this->mConn = db2_pconnect( $dbName, $user, $password ); - wfRestoreWarnings(); - } - - /** - * Opens an uncataloged database connection, sets mConn - */ - protected function openUncataloged( $dbName, $user, $password, $server, $port ) - { - $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;"; - wfSuppressWarnings(); - $this->mConn = db2_pconnect( $dsn, "", "", array() ); - wfRestoreWarnings(); - } - - /** - * Closes a database connection, if it is open - * Returns success, true if already closed - * @return bool - */ - protected function closeConnection() { - return db2_close( $this->mConn ); - } - - /** - * Retrieves the most current database error - * Forces a database rollback - * @return bool|string - */ - public function lastError() { - $connerr = db2_conn_errormsg(); - if ( $connerr ) { - //$this->rollback( __METHOD__ ); - return $connerr; - } - $stmterr = db2_stmt_errormsg(); - if ( $stmterr ) { - //$this->rollback( __METHOD__ ); - return $stmterr; - } - - return false; - } - - /** - * Get the last error number - * Return 0 if no error - * @return integer - */ - public function lastErrno() { - $connerr = db2_conn_error(); - if ( $connerr ) { - return $connerr; - } - $stmterr = db2_stmt_error(); - if ( $stmterr ) { - return $stmterr; - } - return 0; - } - - /** - * Is a database connection open? - * @return - */ - public function isOpen() { return $this->mOpened; } - - /** - * The DBMS-dependent part of query() - * @param $sql String: SQL query. - * @return object Result object for fetch functions or false on failure - */ - protected function doQuery( $sql ) { - $this->applySchema(); - - // Needed to handle any UTF-8 encoding issues in the raw sql - // Note that we fully support prepared statements for DB2 - // prepare() and execute() should be used instead of doQuery() whenever possible - $sql = utf8_decode( $sql ); - - $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions ); - if( $ret == false ) { - $error = db2_stmt_errormsg(); - - $this->installPrint( "
    $sql
    " ); - $this->installPrint( $error ); - throw new DBUnexpectedError( $this, 'SQL error: ' - . htmlspecialchars( $error ) ); - } - $this->mLastResult = $ret; - $this->mAffectedRows = null; // Not calculated until asked for - return $ret; - } - - /** - * @return string Version information from the database - */ - public function getServerVersion() { - $info = db2_server_info( $this->mConn ); - return $info->DBMS_VER; - } - - /** - * Queries whether a given table exists - * @return boolean - */ - public function tableExists( $table, $fname = __METHOD__ ) { - $schema = $this->mSchema; - - $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' || $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 - * member variables. - * - * @param $res array|ResultWrapper SQL result object as returned from Database::query(), etc. - * @return DB2 row object - * @throws DBUnexpectedError Thrown if the database returns an error - */ - public function fetchObject( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - wfSuppressWarnings(); - $row = db2_fetch_object( $res ); - wfRestoreWarnings(); - if( $this->lastErrno() ) { - 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']. - * - * @param $res array|ResultWrapper SQL result object as returned from Database::query(), etc. - * @return ResultWrapper row object - * @throws DBUnexpectedError Thrown if the database returns an error - */ - public function fetchRow( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - if ( db2_num_rows( $res ) > 0) { - wfSuppressWarnings(); - $row = db2_fetch_array( $res ); - wfRestoreWarnings(); - if ( $this->lastErrno() ) { - throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' - . htmlspecialchars( $this->lastError() ) ); - } - return $row; - } - return false; - } - - /** - * Escapes strings - * Doesn't escape numbers - * - * @param $s String: string to escape - * @return string escaped string - */ - public function addQuotes( $s ) { - //$this->installPrint( "DB2::addQuotes( $s )\n" ); - if ( is_null( $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 { - return "'$s'"; - } - } - - /** - * Verifies that a DB2 column/field type is 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; - } - return false; - } - - /** - * Alias for addQuotes() - * @param $s String: string to escape - * @return string escaped string - */ - public function strencode( $s ) { - // Bloody useless function - // 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" ); - $s = str_replace( $from, $to, $s ); // DB2 expects '', not \' escaping - return $s; - } - - /** - * Switch into the database schema - */ - protected function applySchema() { - if ( !( $this->mSchemaSet ) ) { - $this->mSchemaSet = true; - $this->begin( __METHOD__ ); - $this->doQuery( "SET SCHEMA = $this->mSchema" ); - $this->commit( __METHOD__ ); - } - } - - /** - * Start a transaction (mandatory) - */ - protected function doBegin( $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() - */ - protected function doCommit( $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 - */ - protected function doRollback( $fname = 'DatabaseIbm_db2::rollback' ) { - db2_rollback( $this->mConn ); - // turn auto-commit back on - // not sure if this is appropriate - 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 - * LIST_SET_PREPARED - like LIST_SET, except with ? tokens as values - * @return string - */ - function makeList( $a, $mode = LIST_COMMA ) { - if ( !is_array( $a ) ) { - throw new DBUnexpectedError( $this, - '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) - if ( $mode == LIST_SET_PREPARED ) { - $first = true; - $list = ''; - foreach ( $a as $field => $value ) { - if ( !$first ) { - $list .= ", $field = ?"; - } 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) - * @return string - */ - 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 ) { - if ( stripos( $sql, 'where' ) === false ) { - return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )"; - } else { - return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )"; - } - } - return "$sql FETCH FIRST $limit ROWS ONLY "; - } - - /** - * Handle reserved keyword replacement in table names - * - * @param $name Object - * @param $format String Ignored parameter Default 'quoted'Boolean - * @return String - */ - public function tableName( $name, $format = 'quoted' ) { - // we want maximum compatibility with MySQL schema - return $name; - } - - /** - * Generates a timestamp in an insertable format - * - * @param $ts string timestamp - * @return String: timestamp value - */ - public function timestamp( $ts = 0 ) { - // TS_MW cannot be easily distinguished from an integer - return wfTimestamp( TS_DB2, $ts ); - } - - /** - * Return the next in a sequence, save the value for retrieval via insertId() - * @param $seqName String: name of a defined sequence in the database - * @return int next value in that sequence - */ - public function nextSequenceValue( $seqName ) { - // 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" ); - $row = $this->fetchRow( $res ); - $this->mInsertId = $row[0]; - return $this->mInsertId; - */ - return null; - } - - /** - * This must be called after nextSequenceVal - * @return int Last sequence value used as a primary key - */ - 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 - * of the SELECT primary_key FROM FINAL TABLE ( INSERT ... ) form - */ - 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 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. - * @param $fname String: Name of the function, for profiling - * @param $options String or Array. Valid options: IGNORE - * - * @return bool Success of insert operation. IGNORE always returns true. - */ - 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 - $options = IBM_DB2Helper::makeArray( $options ); - // format args as an array of arrays - 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 - // we'll be able to read its value after insertion - $primaryKey = false; - if ( count( $primaryKeys ) == 1 ) { - $primaryKey = $primaryKeys[0]; - } - - // get column names - $keys = array_keys( $args[0] ); - $key_count = count( $keys ); - - // If IGNORE is set, we use savepoints to emulate mysql's behavior - $ignore = in_array( 'IGNORE', $options ) ? 'mw' : ''; - - // assume success - $res = true; - // If we are not in a transaction, we need to be for savepoint trickery - if ( !$this->mTrxLevel ) { - $this->begin( __METHOD__ ); - } - - $sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES '; - if ( $key_count == 1 ) { - $sql .= '( ? )'; - } else { - $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )'; - } - $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( __METHOD__ ); - - 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 ); - if ( !$res ) { - $this->installPrint( 'Last error:' ); - $this->installPrint( $this->lastError() ); - } - // get the last inserted value into a generated column - $this->calcInsertId( $table, $primaryKey, $stmt ); - } - } 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 ); - - $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 ); - - $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 ); - $numrowsinserted++; - } - } - - $olde = error_reporting( $olde ); - // Set the affected row count for the whole operation - $this->mAffectedRows = $numrowsinserted; - } - // commit either way - $this->commit( __METHOD__ ); - $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 = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '" - . strtoupper( $table ) - . "' AND TBCREATOR = '" - . strtoupper( $schema ) - . "' AND KEYSEQ > 0" ); - - $keys = array(); - 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 $key ) { - if ( $row[$key] == null ) { - unset( $row[$key] ); - } - } - $args[$ai] = $row; - } - // return modified hash - return array( $args, $keys ); - } - - /** - * UPDATE wrapper, takes a condition array and a SET array - * - * @param $table String: The table to UPDATE - * @param $values array An array of values to SET - * @param $conds array An array of conditions ( WHERE ). Use '*' to update all rows. - * @param $fname String: The Class::Function calling this function - * ( for the log ) - * @param $options array An array of UPDATE options, can be one or - * more of IGNORE, LOW_PRIORITY - * @return Boolean - */ - 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_PREPARED ); - if ( $conds != '*' ) { - $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); - } - $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 - * @return bool|\ResultWrapper - */ - public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) { - if ( !$conds ) { - 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 ); - } - $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 - */ - public function affectedRows() { - if ( !is_null( $this->mAffectedRows ) ) { - // Forced result for simulated queries - return $this->mAffectedRows; - } - if( empty( $this->mLastResult ) ) { - return 0; - } - return db2_num_rows( $this->mLastResult ); - } - - /** - * Returns the number of rows in the result set - * Has to be called right after the corresponding select query - * @param $res Object result set - * @return Integer: number of rows - */ - public function numRows( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - - if ( $this->mNumRows ) { - return $this->mNumRows; - } else { - return 0; - } - } - - /** - * Moves the row pointer of the result set - * @param $res Object: result set - * @param $row Integer: row number - * @return bool success or failure - */ - public function dataSeek( $res, $row ) { - if ( $res instanceof ResultWrapper ) { - return $res = $res->result; - } - if ( $res instanceof IBM_DB2Result ) { - return $res->dataSeek( $row ); - } - wfDebug( "dataSeek operation in DB2 database\n" ); - return false; - } - - ### - # Fix notices in Block.php - ### - - /** - * Frees memory associated with a statement resource - * @param $res Object: statement resource to free - * @return Boolean success or failure - */ - public function freeResult( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - wfSuppressWarnings(); - $ok = db2_free_result( $res ); - wfRestoreWarnings(); - if ( !$ok ) { - throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" ); - } - } - - /** - * Returns the number of columns in a resource - * @param $res Object: statement resource - * @return Number of fields/columns in resource - */ - public function numFields( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - if ( $res instanceof IBM_DB2Result ) { - $res = $res->getResult(); - } - return db2_num_fields( $res ); - } - - /** - * Returns the nth column name - * @param $res Object: statement resource - * @param $n Integer: Index of field or column - * @return String name of nth column - */ - public function fieldName( $res, $n ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - if ( $res instanceof IBM_DB2Result ) { - $res = $res->getResult(); - } - 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 array Associative array of options - * (e.g. array( 'GROUP BY' => 'page_title' )), - * see Database::makeSelectOptions code for list of - * supported stuff - * @param $join_conds array Associative array of table join conditions (optional) - * (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() ) - { - $res = parent::select( $table, $vars, $conds, $fname, $options, - $join_conds ); - $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); - - // We must adjust for offset - if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) { - $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 - $options2 = array(); - 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 ); - $this->mNumRows = $obj->num_rows; - - return new ResultWrapper( $this, new IBM_DB2Result( $this, $res, $obj->num_rows, $vars, $sql ) ); - } - - /** - * Handles ordering, grouping, and having options ('GROUP BY' => colname) - * Has limited support for per-column options (colnum => 'DISTINCT') - * - * @private - * - * @param $options array Associative array of options to be turned into - * an SQL query, valid keys are listed in the function. - * @return Array - */ - function makeSelectOptions( $options ) { - $preLimitTail = $postLimitTail = ''; - $startOpts = ''; - - $noKeyOptions = array(); - foreach ( $options as $key => $option ) { - if ( is_numeric( $key ) ) { - $noKeyOptions[$option] = true; - } - } - - 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 - */ - public static function getSoftwareLink() { - 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'; - } - - /** - * Did the last database access fail because of deadlock? - * @return Boolean - */ - public function wasDeadlock() { - // get SQLSTATE - $err = $this->lastErrno(); - 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" ); - 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 - * @return Boolean: whether the connection exists - */ - public function ping() { - // db2_ping() doesn't exist - // Emulate - $this->close(); - $this->openUncataloged( $this->mDBName, $this->mUser, - $this->mPassword, $this->mServer, $this->mPort ); - - return false; - } - ###################################### - # Unimplemented and not applicable - ###################################### - - /** - * Only useful with fake prepare like in base Database class - * @return string - */ - 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 - * @param $table String: table name - * @param $index String: index name - * @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' ) - { - $table = $this->tableName( $table ); - $sql = <<query( $sql, $fname ); - if ( !$res ) { - return null; - } - $row = $this->fetchObject( $res ); - if ( $row != null ) { - return $row; - } else { - return false; - } - } - - /** - * Returns an information object on a table column - * @param $table String: table name - * @param $field String: column name - * @return IBM_DB2Field - */ - public function fieldInfo( $table, $field ) { - return IBM_DB2Field::fromText( $this, $table, $field ); - } - - /** - * db2_field_type() wrapper - * @param $res Object: result of executed statement - * @param $index Mixed: number or name of the column - * @return String column type - */ - public function fieldType( $res, $index ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - if ( $res instanceof IBM_DB2Result ) { - $res = $res->getResult(); - } - return db2_field_type( $res, $index ); - } - - /** - * Verifies that an index was created as unique - * @param $table String: table name - * @param $index String: index name - * @param $fname string function name for profiling - * @return Bool - */ - 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 ) ) { - return true; - } - return false; - - } - - /** - * Returns the size of a text field, or -1 for "unlimited" - * @param $table String: table name - * @param $field String: column name - * @return Integer: length or -1 for unlimited - */ - public function textFieldSize( $table, $field ) { - $table = $this->tableName( $table ); - $sql = <<query( $sql ); - $row = $this->fetchObject( $res ); - $size = $row->size; - return $size; - } - - /** - * Description is left as an exercise for the reader - * @param $b Mixed: data to be encoded - * @return IBM_DB2Blob - */ - 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"; - } - - /** - * Convert into a list of string being concatenated - * @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 ) { - // || is equivalent to CONCAT - // 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 - * @return String: SQL code - */ - public function extractUnixEpoch( $column ) { - // 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 - * - * ? = scalar value, quoted as necessary - * ! = raw SQL bit (a function for instance) - * & = filename; reads the file and inserts as a blob - * (we don't use this though...) - * @param $sql String: SQL statement with appropriate markers - * @param $func String: Name of the function, for profiling - * @return resource a prepared DB2 SQL statement - */ - public function prepare( $sql, $func = 'DB2::prepare' ) { - $stmt = db2_prepare( $this->mConn, $sql, $this->mStmtOptions ); - return $stmt; - } - - /** - * Frees resources associated with a prepared statement - * @return Boolean success or failure - */ - public function freePrepared( $prepared ) { - return db2_free_stmt( $prepared ); - } - - /** - * Execute a prepared query with the various arguments - * @param $prepared String: the prepared sql - * @param $args Mixed: either an array here, or put scalars as varargs - * @return Resource: results object - */ - public function execute( $prepared, $args = null ) { - if( !is_array( $args ) ) { - # Pull the var args - $args = func_get_args(); - array_shift( $args ); - } - $res = db2_execute( $prepared, $args ); - if ( !$res ) { - $this->installPrint( db2_stmt_errormsg() ); - } - return $res; - } - - /** - * For faking prepared SQL statements on DBs that don't support - * it directly. - * @param $preparedQuery String: a 'preparable' SQL statement - * @param $args Array of arguments to fill it with - * @return String: executable statement - */ - 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 - * @return string - */ - 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 )"; - } - - /** - * Bitwise AND of two columns or values in SQL - * Same as (fieldLeft & fieldRight) in C - * @param $fieldLeft String - * @param $fieldRight String - * @return String - */ - function bitAnd( $fieldLeft, $fieldRight ) { - return "BITAND( $fieldLeft, $fieldRight )"; - } - - /** - * Bitwise OR of two columns or values in SQL - * Same as (fieldLeft | fieldRight) in C - * @param $fieldLeft String - * @param $fieldRight String - * @return String - */ - 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; - } -} diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php index 914ab408..6c45ffaf 100644 --- a/includes/db/DatabaseMssql.php +++ b/includes/db/DatabaseMssql.php @@ -28,9 +28,9 @@ * @ingroup Database */ class DatabaseMssql extends DatabaseBase { - var $mInsertId = NULL; - var $mLastResult = NULL; - var $mAffectedRows = NULL; + var $mInsertId = null; + var $mLastResult = null; + var $mAffectedRows = null; var $mPort; @@ -61,6 +61,11 @@ class DatabaseMssql extends DatabaseBase { /** * Usually aborts on failure + * @param string $server + * @param string $user + * @param string $password + * @param string $dbName + * @throws DBConnectionError * @return bool|DatabaseBase|null */ function open( $server, $user, $password, $dbName ) { @@ -97,7 +102,7 @@ class DatabaseMssql extends DatabaseBase { $ntAuthPassTest = strtolower( $password ); // Decide which auth scenerio to use - if( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ){ + if( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ) { // Don't add credentials to $connectionInfo } else { $connectionInfo['UID'] = $user; @@ -139,7 +144,7 @@ class DatabaseMssql extends DatabaseBase { // $this->limitResult(); if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) { // massage LIMIT -> TopN - $sql = $this->LimitToTopN( $sql ) ; + $sql = $this->LimitToTopN( $sql ); } // MSSQL doesn't have EXTRACT(epoch FROM XXX) @@ -151,7 +156,7 @@ class DatabaseMssql extends DatabaseBase { // perform query $stmt = sqlsrv_query( $this->mConn, $sql ); if ( $stmt == false ) { - $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" . + $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" . "Query: " . htmlentities( $sql ) . "\n" . "Function: " . __METHOD__ . "\n"; // process each error (our driver will give us an array of errors unlike other providers) @@ -279,7 +284,7 @@ class DatabaseMssql extends DatabaseBase { * @param $vars Mixed: array or string, field name(s) to be retrieved * @param $conds Mixed: array or string, condition(s) for WHERE * @param $fname String: calling function name (use __METHOD__) for logs/profiling - * @param $options Array: associative array of options (e.g. array('GROUP BY' => 'page_title')), + * @param array $options associative array of options (e.g. array('GROUP BY' => 'page_title')), * see Database::makeSelectOptions code for list of supported stuff * @param $join_conds Array: Associative array of table join conditions (optional) * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') ) @@ -304,7 +309,7 @@ class DatabaseMssql extends DatabaseBase { * @param $vars Mixed: Array or string, field name(s) to be retrieved * @param $conds Mixed: Array or string, condition(s) for WHERE * @param $fname String: Calling function name (use __METHOD__) for logs/profiling - * @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')), + * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')), * see Database::makeSelectOptions code for list of supported stuff * @param $join_conds Array: Associative array of table join conditions (optional) * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') ) @@ -314,7 +319,7 @@ class DatabaseMssql extends DatabaseBase { if ( isset( $options['EXPLAIN'] ) ) { unset( $options['EXPLAIN'] ); } - return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); + return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); } /** @@ -348,7 +353,7 @@ class DatabaseMssql extends DatabaseBase { $sql = "sp_helpindex '" . $table . "'"; $res = $this->query( $sql, $fname ); if ( !$res ) { - return NULL; + return null; } $result = array(); @@ -380,6 +385,11 @@ class DatabaseMssql extends DatabaseBase { * * Usually aborts on failure * If errors are explicitly ignored, returns success + * @param string $table + * @param array $arrToInsert + * @param string $fname + * @param array $options + * @throws DBQueryError * @return bool */ function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) { @@ -404,7 +414,7 @@ class DatabaseMssql extends DatabaseBase { $identity = null; $tableRaw = preg_replace( '#\[([^\]]*)\]#', '$1', $table ); // strip matching square brackets from table name $res = $this->doQuery( "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'" ); - if( $res && $res->numrows() ){ + if( $res && $res->numrows() ) { // There is an identity for this table. $identity = array_pop( $res->fetch( SQLSRV_FETCH_ASSOC ) ); } @@ -421,9 +431,9 @@ class DatabaseMssql extends DatabaseBase { // iterate through foreach ($a as $k => $v ) { if ( $k == $identity ) { - if( !is_null($v) ){ + if( !is_null($v) ) { // there is a value being passed to us, we need to turn on and off inserted identity - $sqlPre = "SET IDENTITY_INSERT $table ON;" ; + $sqlPre = "SET IDENTITY_INSERT $table ON;"; $sqlPost = ";SET IDENTITY_INSERT $table OFF;"; } else { @@ -474,7 +484,7 @@ class DatabaseMssql extends DatabaseBase { } elseif ( is_array( $value ) || is_object( $value ) ) { if ( is_object( $value ) && strtolower( get_class( $value ) ) == 'blob' ) { $sql .= $this->addQuotes( $value ); - } else { + } else { $sql .= $this->addQuotes( serialize( $value ) ); } } else { @@ -488,7 +498,7 @@ class DatabaseMssql extends DatabaseBase { if ( $ret === false ) { throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname ); - } elseif ( $ret != NULL ) { + } elseif ( $ret != null ) { // remember number of rows affected $this->mAffectedRows = sqlsrv_rows_affected( $ret ); if ( !is_null($identity) ) { @@ -510,7 +520,15 @@ class DatabaseMssql extends DatabaseBase { * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes() * $conds may be "*" to copy the whole table * srcTable may be an array of tables. - * @return null|\ResultWrapper + * @param string $destTable + * @param array|string $srcTable + * @param array $varMap + * @param array $conds + * @param string $fname + * @param array $insertOptions + * @param array $selectOptions + * @throws DBQueryError + * @return null|ResultWrapper */ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect', $insertOptions = array(), $selectOptions = array() ) { @@ -518,12 +536,12 @@ class DatabaseMssql extends DatabaseBase { if ( $ret === false ) { throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), /*$sql*/ '', $fname ); - } elseif ( $ret != NULL ) { + } elseif ( $ret != null ) { // remember number of rows affected $this->mAffectedRows = sqlsrv_rows_affected( $ret ); return $ret; } - return NULL; + return null; } /** @@ -590,9 +608,9 @@ class DatabaseMssql extends DatabaseBase { } else { $sql = ' SELECT * FROM ( - SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 FROM ( - SELECT 1 AS line2, sub1.* FROM (' . $sql . ') AS sub1 - ) as sub2 + SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 FROM ( + SELECT 1 AS line2, sub1.* FROM (' . $sql . ') AS sub1 + ) as sub2 ) AS sub3 WHERE line3 BETWEEN ' . ( $offset + 1 ) . ' AND ' . ( $offset + $limit ); return $sql; @@ -720,6 +738,8 @@ class DatabaseMssql extends DatabaseBase { * Escapes a identifier for use inm SQL. * Throws an exception if it is invalid. * Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx + * @param $identifier + * @throws MWException * @return string */ private function escapeIdentifier( $identifier ) { @@ -750,17 +770,17 @@ class DatabaseMssql extends DatabaseBase { $newUser = $this->escapeIdentifier( $newUser ); $loginPassword = $this->addQuotes( $loginPassword ); - $this->doQuery("CREATE DATABASE $dbName;"); - $this->doQuery("USE $dbName;"); - $this->doQuery("CREATE SCHEMA $dbName;"); - $this->doQuery(" + $this->doQuery( "CREATE DATABASE $dbName;" ); + $this->doQuery( "USE $dbName;" ); + $this->doQuery( "CREATE SCHEMA $dbName;" ); + $this->doQuery( " CREATE LOGIN $newUser WITH PASSWORD=$loginPassword ; - "); - $this->doQuery(" + " ); + $this->doQuery( " CREATE USER $newUser FOR @@ -768,8 +788,8 @@ class DatabaseMssql extends DatabaseBase { WITH DEFAULT_SCHEMA=$dbName ; - "); - $this->doQuery(" + " ); + $this->doQuery( " GRANT BACKUP DATABASE, BACKUP LOG, @@ -784,17 +804,15 @@ class DatabaseMssql extends DatabaseBase { DATABASE::$dbName TO $newUser ; - "); - $this->doQuery(" + " ); + $this->doQuery( " GRANT CONTROL ON SCHEMA::$dbName TO $newUser ; - "); - - + " ); } function encodeBlob( $b ) { @@ -873,7 +891,7 @@ class DatabaseMssql extends DatabaseBase { /** * @private * - * @param $options Array: an associative array of options to be turned into + * @param array $options an associative array of options to be turned into * an SQL query, valid keys are listed in the function. * @return Array */ @@ -888,29 +906,23 @@ class DatabaseMssql extends DatabaseBase { } } - if ( isset( $options['GROUP BY'] ) ) { - $tailOpts .= " GROUP BY {$options['GROUP BY']}"; - } - if ( isset( $options['HAVING'] ) ) { - $tailOpts .= " HAVING {$options['GROUP BY']}"; - } - if ( isset( $options['ORDER BY'] ) ) { - $tailOpts .= " ORDER BY {$options['ORDER BY']}"; - } + $tailOpts .= $this->makeGroupByWithHaving( $options ); + + $tailOpts .= $this->makeOrderBy( $options ); if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) { $startOpts .= 'DISTINCT'; } // we want this to be compatible with the output of parent::makeSelectOptions() - return array( $startOpts, '' , $tailOpts, '' ); + return array( $startOpts, '', $tailOpts, '' ); } /** * Get the type of the DBMS, as it appears in $wgDBtype. * @return string */ - function getType(){ + function getType() { return 'mssql'; } @@ -1118,6 +1130,5 @@ class MssqlResult { public function free() { unset( $this->mRows ); - return; } } diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php index 7f389da9..27aae188 100644 --- a/includes/db/DatabaseMysql.php +++ b/includes/db/DatabaseMysql.php @@ -133,7 +133,7 @@ class DatabaseMysql extends DatabaseBase { substr( $password, 0, 3 ) . "..., error: " . $error . "\n" ); wfProfileOut( __METHOD__ ); - $this->reportConnectionError( $error ); + return $this->reportConnectionError( $error ); } if ( $dbName != '' ) { @@ -146,7 +146,7 @@ class DatabaseMysql extends DatabaseBase { "from client host " . wfHostname() . "\n" ); wfProfileOut( __METHOD__ ); - $this->reportConnectionError( "Error selecting database $dbName" ); + return $this->reportConnectionError( "Error selecting database $dbName" ); } } @@ -193,7 +193,7 @@ class DatabaseMysql extends DatabaseBase { /** * @param $res ResultWrapper - * @return object|stdClass + * @return object|bool * @throws DBUnexpectedError */ function fetchObject( $res ) { @@ -208,7 +208,7 @@ class DatabaseMysql extends DatabaseBase { // Unfortunately, mysql_fetch_object does not reset the last errno. // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as // these are the only errors mysql_fetch_object can cause. - // See http://dev.mysql.com/doc/refman/5.0/es/mysql-fetch-row.html. + // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html. if( $errno == 2000 || $errno == 2013 ) { throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) ); } @@ -217,7 +217,7 @@ class DatabaseMysql extends DatabaseBase { /** * @param $res ResultWrapper - * @return array + * @return array|bool * @throws DBUnexpectedError */ function fetchRow( $res ) { @@ -232,7 +232,7 @@ class DatabaseMysql extends DatabaseBase { // Unfortunately, mysql_fetch_array does not reset the last errno. // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as // these are the only errors mysql_fetch_object can cause. - // See http://dev.mysql.com/doc/refman/5.0/es/mysql-fetch-row.html. + // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html. if( $errno == 2000 || $errno == 2013 ) { throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) ); } @@ -251,9 +251,11 @@ class DatabaseMysql extends DatabaseBase { wfSuppressWarnings(); $n = mysql_num_rows( $res ); wfRestoreWarnings(); - if( $this->lastErrno() ) { - throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) ); - } + // Unfortunately, mysql_num_rows does not reset the last errno. + // We are not checking for any errors here, since + // these are no errors mysql_num_rows can cause. + // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html. + // See https://bugzilla.wikimedia.org/42430 return $n; } @@ -361,7 +363,7 @@ class DatabaseMysql extends DatabaseBase { * @param $options string|array * @return int */ - public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) { + public function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) { $options['EXPLAIN'] = true; $res = $this->select( $table, $vars, $conds, $fname, $options ); if ( $res === false ) { @@ -393,7 +395,7 @@ class DatabaseMysql extends DatabaseBase { for( $i = 0; $i < $n; $i++ ) { $meta = mysql_fetch_field( $res->result, $i ); if( $field == $meta->name ) { - return new MySQLField($meta); + return new MySQLField( $meta ); } } return false; @@ -414,6 +416,7 @@ class DatabaseMysql extends DatabaseBase { # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html $table = $this->tableName( $table ); $index = $this->indexName( $index ); + $sql = 'SHOW INDEX FROM ' . $table; $res = $this->query( $sql, $fname ); @@ -428,7 +431,6 @@ class DatabaseMysql extends DatabaseBase { $result[] = $row; } } - return empty( $result ) ? false : $result; } @@ -449,7 +451,7 @@ class DatabaseMysql extends DatabaseBase { function strencode( $s ) { $sQuoted = mysql_real_escape_string( $s, $this->mConn ); - if($sQuoted === false) { + if( $sQuoted === false ) { $this->ping(); $sQuoted = mysql_real_escape_string( $s, $this->mConn ); } @@ -597,10 +599,9 @@ class DatabaseMysql extends DatabaseBase { if ( $res && $row = $this->fetchRow( $res ) ) { wfProfileOut( $fname ); return $row[0]; - } else { - wfProfileOut( $fname ); - return false; } + wfProfileOut( $fname ); + return false; } /** @@ -686,7 +687,7 @@ class DatabaseMysql extends DatabaseBase { public function streamStatementEnd( &$sql, &$newLine ) { if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) { - preg_match( '/^DELIMITER\s+(\S+)/' , $newLine, $m ); + preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m ); $this->delimiter = $m[1]; $newLine = ''; } @@ -696,8 +697,8 @@ class DatabaseMysql extends DatabaseBase { /** * Check to see if a named lock is available. This is non-blocking. * - * @param $lockName String: name of lock to poll - * @param $method String: name of method calling us + * @param string $lockName name of lock to poll + * @param string $method name of method calling us * @return Boolean * @since 1.20 */ @@ -722,7 +723,7 @@ class DatabaseMysql extends DatabaseBase { if( $row->lockstatus == 1 ) { return true; } else { - wfDebug( __METHOD__." failed to acquire lock\n" ); + wfDebug( __METHOD__ . " failed to acquire lock\n" ); return false; } } @@ -745,6 +746,7 @@ class DatabaseMysql extends DatabaseBase { * @param $write array * @param $method string * @param $lowPriority bool + * @return bool */ public function lockTables( $read, $write, $method, $lowPriority = true ) { $items = array(); @@ -760,13 +762,16 @@ class DatabaseMysql extends DatabaseBase { } $sql = "LOCK TABLES " . implode( ',', $items ); $this->query( $sql, $method ); + return true; } /** * @param $method string + * @return bool */ public function unlockTables( $method ) { $this->query( "UNLOCK TABLES", $method ); + return true; } /** @@ -805,7 +810,8 @@ class DatabaseMysql extends DatabaseBase { * @param $delVar string * @param $joinVar string * @param $conds array|string - * @param $fname bool + * @param bool|string $fname bool + * @throws DBUnexpectedError * @return bool|ResultWrapper */ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) { @@ -889,8 +895,8 @@ class DatabaseMysql extends DatabaseBase { /** * List all tables on the database * - * @param $prefix string Only show tables with this prefix, e.g. mw_ - * @param $fname String: calling function name + * @param string $prefix Only show tables with this prefix, e.g. mw_ + * @param string $fname calling function name * @return array */ function listTables( $prefix = null, $fname = 'DatabaseMysql::listTables' ) { @@ -899,7 +905,7 @@ class DatabaseMysql extends DatabaseBase { $endArray = array(); foreach( $result as $table ) { - $vars = get_object_vars($table); + $vars = get_object_vars( $table ); $table = array_pop( $vars ); if( !$prefix || strpos( $table, $prefix ) === 0 ) { @@ -951,13 +957,6 @@ class DatabaseMysql extends DatabaseBase { } -/** - * Legacy support: Database == DatabaseMysql - * - * @deprecated in 1.16 - */ -class Database extends DatabaseMysql {} - /** * Utility class. * @ingroup Database diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php index 7d8884fb..75b3550a 100644 --- a/includes/db/DatabaseOracle.php +++ b/includes/db/DatabaseOracle.php @@ -23,7 +23,7 @@ /** * The oci8 extension is fairly weak and doesn't support oci_num_rows, among - * other things. We use a wrapper class to handle that and other + * other things. We use a wrapper class to handle that and other * Oracle-specific bits, like converting column names back to lowercase. * @ingroup Database */ @@ -69,7 +69,7 @@ class ORAResult { $this->nrows = count( $this->rows ); } - if ($this->nrows > 0) { + if ( $this->nrows > 0 ) { foreach ( $this->rows[0] as $k => $v ) { $this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) ); } @@ -80,7 +80,7 @@ class ORAResult { } public function free() { - unset($this->db); + unset( $this->db ); } public function seek( $row ) { @@ -92,7 +92,7 @@ class ORAResult { } public function numFields() { - return count($this->columns); + return count( $this->columns ); } public function fetchObject() { @@ -241,6 +241,11 @@ class DatabaseOracle extends DatabaseBase { /** * Usually aborts on failure + * @param string $server + * @param string $user + * @param string $password + * @param string $dbName + * @throws DBConnectionError * @return DatabaseBase|null */ function open( $server, $user, $password, $dbName ) { @@ -313,7 +318,7 @@ class DatabaseOracle extends DatabaseBase { protected function doQuery( $sql ) { wfDebug( "SQL: [$sql]\n" ); - if ( !mb_check_encoding( $sql ) ) { + if ( !StringUtils::isUtf8( $sql ) ) { throw new MWException( "SQL encoding is invalid\n$sql" ); } @@ -628,7 +633,7 @@ class DatabaseOracle extends DatabaseBase { } list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); if ( is_array( $srcTable ) ) { - $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); + $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); } else { $srcTable = $this->tableName( $srcTable ); } @@ -751,7 +756,7 @@ class DatabaseOracle extends DatabaseBase { function unionQueries( $sqls, $all ) { $glue = ' UNION ALL '; - return 'SELECT * ' . ( $all ? '':'/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')' ; + return 'SELECT * ' . ( $all ? '':'/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')'; } function wasDeadlock() { @@ -773,8 +778,8 @@ class DatabaseOracle extends DatabaseBase { function listTables( $prefix = null, $fname = 'DatabaseOracle::listTables' ) { $listWhere = ''; - if (!empty($prefix)) { - $listWhere = ' AND table_name LIKE \''.strtoupper($prefix).'%\''; + if ( !empty( $prefix ) ) { + $listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\''; } $owner = strtoupper( $this->mDBname ); @@ -782,12 +787,12 @@ class DatabaseOracle extends DatabaseBase { // dirty code ... i know $endArray = array(); - $endArray[] = strtoupper($prefix.'MWUSER'); - $endArray[] = strtoupper($prefix.'PAGE'); - $endArray[] = strtoupper($prefix.'IMAGE'); + $endArray[] = strtoupper( $prefix . 'MWUSER' ); + $endArray[] = strtoupper( $prefix . 'PAGE' ); + $endArray[] = strtoupper( $prefix . 'IMAGE' ); $fixedOrderTabs = $endArray; - while (($row = $result->fetchRow()) !== false) { - if (!in_array($row['table_name'], $fixedOrderTabs)) + while ( ($row = $result->fetchRow()) !== false ) { + if ( !in_array( $row['table_name'], $fixedOrderTabs ) ) $endArray[] = $row['table_name']; } @@ -795,7 +800,7 @@ class DatabaseOracle extends DatabaseBase { } public function dropTable( $tableName, $fName = 'DatabaseOracle::dropTable' ) { - $tableName = $this->tableName($tableName); + $tableName = $this->tableName( $tableName ); if( !$this->tableExists( $tableName ) ) { return false; } @@ -841,7 +846,7 @@ class DatabaseOracle extends DatabaseBase { function getServerVersion() { //better version number, fallback on driver $rset = $this->doQuery( 'SELECT version FROM product_component_version WHERE UPPER(product) LIKE \'ORACLE DATABASE%\'' ); - if ( !( $row = $rset->fetchRow() ) ) { + if ( !( $row = $rset->fetchRow() ) ) { return oci_server_version( $this->mConn ); } return $row['version']; @@ -902,7 +907,7 @@ class DatabaseOracle extends DatabaseBase { $table = array_map( array( &$this, 'tableNameInternal' ), $table ); $tableWhere = 'IN ('; foreach( $table as &$singleTable ) { - $singleTable = $this->removeIdentifierQuotes($singleTable); + $singleTable = $this->removeIdentifierQuotes( $singleTable ); if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) { return $this->mFieldInfoCache["$singleTable.$field"]; } @@ -910,14 +915,14 @@ class DatabaseOracle extends DatabaseBase { } $tableWhere = rtrim( $tableWhere, ',' ) . ')'; } else { - $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) ); + $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) ); if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) { return $this->mFieldInfoCache["$table.$field"]; } $tableWhere = '= \''.$table.'\''; } - $fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name '.$tableWhere.' and column_name = \''.$field.'\'' ); + $fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name ' . $tableWhere . ' and column_name = \'' . $field . '\'' ); if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) { $e = oci_error( $fieldInfoStmt ); $this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ ); @@ -952,7 +957,7 @@ class DatabaseOracle extends DatabaseBase { if ( is_array( $table ) ) { throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' ); } - return $this->fieldInfoMulti ($table, $field); + return $this->fieldInfoMulti( $table, $field ); } protected function doBegin( $fname = 'DatabaseOracle::begin' ) { @@ -1061,7 +1066,7 @@ class DatabaseOracle extends DatabaseBase { if ( $db == null || $db == $this->mUser ) { return true; } - $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper($db); + $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db ); $stmt = oci_parse( $this->mConn, $sql ); wfSuppressWarnings(); $success = oci_execute( $stmt ); @@ -1096,11 +1101,11 @@ class DatabaseOracle extends DatabaseBase { } public function removeIdentifierQuotes( $s ) { - return strpos($s, '/*Q*/') === FALSE ? $s : substr($s, 5); + return strpos( $s, '/*Q*/' ) === false ? $s : substr( $s, 5 ); } public function isQuotedIdentifier( $s ) { - return strpos($s, '/*Q*/') !== FALSE; + return strpos( $s, '/*Q*/' ) !== false; } private function wrapFieldForWhere( $table, &$col, &$val ) { @@ -1111,7 +1116,7 @@ class DatabaseOracle extends DatabaseBase { if ( $col_type == 'CLOB' ) { $col = 'TO_CHAR(' . $col . ')'; $val = $wgContLang->checkTitleEncoding( $val ); - } elseif ( $col_type == 'VARCHAR2' && !mb_check_encoding( $val ) ) { + } elseif ( $col_type == 'VARCHAR2' ) { $val = $wgContLang->checkTitleEncoding( $val ); } } @@ -1134,7 +1139,7 @@ class DatabaseOracle extends DatabaseBase { } function selectRow( $table, $vars, $conds, $fname = 'DatabaseOracle::selectRow', $options = array(), $join_conds = array() ) { - if ( is_array($conds) ) { + if ( is_array( $conds ) ) { $conds = $this->wrapConditionsForWhere( $table, $conds ); } return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds ); @@ -1146,7 +1151,7 @@ class DatabaseOracle extends DatabaseBase { * * @private * - * @param $options Array: an associative array of options to be turned into + * @param array $options an associative array of options to be turned into * an SQL query, valid keys are listed in the function. * @return array */ @@ -1161,15 +1166,14 @@ class DatabaseOracle extends DatabaseBase { } } - if ( isset( $options['GROUP BY'] ) ) { - $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; - } - if ( isset( $options['ORDER BY'] ) ) { - $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; + $preLimitTail .= $this->makeGroupByWithHaving( $options ); + + $preLimitTail .= $this->makeOrderBy( $options ); + + if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { + $postLimitTail .= ' FOR UPDATE'; } - # if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE'; - # if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE'; if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { $startOpts .= 'DISTINCT'; } @@ -1184,13 +1188,13 @@ class DatabaseOracle extends DatabaseBase { } public function delete( $table, $conds, $fname = 'DatabaseOracle::delete' ) { - if ( is_array($conds) ) { + if ( is_array( $conds ) ) { $conds = $this->wrapConditionsForWhere( $table, $conds ); } // a hack for deleting pages, users and images (which have non-nullable FKs) // all deletions on these tables have transactions so final failure rollbacks these updates $table = $this->tableName( $table ); - if ( $table == $this->tableName( 'user' ) ) { + if ( $table == $this->tableName( 'user' ) ) { $this->update( 'archive', array( 'ar_user' => 0 ), array( 'ar_user' => $conds['user_id'] ), $fname ); $this->update( 'ipblocks', array( 'ipb_user' => 0 ), array( 'ipb_user' => $conds['user_id'] ), $fname ); $this->update( 'image', array( 'img_user' => 0 ), array( 'img_user' => $conds['user_id'] ), $fname ); @@ -1200,7 +1204,7 @@ class DatabaseOracle extends DatabaseBase { $this->update( 'uploadstash', array( 'us_user' => 0 ), array( 'us_user' => $conds['user_id'] ), $fname ); $this->update( 'recentchanges', array( 'rc_user' => 0 ), array( 'rc_user' => $conds['user_id'] ), $fname ); $this->update( 'logging', array( 'log_user' => 0 ), array( 'log_user' => $conds['user_id'] ), $fname ); - } elseif ( $table == $this->tableName( 'image' ) ) { + } elseif ( $table == $this->tableName( 'image' ) ) { $this->update( 'oldimage', array( 'oi_name' => 0 ), array( 'oi_name' => $conds['img_name'] ), $fname ); } return parent::delete( $table, $conds, $fname ); diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php index 457bf384..f32d7758 100644 --- a/includes/db/DatabasePostgres.php +++ b/includes/db/DatabasePostgres.php @@ -176,8 +176,8 @@ class PostgresTransactionState { $old = reset( $this->mCurrentState ); $new = reset( $this->mNewState ); foreach ( self::$WATCHED as $watched ) { - if ($old !== $new) { - $this->log_changed($old, $new, $watched); + if ( $old !== $new ) { + $this->log_changed( $old, $new, $watched ); } $old = next( $this->mCurrentState ); $new = next( $this->mNewState ); @@ -197,11 +197,11 @@ class PostgresTransactionState { } protected function log_changed( $old, $new, $watched ) { - wfDebug(sprintf($watched["desc"], + wfDebug( sprintf( $watched["desc"], $this->mConn, $this->describe_changed( $old, $watched["states"] ), - $this->describe_changed( $new, $watched["states"] )) - ); + $this->describe_changed( $new, $watched["states"] ) + ) ); } } @@ -218,7 +218,7 @@ class SavepointPostgres { protected $id; protected $didbegin; - public function __construct ($dbw, $id) { + public function __construct ( $dbw, $id ) { $this->dbw = $dbw; $this->id = $id; $this->didbegin = false; @@ -232,12 +232,14 @@ class SavepointPostgres { public function __destruct() { if ( $this->didbegin ) { $this->dbw->rollback(); + $this->didbegin = false; } } public function commit() { if ( $this->didbegin ) { $this->dbw->commit(); + $this->didbegin = false; } } @@ -245,29 +247,29 @@ class SavepointPostgres { global $wgDebugDBTransactions; if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) { if ( $wgDebugDBTransactions ) { - wfDebug( sprintf ($msg_ok, $this->id ) ); + wfDebug( sprintf ( $msg_ok, $this->id ) ); } } else { - wfDebug( sprintf ($msg_failed, $this->id ) ); + wfDebug( sprintf ( $msg_failed, $this->id ) ); } } public function savepoint() { - $this->query("SAVEPOINT", + $this->query( "SAVEPOINT", "Transaction state: savepoint \"%s\" established.\n", "Transaction state: establishment of savepoint \"%s\" FAILED.\n" ); } public function release() { - $this->query("RELEASE", + $this->query( "RELEASE", "Transaction state: savepoint \"%s\" released.\n", "Transaction state: release of savepoint \"%s\" FAILED.\n" ); } public function rollback() { - $this->query("ROLLBACK TO", + $this->query( "ROLLBACK TO", "Transaction state: savepoint \"%s\" rolled back.\n", "Transaction state: rollback of savepoint \"%s\" FAILED.\n" ); @@ -325,6 +327,11 @@ class DatabasePostgres extends DatabaseBase { /** * Usually aborts on failure + * @param string $server + * @param string $user + * @param string $password + * @param string $dbName + * @throws DBConnectionError * @return DatabaseBase|null */ function open( $server, $user, $password, $dbName ) { @@ -386,6 +393,9 @@ class DatabasePostgres extends DatabaseBase { $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ ); $this->query( "SET timezone = 'GMT'", __METHOD__ ); $this->query( "SET standard_conforming_strings = on", __METHOD__ ); + if ( $this->getServerVersion() >= 9.0 ) { + $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127 + } global $wgDBmwschema; $this->determineCoreSchema( $wgDBmwschema ); @@ -472,7 +482,6 @@ class DatabasePostgres extends DatabaseBase { parent::reportQueryError( $error, $errno, $sql, $fname, false ); } - function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) { return $this->query( $sql, $fname, true ); } @@ -599,7 +608,7 @@ class DatabasePostgres extends DatabaseBase { * Takes same arguments as Database::select() * @return int */ - function estimateRowCount( $table, $vars = '*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) { + function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) { $options['EXPLAIN'] = true; $res = $this->select( $table, $vars, $conds, $fname, $options ); $rows = -1; @@ -677,7 +686,7 @@ class DatabasePostgres extends DatabaseBase { AND i.indclass[s.g] = opcls.oid AND pg_am.oid = opcls.opcmethod __INDEXATTR__; - $res = $this->query($sql, __METHOD__); + $res = $this->query( $sql, __METHOD__ ); $a = array(); if ( $res ) { foreach ( $res as $row ) { @@ -685,7 +694,7 @@ __INDEXATTR__; $row->attname, $row->opcname, $row->amname, - $row->option); + $row->option ); } } else { return null; @@ -693,7 +702,6 @@ __INDEXATTR__; return $a; } - function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) { $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'". " AND indexdef LIKE 'CREATE UNIQUE%(" . @@ -718,7 +726,7 @@ __INDEXATTR__; * @param $table String: Name of the table to insert to. * @param $args Array: Items to insert into the table. * @param $fname String: Name of the function, for profiling - * @param $options String or Array. Valid options: IGNORE + * @param string $options or Array. Valid options: IGNORE * * @return bool Success of insert operation. IGNORE always returns true. */ @@ -728,7 +736,7 @@ __INDEXATTR__; } $table = $this->tableName( $table ); - if (! isset( $this->numeric_version ) ) { + if ( !isset( $this->numeric_version ) ) { $this->getServerVersion(); } @@ -980,7 +988,7 @@ __INDEXATTR__; $endArray = array(); foreach( $result as $table ) { - $vars = get_object_vars($table); + $vars = get_object_vars( $table ); $table = array_pop( $vars ); if( !$prefix || strpos( $table, $prefix ) === 0 ) { $endArray[] = $table; @@ -1052,7 +1060,6 @@ __INDEXATTR__; return '[http://www.postgresql.org/ PostgreSQL]'; } - /** * Return current schema (executes SELECT current_schema()) * Needs transaction @@ -1061,7 +1068,7 @@ __INDEXATTR__; * @return string return default schema for the current session */ function getCurrentSchema() { - $res = $this->query( "SELECT current_schema()", __METHOD__); + $res = $this->query( "SELECT current_schema()", __METHOD__ ); $row = $this->fetchRow( $res ); return $row[0]; } @@ -1071,17 +1078,17 @@ __INDEXATTR__; * This is list does not contain magic keywords like "$user" * Needs transaction * - * @seealso getSearchPath() - * @seealso setSearchPath() + * @see getSearchPath() + * @see setSearchPath() * @since 1.19 * @return array list of actual schemas for the current sesson */ function getSchemas() { - $res = $this->query( "SELECT current_schemas(false)", __METHOD__); + $res = $this->query( "SELECT current_schemas(false)", __METHOD__ ); $row = $this->fetchRow( $res ); $schemas = array(); /* PHP pgsql support does not support array type, "{a,b}" string is returned */ - return $this->pg_array_parse($row[0], $schemas); + return $this->pg_array_parse( $row[0], $schemas ); } /** @@ -1094,10 +1101,10 @@ __INDEXATTR__; * @return array how to search for table names schemas for the current user */ function getSearchPath() { - $res = $this->query( "SHOW search_path", __METHOD__); + $res = $this->query( "SHOW search_path", __METHOD__ ); $row = $this->fetchRow( $res ); /* PostgreSQL returns SHOW values as strings */ - return explode(",", $row[0]); + return explode( ",", $row[0] ); } /** @@ -1108,7 +1115,7 @@ __INDEXATTR__; * @param $search_path array list of schemas to be searched by default */ function setSearchPath( $search_path ) { - $this->query( "SET search_path = " . implode(", ", $search_path) ); + $this->query( "SET search_path = " . implode( ", ", $search_path ) ); } /** @@ -1129,7 +1136,7 @@ __INDEXATTR__; if ( $this->schemaExists( $desired_schema ) ) { if ( in_array( $desired_schema, $this->getSchemas() ) ) { $this->mCoreSchema = $desired_schema; - wfDebug("Schema \"" . $desired_schema . "\" already in the search path\n"); + wfDebug( "Schema \"" . $desired_schema . "\" already in the search path\n" ); } else { /** * Prepend our schema (e.g. 'mediawiki') in front @@ -1141,11 +1148,11 @@ __INDEXATTR__; $this->addIdentifierQuotes( $desired_schema )); $this->setSearchPath( $search_path ); $this->mCoreSchema = $desired_schema; - wfDebug("Schema \"" . $desired_schema . "\" added to the search path\n"); + wfDebug( "Schema \"" . $desired_schema . "\" added to the search path\n" ); } } else { $this->mCoreSchema = $this->getCurrentSchema(); - wfDebug("Schema \"" . $desired_schema . "\" not found, using current \"". $this->mCoreSchema ."\"\n"); + wfDebug( "Schema \"" . $desired_schema . "\" not found, using current \"" . $this->mCoreSchema . "\"\n" ); } /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */ $this->commit( __METHOD__ ); @@ -1251,8 +1258,8 @@ SQL; } function constraintExists( $table, $constraint ) { - $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints ". - "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s", + $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints " . + "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s", $this->addQuotes( $this->getCoreSchema() ), $this->addQuotes( $table ), $this->addQuotes( $constraint ) @@ -1340,7 +1347,7 @@ SQL; * * @private * - * @param $ins String: SQL string, read from a stream (usually tables.sql) + * @param string $ins SQL string, read from a stream (usually tables.sql) * * @return string SQL string */ @@ -1364,7 +1371,7 @@ SQL; * * @private * - * @param $options Array: an associative array of options to be turned into + * @param array $options an associative array of options to be turned into * an SQL query, valid keys are listed in the function. * @return array */ @@ -1379,23 +1386,9 @@ SQL; } } - if ( isset( $options['GROUP BY'] ) ) { - $gb = is_array( $options['GROUP BY'] ) - ? implode( ',', $options['GROUP BY'] ) - : $options['GROUP BY']; - $preLimitTail .= " GROUP BY {$gb}"; - } + $preLimitTail .= $this->makeGroupByWithHaving( $options ); - if ( isset( $options['HAVING'] ) ) { - $preLimitTail .= " HAVING {$options['HAVING']}"; - } - - if ( isset( $options['ORDER BY'] ) ) { - $ob = is_array( $options['ORDER BY'] ) - ? implode( ',', $options['ORDER BY'] ) - : $options['ORDER BY']; - $preLimitTail .= " ORDER BY {$ob}"; - } + $preLimitTail .= $this->makeOrderBy( $options ); //if ( isset( $options['LIMIT'] ) ) { // $tailOpts .= $this->limitResult( '', $options['LIMIT'], @@ -1443,4 +1436,65 @@ SQL; } return parent::streamStatementEnd( $sql, $newLine ); } + + /** + * Check to see if a named lock is available. This is non-blocking. + * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS + * + * @param string $lockName name of lock to poll + * @param string $method name of method calling us + * @return Boolean + * @since 1.20 + */ + public function lockIsFree( $lockName, $method ) { + $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) ); + $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key)) + WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method ); + $row = $this->fetchObject( $result ); + return ( $row->lockstatus === 't' ); + } + + /** + * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS + * @param $lockName string + * @param $method string + * @param $timeout int + * @return bool + */ + public function lock( $lockName, $method, $timeout = 5 ) { + $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) ); + for ( $attempts = 1; $attempts <= $timeout; ++$attempts ) { + $result = $this->query( + "SELECT pg_try_advisory_lock($key) AS lockstatus", $method ); + $row = $this->fetchObject( $result ); + if ( $row->lockstatus === 't' ) { + return true; + } else { + sleep( 1 ); + } + } + wfDebug( __METHOD__." failed to acquire lock\n" ); + return false; + } + + /** + * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS + * @param $lockName string + * @param $method string + * @return bool + */ + public function unlock( $lockName, $method ) { + $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) ); + $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method ); + $row = $this->fetchObject( $result ); + return ( $row->lockstatus === 't' ); + } + + /** + * @param string $lockName + * @return string Integer + */ + private function bigintFromLockName( $lockName ) { + return wfBaseConvert( substr( sha1( $lockName ), 0, 15 ), 16, 10 ); + } } // end DatabasePostgres class diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php index f1e553d7..0789e1b1 100644 --- a/includes/db/DatabaseSqlite.php +++ b/includes/db/DatabaseSqlite.php @@ -79,11 +79,12 @@ class DatabaseSqlite extends DatabaseBase { /** Open an SQLite database and return a resource handle to it * NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases * - * @param $server - * @param $user - * @param $pass - * @param $dbName + * @param string $server + * @param string $user + * @param string $pass + * @param string $dbName * + * @throws DBConnectionError * @return PDO */ function open( $server, $user, $pass, $dbName ) { @@ -103,6 +104,7 @@ class DatabaseSqlite extends DatabaseBase { * * @param $fileName string * + * @throws DBConnectionError * @return PDO|bool SQL connection or false if failed */ function openFile( $fileName ) { @@ -125,6 +127,8 @@ class DatabaseSqlite extends DatabaseBase { # set error codes only, don't raise exceptions if ( $this->mOpened ) { $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); + # Enforce LIKE to be case sensitive, just like MySQL + $this->query( 'PRAGMA case_sensitive_like = 1' ); return true; } } @@ -140,8 +144,8 @@ class DatabaseSqlite extends DatabaseBase { /** * Generates a database file name. Explicitly public for installer. - * @param $dir String: Directory where database resides - * @param $dbName String: Database name + * @param string $dir Directory where database resides + * @param string $dbName Database name * @return String */ public static function generateFileName( $dir, $dbName ) { @@ -159,7 +163,7 @@ class DatabaseSqlite extends DatabaseBase { $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ ); if ( $res ) { $row = $res->fetchRow(); - self::$fulltextEnabled = stristr($row['sql'], 'fts' ) !== false; + self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false; } } return self::$fulltextEnabled; @@ -190,9 +194,9 @@ class DatabaseSqlite extends DatabaseBase { * Attaches external database to our connection, see http://sqlite.org/lang_attach.html * for details. * - * @param $name String: database name to be used in queries like SELECT foo FROM dbname.table - * @param $file String: database file name. If omitted, will be generated using $name and $wgSQLiteDataDir - * @param $fname String: calling function name + * @param string $name database name to be used in queries like SELECT foo FROM dbname.table + * @param string $file database file name. If omitted, will be generated using $name and $wgSQLiteDataDir + * @param string $fname calling function name * * @return ResultWrapper */ @@ -248,7 +252,7 @@ class DatabaseSqlite extends DatabaseBase { /** * @param $res ResultWrapper - * @return + * @return object|bool */ function fetchObject( $res ) { if ( $res instanceof ResultWrapper ) { @@ -274,7 +278,7 @@ class DatabaseSqlite extends DatabaseBase { /** * @param $res ResultWrapper - * @return bool|mixed + * @return array|bool */ function fetchRow( $res ) { if ( $res instanceof ResultWrapper ) { @@ -467,7 +471,7 @@ class DatabaseSqlite extends DatabaseBase { */ function makeSelectOptions( $options ) { foreach ( $options as $k => $v ) { - if ( is_numeric( $k ) && $v == 'FOR UPDATE' ) { + if ( is_numeric( $k ) && ($v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE') ) { $options[$k] = ''; } } @@ -593,7 +597,7 @@ class DatabaseSqlite extends DatabaseBase { * @return bool */ function wasErrorReissuable() { - return $this->lastErrno() == 17; // SQLITE_SCHEMA; + return $this->lastErrno() == 17; // SQLITE_SCHEMA; } /** @@ -703,6 +707,14 @@ class DatabaseSqlite extends DatabaseBase { function addQuotes( $s ) { if ( $s instanceof Blob ) { return "x'" . bin2hex( $s->fetch() ) . "'"; + } else if ( strpos( $s, "\0" ) !== false ) { + // SQLite doesn't support \0 in strings, so use the hex representation as a workaround. + // This is a known limitation of SQLite's mprintf function which PDO should work around, + // but doesn't. I have reported this to php.net as bug #63419: + // https://bugs.php.net/bug.php?id=63419 + // There was already a similar report for SQLite3::escapeString, bug #62361: + // https://bugs.php.net/bug.php?id=62361 + return "x'" . bin2hex( $s ) . "'"; } else { return $this->mConn->quote( $s ); } @@ -819,12 +831,11 @@ class DatabaseSqlite extends DatabaseBase { return $this->query( $sql, $fname ); } - /** * List all tables on the database * - * @param $prefix string Only show tables with this prefix, e.g. mw_ - * @param $fname String: calling function name + * @param string $prefix Only show tables with this prefix, e.g. mw_ + * @param string $fname calling function name * * @return array */ @@ -838,7 +849,7 @@ class DatabaseSqlite extends DatabaseBase { $endArray = array(); foreach( $result as $table ) { - $vars = get_object_vars($table); + $vars = get_object_vars( $table ); $table = array_pop( $vars ); if( !$prefix || strpos( $table, $prefix ) === 0 ) { diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php index c846788d..9a1c8bde 100644 --- a/includes/db/DatabaseUtility.php +++ b/includes/db/DatabaseUtility.php @@ -1,6 +1,6 @@ str = $s; @@ -306,4 +306,3 @@ class LikeMatch { */ interface DBMasterPos { } - diff --git a/includes/db/IORMRow.php b/includes/db/IORMRow.php index e99ba6cc..6bc0cdd2 100644 --- a/includes/db/IORMRow.php +++ b/includes/db/IORMRow.php @@ -27,13 +27,12 @@ * @file * @ingroup ORM * - * @licence GNU GPL v2 or later + * @license GNU GPL v2 or later * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ interface IORMRow { - /** * Constructor. * @@ -272,4 +271,4 @@ interface IORMRow { */ public function getTable(); -} \ No newline at end of file +} diff --git a/includes/db/IORMTable.php b/includes/db/IORMTable.php index 99413f99..36865655 100644 --- a/includes/db/IORMTable.php +++ b/includes/db/IORMTable.php @@ -23,7 +23,7 @@ * @file * @ingroup ORM * - * @licence GNU GPL v2 or later + * @license GNU GPL v2 or later * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ @@ -97,6 +97,8 @@ interface IORMTable { * Selects the the specified fields of the records matching the provided * conditions and returns them as DBDataObject. Field names get prefixed. * + * @see DatabaseBase::select() + * * @since 1.20 * * @param array|string|null $fields @@ -104,7 +106,8 @@ interface IORMTable { * @param array $options * @param string|null $functionName * - * @return ORMResult + * @return ORMResult The result set + * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode) */ public function select( $fields = null, array $conditions = array(), array $options = array(), $functionName = null ); @@ -136,6 +139,7 @@ interface IORMTable { * @param null|string $functionName * * @return ResultWrapper + * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode) */ public function rawSelect( $fields = null, array $conditions = array(), array $options = array(), $functionName = null ); @@ -229,6 +233,15 @@ interface IORMTable { */ public function has( array $conditions = array() ); + /** + * Checks if the table exists + * + * @since 1.21 + * + * @return boolean + */ + public function exists(); + /** * Returns the amount of matching records. * Condition field names get prefixed. @@ -298,6 +311,71 @@ interface IORMTable { */ public function setReadDb( $db ); + /** + * Get the ID of the any foreign wiki to use as a target for database operations + * + * @since 1.20 + * + * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used) + */ + public function getTargetWiki(); + + /** + * Set the ID of the any foreign wiki to use as a target for database operations + * + * @param string|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used) + * + * @since 1.20 + */ + public function setTargetWiki( $wiki ); + + /** + * Get the database type used for read operations. + * This is to be used instead of wfGetDB. + * + * @see LoadBalancer::getConnection + * + * @since 1.20 + * + * @return DatabaseBase The database object + */ + public function getReadDbConnection(); + + /** + * Get the database type used for read operations. + * This is to be used instead of wfGetDB. + * + * @see LoadBalancer::getConnection + * + * @since 1.20 + * + * @return DatabaseBase The database object + */ + public function getWriteDbConnection(); + + /** + * Get the database type used for read operations. + * + * @see wfGetLB + * + * @since 1.20 + * + * @return LoadBalancer The database load balancer object + */ + public function getLoadBalancer(); + + /** + * Releases the lease on the given database connection. This is useful mainly + * for connections to a foreign wiki. It does nothing for connections to the local wiki. + * + * @see LoadBalancer::reuseConnection + * + * @param DatabaseBase $db the database + * + * @since 1.20 + */ + public function releaseConnection( DatabaseBase $db ); + /** * Update the records matching the provided conditions by * setting the fields that are keys in the $values param to @@ -380,15 +458,6 @@ interface IORMTable { */ public function unprefixFieldName( $fieldName ); - /** - * Get an instance of this class. - * - * @since 1.20 - * - * @return IORMTable - */ - public static function singleton(); - /** * Get an array with fields from a database result, * that can be fed directly to the constructor or diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php index e82c54ba..d469e867 100644 --- a/includes/db/LBFactory.php +++ b/includes/db/LBFactory.php @@ -86,7 +86,7 @@ abstract class LBFactory { * Create a new load balancer object. The resulting object will be untracked, * not chronology-protected, and the caller is responsible for cleaning it up. * - * @param $wiki String: wiki ID, or false for the current wiki + * @param string $wiki wiki ID, or false for the current wiki * @return LoadBalancer */ abstract function newMainLB( $wiki = false ); @@ -94,7 +94,7 @@ abstract class LBFactory { /** * Get a cached (tracked) load balancer object. * - * @param $wiki String: wiki ID, or false for the current wiki + * @param string $wiki wiki ID, or false for the current wiki * @return LoadBalancer */ abstract function getMainLB( $wiki = false ); @@ -104,8 +104,8 @@ abstract class LBFactory { * untracked, not chronology-protected, and the caller is responsible for * cleaning it up. * - * @param $cluster String: external storage cluster, or false for core - * @param $wiki String: wiki ID, or false for the current wiki + * @param string $cluster external storage cluster, or false for core + * @param string $wiki wiki ID, or false for the current wiki * * @return LoadBalancer */ @@ -114,8 +114,8 @@ abstract class LBFactory { /** * Get a cached (tracked) load balancer for external storage * - * @param $cluster String: external storage cluster, or false for core - * @param $wiki String: wiki ID, or false for the current wiki + * @param string $cluster external storage cluster, or false for core + * @param string $wiki wiki ID, or false for the current wiki * * @return LoadBalancer */ @@ -240,7 +240,7 @@ class LBFactory_Simple extends LBFactory { function newExternalLB( $cluster, $wiki = false ) { global $wgExternalServers; if ( !isset( $wgExternalServers[$cluster] ) ) { - throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" ); + throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" ); } return new LoadBalancer( array( 'servers' => $wgExternalServers[$cluster] @@ -345,7 +345,7 @@ class ChronologyProtector { if ( $lb->getServerCount() > 1 && !empty( $this->startupPos[$masterName] ) ) { $info = $lb->parentInfo(); $pos = $this->startupPos[$masterName]; - wfDebug( __METHOD__.": LB " . $info['id'] . " waiting for master pos $pos\n" ); + wfDebug( __METHOD__ . ": LB " . $info['id'] . " waiting for master pos $pos\n" ); $lb->waitFor( $this->startupPos[$masterName] ); } } @@ -370,11 +370,11 @@ class ChronologyProtector { $db = $lb->getAnyOpenConnection( 0 ); $info = $lb->parentInfo(); if ( !$db || !$db->doneWrites() ) { - wfDebug( __METHOD__.": LB {$info['id']}, no writes done\n" ); + wfDebug( __METHOD__ . ": LB {$info['id']}, no writes done\n" ); return; } $pos = $db->getMasterPos(); - wfDebug( __METHOD__.": LB {$info['id']} has master pos $pos\n" ); + wfDebug( __METHOD__ . ": LB {$info['id']} has master pos $pos\n" ); $this->shutdownPos[$masterName] = $pos; } @@ -384,7 +384,7 @@ class ChronologyProtector { */ function shutdown() { if ( session_id() != '' && count( $this->shutdownPos ) ) { - wfDebug( __METHOD__.": saving master pos for " . + wfDebug( __METHOD__ . ": saving master pos for " . count( $this->shutdownPos ) . " master(s)\n" ); $_SESSION[__CLASS__] = $this->shutdownPos; } diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactory_Multi.php index 6008813b..2e4963d4 100644 --- a/includes/db/LBFactory_Multi.php +++ b/includes/db/LBFactory_Multi.php @@ -21,7 +21,6 @@ * @ingroup Database */ - /** * A multi-wiki, multi-master factory for Wikimedia and similar installations. * Ignores the old configuration globals @@ -70,6 +69,7 @@ class LBFactory_Multi extends LBFactory { /** * @param $conf array + * @throws MWException */ function __construct( $conf ) { $this->chronProt = new ChronologyProtector; @@ -82,7 +82,7 @@ class LBFactory_Multi extends LBFactory { foreach ( $required as $key ) { if ( !isset( $conf[$key] ) ) { - throw new MWException( __CLASS__.": $key is required in configuration" ); + throw new MWException( __CLASS__ . ": $key is required in configuration" ); } $this->$key = $conf[$key]; } @@ -153,13 +153,14 @@ class LBFactory_Multi extends LBFactory { } /** - * @param $cluster - * @param $wiki + * @param string $cluster + * @param bool $wiki + * @throws MWException * @return LoadBalancer */ function newExternalLB( $cluster, $wiki = false ) { if ( !isset( $this->externalLoads[$cluster] ) ) { - throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" ); + throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" ); } $template = $this->serverTemplate; if ( isset( $this->externalTemplateOverrides ) ) { diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactory_Single.php index 4b165b2a..7dca06d7 100644 --- a/includes/db/LBFactory_Single.php +++ b/includes/db/LBFactory_Single.php @@ -28,7 +28,7 @@ class LBFactory_Single extends LBFactory { protected $lb; /** - * @param $conf array An associative array with one member: + * @param array $conf An associative array with one member: * - connection: The DatabaseBase connection object */ function __construct( $conf ) { diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php index 0e455e0c..1e859278 100644 --- a/includes/db/LoadBalancer.php +++ b/includes/db/LoadBalancer.php @@ -37,14 +37,15 @@ class LoadBalancer { private $mLoadMonitorClass, $mLoadMonitor; /** - * @param $params Array with keys: + * @param array $params with keys: * servers Required. Array of server info structures. * masterWaitTimeout Replication lag wait timeout * loadMonitor Name of a class used to fetch server lag and load. + * @throws MWException */ function __construct( $params ) { if ( !isset( $params['servers'] ) ) { - throw new MWException( __CLASS__.': missing servers parameter' ); + throw new MWException( __CLASS__ . ': missing servers parameter' ); } $this->mServers = $params['servers']; @@ -116,34 +117,14 @@ class LoadBalancer { * Given an array of non-normalised probabilities, this function will select * an element and return the appropriate key * + * @deprecated 1.21, use ArrayUtils::pickRandom() + * * @param $weights array * - * @return int + * @return bool|int|string */ function pickRandom( $weights ) { - if ( !is_array( $weights ) || count( $weights ) == 0 ) { - return false; - } - - $sum = array_sum( $weights ); - if ( $sum == 0 ) { - # No loads on any of them - # In previous versions, this triggered an unweighted random selection, - # but this feature has been removed as of April 2006 to allow for strict - # separation of query groups. - return false; - } - $max = mt_getrandmax(); - $rand = mt_rand( 0, $max ) / $max * $sum; - - $sum = 0; - foreach ( $weights as $i => $w ) { - $sum += $w; - if ( $sum >= $rand ) { - break; - } - } - return $i; + return ArrayUtils::pickRandom( $weights ); } /** @@ -197,17 +178,18 @@ class LoadBalancer { * Side effect: opens connections to databases * @param $group bool * @param $wiki bool + * @throws MWException * @return bool|int|string */ function getReaderIndex( $group = false, $wiki = false ) { global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype; # @todo FIXME: For now, only go through all this for mysql databases - if ($wgDBtype != 'mysql') { + if ( $wgDBtype != 'mysql' ) { return $this->getWriterIndex(); } - if ( count( $this->mServers ) == 1 ) { + if ( count( $this->mServers ) == 1 ) { # Skip the load balancing if there's only one server return 0; } elseif ( $group === false and $this->mReadIndex >= 0 ) { @@ -228,7 +210,7 @@ class LoadBalancer { $nonErrorLoads = $this->mGroupLoads[$group]; } else { # No loads for this group, return false and the caller can use some other group - wfDebug( __METHOD__.": no loads for group $group\n" ); + wfDebug( __METHOD__ . ": no loads for group $group\n" ); wfProfileOut( __METHOD__ ); return false; } @@ -256,7 +238,7 @@ class LoadBalancer { $i = $this->pickRandom( $currentLoads ); } else { $i = $this->getRandomNonLagged( $currentLoads, $wiki ); - if ( $i === false && count( $currentLoads ) != 0 ) { + if ( $i === false && count( $currentLoads ) != 0 ) { # All slaves lagged. Switch to read-only mode wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode\n" ); $wgReadOnly = 'The database has been automatically locked ' . @@ -270,16 +252,16 @@ class LoadBalancer { # pickRandom() returned false # This is permanent and means the configuration or the load monitor # wants us to return false. - wfDebugLog( 'connect', __METHOD__.": pickRandom() returned false\n" ); + wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false\n" ); wfProfileOut( __METHOD__ ); return false; } - wfDebugLog( 'connect', __METHOD__.": Using reader #$i: {$this->mServers[$i]['host']}...\n" ); + wfDebugLog( 'connect', __METHOD__ . ": Using reader #$i: {$this->mServers[$i]['host']}...\n" ); $conn = $this->openConnection( $i, $wiki ); if ( !$conn ) { - wfDebugLog( 'connect', __METHOD__.": Failed connecting to $i/$wiki\n" ); + wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki\n" ); unset( $nonErrorLoads[$i] ); unset( $currentLoads[$i] ); continue; @@ -318,7 +300,7 @@ class LoadBalancer { # Some servers must have been overloaded if ( $overloadedServers == 0 ) { - throw new MWException( __METHOD__.": unexpectedly found no overloaded servers" ); + throw new MWException( __METHOD__ . ": unexpectedly found no overloaded servers" ); } # Back off for a while # Scale the sleep time by the number of connected threads, to produce a @@ -341,7 +323,7 @@ class LoadBalancer { $this->mServers[$i]['slave pos'] = $conn->getSlavePos(); } } - if ( $this->mReadIndex <=0 && $this->mLoads[$i]>0 && $i !== false ) { + if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $i !== false ) { $this->mReadIndex = $i; } } @@ -356,7 +338,7 @@ class LoadBalancer { */ function sleep( $t ) { wfProfileIn( __METHOD__ ); - wfDebug( __METHOD__.": waiting $t us\n" ); + wfDebug( __METHOD__ . ": waiting $t us\n" ); usleep( $t ); wfProfileOut( __METHOD__ ); return $t; @@ -390,7 +372,7 @@ class LoadBalancer { wfProfileIn( __METHOD__ ); $this->mWaitForPos = $pos; for ( $i = 1; $i < count( $this->mServers ); $i++ ) { - $this->doWait( $i , true ); + $this->doWait( $i, true ); } wfProfileOut( __METHOD__ ); } @@ -433,15 +415,15 @@ class LoadBalancer { } } - wfDebug( __METHOD__.": Waiting for slave #$index to catch up...\n" ); + wfDebug( __METHOD__ . ": Waiting for slave #$index to catch up...\n" ); $result = $conn->masterPosWait( $this->mWaitForPos, $this->mWaitTimeout ); if ( $result == -1 || is_null( $result ) ) { # Timed out waiting for slave, use master instead - wfDebug( __METHOD__.": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" ); + wfDebug( __METHOD__ . ": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" ); return false; } else { - wfDebug( __METHOD__.": Done\n" ); + wfDebug( __METHOD__ . ": Done\n" ); return true; } } @@ -451,9 +433,10 @@ class LoadBalancer { * This is the main entry point for this class. * * @param $i Integer: server index - * @param $groups Array: query groups - * @param $wiki String: wiki ID + * @param array $groups query groups + * @param bool|string $wiki Wiki ID * + * @throws MWException * @return DatabaseBase */ public function &getConnection( $i, $groups = array(), $wiki = false ) { @@ -476,7 +459,7 @@ class LoadBalancer { $groupIndex = $this->getReaderIndex( $groups, $wiki ); if ( $groupIndex !== false ) { $serverName = $this->getServerName( $groupIndex ); - wfDebug( __METHOD__.": using server $serverName for group $groups\n" ); + wfDebug( __METHOD__ . ": using server $serverName for group $groups\n" ); $i = $groupIndex; } } else { @@ -484,7 +467,7 @@ class LoadBalancer { $groupIndex = $this->getReaderIndex( $group, $wiki ); if ( $groupIndex !== false ) { $serverName = $this->getServerName( $groupIndex ); - wfDebug( __METHOD__.": using server $serverName for group $group\n" ); + wfDebug( __METHOD__ . ": using server $serverName for group $group\n" ); $i = $groupIndex; break; } @@ -497,16 +480,16 @@ class LoadBalancer { # Couldn't find a working server in getReaderIndex()? if ( $i === false ) { $this->mLastError = 'No working slave server: ' . $this->mLastError; - $this->reportConnectionError( $this->mErrorConnection ); wfProfileOut( __METHOD__ ); - return false; + return $this->reportConnectionError(); } } # Now we have an explicit index into the servers array $conn = $this->openConnection( $i, $wiki ); if ( !$conn ) { - $this->reportConnectionError( $this->mErrorConnection ); + wfProfileOut( __METHOD__ ); + return $this->reportConnectionError(); } wfProfileOut( __METHOD__ ); @@ -519,10 +502,11 @@ class LoadBalancer { * the same number of times as getConnection() to work. * * @param DatabaseBase $conn + * @throws MWException */ public function reuseConnection( $conn ) { - $serverIndex = $conn->getLBInfo('serverIndex'); - $refCount = $conn->getLBInfo('foreignPoolRefCount'); + $serverIndex = $conn->getLBInfo( 'serverIndex' ); + $refCount = $conn->getLBInfo( 'foreignPoolRefCount' ); $dbName = $conn->getDBname(); $prefix = $conn->tablePrefix(); if ( strval( $prefix ) !== '' ) { @@ -531,7 +515,7 @@ class LoadBalancer { $wiki = $dbName; } if ( $serverIndex === null || $refCount === null ) { - wfDebug( __METHOD__.": this connection was not opened as a foreign connection\n" ); + wfDebug( __METHOD__ . ": this connection was not opened as a foreign connection\n" ); /** * This can happen in code like: * foreach ( $dbs as $db ) { @@ -545,15 +529,15 @@ class LoadBalancer { return; } if ( $this->mConns['foreignUsed'][$serverIndex][$wiki] !== $conn ) { - throw new MWException( __METHOD__.": connection not found, has the connection been freed already?" ); + throw new MWException( __METHOD__ . ": connection not found, has the connection been freed already?" ); } $conn->setLBInfo( 'foreignPoolRefCount', --$refCount ); if ( $refCount <= 0 ) { $this->mConns['foreignFree'][$serverIndex][$wiki] = $conn; unset( $this->mConns['foreignUsed'][$serverIndex][$wiki] ); - wfDebug( __METHOD__.": freed connection $serverIndex/$wiki\n" ); + wfDebug( __METHOD__ . ": freed connection $serverIndex/$wiki\n" ); } else { - wfDebug( __METHOD__.": reference count for $serverIndex/$wiki reduced to $refCount\n" ); + wfDebug( __METHOD__ . ": reference count for $serverIndex/$wiki reduced to $refCount\n" ); } } @@ -566,7 +550,7 @@ class LoadBalancer { * error will be available via $this->mErrorConnection. * * @param $i Integer server index - * @param $wiki String wiki ID to open + * @param string $wiki wiki ID to open * @return DatabaseBase * * @access private @@ -575,7 +559,7 @@ class LoadBalancer { wfProfileIn( __METHOD__ ); if ( $wiki !== false ) { $conn = $this->openForeignConnection( $i, $wiki ); - wfProfileOut( __METHOD__); + wfProfileOut( __METHOD__ ); return $conn; } if ( isset( $this->mConns['local'][$i][0] ) ) { @@ -585,6 +569,7 @@ class LoadBalancer { $server['serverIndex'] = $i; $conn = $this->reallyOpenConnection( $server, false ); if ( $conn->isOpen() ) { + wfDebug( "Connected to database $i at {$this->mServers[$i]['host']}\n" ); $this->mConns['local'][$i][0] = $conn; } else { wfDebug( "Failed to connect to database $i at {$this->mServers[$i]['host']}\n" ); @@ -611,22 +596,22 @@ class LoadBalancer { * error will be available via $this->mErrorConnection. * * @param $i Integer: server index - * @param $wiki String: wiki ID to open + * @param string $wiki wiki ID to open * @return DatabaseBase */ function openForeignConnection( $i, $wiki ) { - wfProfileIn(__METHOD__); + wfProfileIn( __METHOD__ ); list( $dbName, $prefix ) = wfSplitWikiID( $wiki ); if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) { // Reuse an already-used connection $conn = $this->mConns['foreignUsed'][$i][$wiki]; - wfDebug( __METHOD__.": reusing connection $i/$wiki\n" ); + wfDebug( __METHOD__ . ": reusing connection $i/$wiki\n" ); } elseif ( isset( $this->mConns['foreignFree'][$i][$wiki] ) ) { // Reuse a free connection for the same wiki $conn = $this->mConns['foreignFree'][$i][$wiki]; unset( $this->mConns['foreignFree'][$i][$wiki] ); $this->mConns['foreignUsed'][$i][$wiki] = $conn; - wfDebug( __METHOD__.": reusing free connection $i/$wiki\n" ); + wfDebug( __METHOD__ . ": reusing free connection $i/$wiki\n" ); } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) { // Reuse a connection from another wiki $conn = reset( $this->mConns['foreignFree'][$i] ); @@ -641,7 +626,7 @@ class LoadBalancer { $conn->tablePrefix( $prefix ); unset( $this->mConns['foreignFree'][$i][$oldWiki] ); $this->mConns['foreignUsed'][$i][$wiki] = $conn; - wfDebug( __METHOD__.": reusing free connection from $oldWiki for $wiki\n" ); + wfDebug( __METHOD__ . ": reusing free connection from $oldWiki for $wiki\n" ); } } else { // Open a new connection @@ -650,13 +635,13 @@ class LoadBalancer { $server['foreignPoolRefCount'] = 0; $conn = $this->reallyOpenConnection( $server, $dbName ); if ( !$conn->isOpen() ) { - wfDebug( __METHOD__.": error opening connection for $i/$wiki\n" ); + wfDebug( __METHOD__ . ": error opening connection for $i/$wiki\n" ); $this->mErrorConnection = $conn; $conn = false; } else { $conn->tablePrefix( $prefix ); $this->mConns['foreignUsed'][$i][$wiki] = $conn; - wfDebug( __METHOD__.": opened new connection for $i/$wiki\n" ); + wfDebug( __METHOD__ . ": opened new connection for $i/$wiki\n" ); } } @@ -665,7 +650,7 @@ class LoadBalancer { $refCount = $conn->getLBInfo( 'foreignPoolRefCount' ); $conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 ); } - wfProfileOut(__METHOD__); + wfProfileOut( __METHOD__ ); return $conn; } @@ -690,6 +675,7 @@ class LoadBalancer { * * @param $server * @param $dbNameOverride bool + * @throws MWException * @return DatabaseBase */ function reallyOpenConnection( $server, $dbNameOverride = false ) { @@ -698,15 +684,11 @@ class LoadBalancer { 'See DefaultSettings.php entry for $wgDBservers.' ); } - $host = $server['host']; - $dbname = $server['dbname']; - if ( $dbNameOverride !== false ) { - $server['dbname'] = $dbname = $dbNameOverride; + $server['dbname'] = $dbNameOverride; } # Create object - wfDebug( "Connecting to $host $dbname...\n" ); try { $db = DatabaseBase::factory( $server['type'], $server ); } catch ( DBConnectionError $e ) { @@ -715,11 +697,6 @@ class LoadBalancer { $db = $e->db; } - if ( $db->isOpen() ) { - wfDebug( "Connected to $host $dbname.\n" ); - } else { - wfDebug( "Connection failed to $host $dbname.\n" ); - } $db->setLBInfo( $server ); if ( isset( $server['fakeSlaveLag'] ) ) { $db->setFakeSlaveLag( $server['fakeSlaveLag'] ); @@ -731,24 +708,24 @@ class LoadBalancer { } /** - * @param $conn * @throws DBConnectionError + * @return bool */ - function reportConnectionError( &$conn ) { - wfProfileIn( __METHOD__ ); + private function reportConnectionError() { + $conn = $this->mErrorConnection; // The connection which caused the error if ( !is_object( $conn ) ) { // No last connection, probably due to all servers being too busy wfLogDBError( "LB failure with no last connection. Connection error: {$this->mLastError}\n" ); - $conn = new Database; + // If all servers were busy, mLastError will contain something sensible - throw new DBConnectionError( $conn, $this->mLastError ); + throw new DBConnectionError( null, $this->mLastError ); } else { $server = $conn->getProperty( 'mServer' ); wfLogDBError( "Connection error: {$this->mLastError} ({$server})\n" ); - $conn->reportConnectionError( "{$this->mLastError} ({$server})" ); + $conn->reportConnectionError( "{$this->mLastError} ({$server})" ); // throws DBConnectionError } - wfProfileOut( __METHOD__ ); + return false; /* not reached */ } /** @@ -909,7 +886,9 @@ class LoadBalancer { foreach ( $this->mConns as $conns2 ) { foreach ( $conns2 as $conns3 ) { foreach ( $conns3 as $conn ) { - $conn->commit( __METHOD__ ); + if ( $conn->trxLevel() ) { + $conn->commit( __METHOD__, 'flush' ); + } } } } @@ -926,8 +905,8 @@ class LoadBalancer { continue; } foreach ( $conns2[$masterIndex] as $conn ) { - if ( $conn->writesOrCallbacksPending() ) { - $conn->commit( __METHOD__ ); + if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) { + $conn->commit( __METHOD__, 'flush' ); } } } @@ -954,10 +933,11 @@ class LoadBalancer { * @return bool */ function allowLagged( $mode = null ) { - if ( $mode === null) { + if ( $mode === null ) { return $this->mAllowLagged; } $this->mAllowLagged = $mode; + return $this->mAllowLagged; } /** @@ -999,7 +979,7 @@ class LoadBalancer { * May attempt to open connections to slaves on the default DB. If there is * no lag, the maximum lag will be reported as -1. * - * @param $wiki string Wiki ID, or false for the default database + * @param string $wiki Wiki ID, or false for the default database * * @return array ( host, max lag, index of max lagged host ) */ diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php index 146ac61e..ad7b3b2f 100644 --- a/includes/db/LoadMonitor.php +++ b/includes/db/LoadMonitor.php @@ -37,7 +37,7 @@ interface LoadMonitor { /** * Perform pre-connection load ratio adjustment. * @param $loads array - * @param $group String: the selected query group + * @param string $group the selected query group * @param $wiki String */ function scaleLoads( &$loads, $group = false, $wiki = false ); @@ -159,7 +159,7 @@ class LoadMonitor_MySQL implements LoadMonitor { $times = array(); foreach ( $serverIndexes as $i ) { - if ($i == 0) { # Master + if ( $i == 0 ) { # Master $times[$i] = 0; } elseif ( false !== ( $conn = $this->parent->getAnyOpenConnection( $i ) ) ) { $times[$i] = $conn->getLag(); @@ -173,7 +173,7 @@ class LoadMonitor_MySQL implements LoadMonitor { $wgMemc->set( $memcKey, $times, $expiry ); # But don't give the timestamp to the caller - unset($times['timestamp']); + unset( $times['timestamp'] ); $lagTimes = $times; wfProfileOut( __METHOD__ ); @@ -189,7 +189,7 @@ class LoadMonitor_MySQL implements LoadMonitor { if ( !$threshold ) { return 0; } - $status = $conn->getMysqlStatus("Thread%"); + $status = $conn->getMysqlStatus( "Thread%" ); if ( $status['Threads_running'] > $threshold ) { $server = $conn->getProperty( 'mServer' ); wfLogDBError( "LB backoff from $server - Threads_running = {$status['Threads_running']}\n" ); @@ -199,4 +199,3 @@ class LoadMonitor_MySQL implements LoadMonitor { } } } - diff --git a/includes/db/ORMIterator.php b/includes/db/ORMIterator.php index 090b8932..077eab0f 100644 --- a/includes/db/ORMIterator.php +++ b/includes/db/ORMIterator.php @@ -23,9 +23,9 @@ * @file * @ingroup ORM * - * @licence GNU GPL v2 or later + * @license GNU GPL v2 or later * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ interface ORMIterator extends Iterator { -} \ No newline at end of file +} diff --git a/includes/db/ORMResult.php b/includes/db/ORMResult.php index 2a5837a1..160033c4 100644 --- a/includes/db/ORMResult.php +++ b/includes/db/ORMResult.php @@ -25,7 +25,7 @@ * @file ORMResult.php * @ingroup ORM * - * @licence GNU GPL v2 or later + * @license GNU GPL v2 or later * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ diff --git a/includes/db/ORMRow.php b/includes/db/ORMRow.php index 303f3a20..6c1f27ff 100644 --- a/includes/db/ORMRow.php +++ b/includes/db/ORMRow.php @@ -27,11 +27,11 @@ * @file ORMRow.php * @ingroup ORM * - * @licence GNU GPL v2 or later + * @license GNU GPL v2 or later * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ -abstract class ORMRow implements IORMRow { +class ORMRow implements IORMRow { /** * The fields of the object. @@ -120,7 +120,8 @@ abstract class ORMRow implements IORMRow { $result = $this->table->rawSelectRow( $this->table->getPrefixedFields( $fields ), array( $this->table->getPrefixedField( 'id' ) => $this->getId() ), - array( 'LIMIT' => 1 ) + array( 'LIMIT' => 1 ), + __METHOD__ ); if ( $result !== false ) { @@ -138,8 +139,9 @@ abstract class ORMRow implements IORMRow { * * @since 1.20 * - * @param string $name - * @param mixed $default + * @param string $name Field name + * @param $default mixed: Default value to return when none is found + * (default: null) * * @throws MWException * @return mixed @@ -159,7 +161,7 @@ abstract class ORMRow implements IORMRow { * * @since 1.20 * - * @param string$name + * @param $name string * * @return mixed */ @@ -259,11 +261,18 @@ abstract class ORMRow implements IORMRow { if ( array_key_exists( $name, $this->fields ) ) { $value = $this->fields[$name]; + // Skip null id fields so that the DBMS can set the default. + if ( $name === 'id' && is_null ( $value ) ) { + continue; + } + switch ( $type ) { case 'array': $value = (array)$value; + // fall-through! case 'blob': $value = serialize( $value ); + // fall-through! } $values[$this->table->getPrefixedField( $name )] = $value; @@ -346,7 +355,7 @@ abstract class ORMRow implements IORMRow { * @return boolean Success indicator */ protected function saveExisting( $functionName = null ) { - $dbw = wfGetDB( DB_MASTER ); + $dbw = $this->table->getWriteDbConnection(); $success = $dbw->update( $this->table->getName(), @@ -355,6 +364,8 @@ abstract class ORMRow implements IORMRow { is_null( $functionName ) ? __METHOD__ : $functionName ); + $this->table->releaseConnection( $dbw ); + // DatabaseBase::update does not always return true for success as documented... return $success !== false; } @@ -382,13 +393,13 @@ abstract class ORMRow implements IORMRow { * @return boolean Success indicator */ protected function insert( $functionName = null, array $options = null ) { - $dbw = wfGetDB( DB_MASTER ); + $dbw = $this->table->getWriteDbConnection(); $success = $dbw->insert( $this->table->getName(), $this->getWriteValues(), is_null( $functionName ) ? __METHOD__ : $functionName, - is_null( $options ) ? array( 'IGNORE' ) : $options + $options ); // DatabaseBase::insert does not always return true for success as documented... @@ -398,6 +409,8 @@ abstract class ORMRow implements IORMRow { $this->setField( 'id', $dbw->insertId() ); } + $this->table->releaseConnection( $dbw ); + return $success; } @@ -411,7 +424,7 @@ abstract class ORMRow implements IORMRow { public function remove() { $this->beforeRemove(); - $success = $this->table->delete( array( 'id' => $this->getId() ) ); + $success = $this->table->delete( array( 'id' => $this->getId() ), __METHOD__ ); // DatabaseBase::delete does not always return true for success as documented... $success = $success !== false; @@ -446,8 +459,8 @@ abstract class ORMRow implements IORMRow { } /** - * Gets called after successfull removal. - * Can be overriden to get rid of linked data. + * Gets called after successful removal. + * Can be overridden to get rid of linked data. * * @since 1.20 */ @@ -501,11 +514,7 @@ abstract class ORMRow implements IORMRow { $value = (float)$value; break; case 'bool': - if ( is_string( $value ) ) { - $value = $value !== '0'; - } elseif ( is_int( $value ) ) { - $value = $value !== 0; - } + $value = (bool)$value; break; case 'array': if ( is_string( $value ) ) { @@ -557,7 +566,7 @@ abstract class ORMRow implements IORMRow { $absoluteAmount = abs( $amount ); $isNegative = $amount < 0; - $dbw = wfGetDB( DB_MASTER ); + $dbw = $this->table->getWriteDbConnection(); $fullField = $this->table->getPrefixedField( $field ); @@ -572,6 +581,8 @@ abstract class ORMRow implements IORMRow { $this->setField( $field, $this->getField( $field ) + $amount ); } + $this->table->releaseConnection( $dbw ); + return $success; } diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php index a77074ff..bcbe94a3 100644 --- a/includes/db/ORMTable.php +++ b/includes/db/ORMTable.php @@ -19,42 +19,149 @@ * http://www.gnu.org/copyleft/gpl.html * * @since 1.20 + * Non-abstract since 1.21 * * @file ORMTable.php * @ingroup ORM * - * @licence GNU GPL v2 or later + * @license GNU GPL v2 or later * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ -abstract class ORMTable implements IORMTable { +class ORMTable extends DBAccessBase implements IORMTable { /** - * Gets the db field prefix. + * Cache for instances, used by the singleton method. * * @since 1.20 + * @deprecated since 1.21 * - * @return string + * @var ORMTable[] */ - protected abstract function getFieldPrefix(); + protected static $instanceCache = array(); /** - * Cache for instances, used by the singleton method. + * @since 1.21 * - * @since 1.20 - * @var array of DBTable + * @var string */ - protected static $instanceCache = array(); + protected $tableName; + + /** + * @since 1.21 + * + * @var string[] + */ + protected $fields = array(); + + /** + * @since 1.21 + * + * @var string + */ + protected $fieldPrefix = ''; + + /** + * @since 1.21 + * + * @var string + */ + protected $rowClass = 'ORMRow'; /** - * The database connection to use for read operations. + * @since 1.21 + * + * @var array + */ + protected $defaults = array(); + + /** + * ID of the database connection to use for read operations. * Can be changed via @see setReadDb. * * @since 1.20 + * * @var integer DB_ enum */ protected $readDb = DB_SLAVE; + /** + * Constructor. + * + * @since 1.21 + * + * @param string $tableName + * @param string[] $fields + * @param array $defaults + * @param string|null $rowClass + * @param string $fieldPrefix + */ + public function __construct( $tableName = '', array $fields = array(), array $defaults = array(), $rowClass = null, $fieldPrefix = '' ) { + $this->tableName = $tableName; + $this->fields = $fields; + $this->defaults = $defaults; + + if ( is_string( $rowClass ) ) { + $this->rowClass = $rowClass; + } + + $this->fieldPrefix = $fieldPrefix; + } + + /** + * @see IORMTable::getName + * + * @since 1.21 + * + * @return string + * @throws MWException + */ + public function getName() { + if ( $this->tableName === '' ) { + throw new MWException( 'The table name needs to be set' ); + } + + return $this->tableName; + } + + /** + * Gets the db field prefix. + * + * @since 1.20 + * + * @return string + */ + protected function getFieldPrefix() { + return $this->fieldPrefix; + } + + /** + * @see IORMTable::getRowClass + * + * @since 1.21 + * + * @return string + */ + public function getRowClass() { + return $this->rowClass; + } + + /** + * @see ORMTable::getFields + * + * @since 1.21 + * + * @return array + * @throws MWException + */ + public function getFields() { + if ( $this->fields === array() ) { + throw new MWException( 'The table needs to have one or more fields' ); + } + + return $this->fields; + } + /** * Returns a list of default field values. * field name => field value @@ -64,7 +171,7 @@ abstract class ORMTable implements IORMTable { * @return array */ public function getDefaults() { - return array(); + return $this->defaults; } /** @@ -94,8 +201,9 @@ abstract class ORMTable implements IORMTable { * @return ORMResult */ public function select( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null ) { - return new ORMResult( $this, $this->rawSelect( $fields, $conditions, $options, $functionName ) ); + array $options = array(), $functionName = null ) { + $res = $this->rawSelect( $fields, $conditions, $options, $functionName ); + return new ORMResult( $this, $res ); } /** @@ -109,10 +217,11 @@ abstract class ORMTable implements IORMTable { * @param array $options * @param string|null $functionName * - * @return array of self + * @return array of row objects + * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode). */ public function selectObjects( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null ) { + array $options = array(), $functionName = null ) { $result = $this->selectFields( $fields, $conditions, $options, false, $functionName ); $objects = array(); @@ -130,14 +239,15 @@ abstract class ORMTable implements IORMTable { * @since 1.20 * * @param null|string|array $fields - * @param array $conditions - * @param array $options - * @param null|string $functionName + * @param array $conditions + * @param array $options + * @param null|string $functionName * * @return ResultWrapper + * @throws DBQueryError if the quey failed (even if the database was in ignoreErrors mode). */ public function rawSelect( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null ) { + array $options = array(), $functionName = null ) { if ( is_null( $fields ) ) { $fields = array_keys( $this->getFields() ); } @@ -145,13 +255,39 @@ abstract class ORMTable implements IORMTable { $fields = (array)$fields; } - return wfGetDB( $this->getReadDb() )->select( + $dbr = $this->getReadDbConnection(); + $result = $dbr->select( $this->getName(), $this->getPrefixedFields( $fields ), $this->getPrefixedValues( $conditions ), is_null( $functionName ) ? __METHOD__ : $functionName, $options ); + + /* @var Exception $error */ + $error = null; + + if ( $result === false ) { + // Database connection was in "ignoreErrors" mode. We don't like that. + // So, we emulate the DBQueryError that should have been thrown. + $error = new DBQueryError( + $dbr, + $dbr->lastError(), + $dbr->lastErrno(), + $dbr->lastQuery(), + is_null( $functionName ) ? __METHOD__ : $functionName + ); + } + + $this->releaseConnection( $dbr ); + + if ( $error ) { + // Note: construct the error before releasing the connection, + // but throw it after. + throw $error; + } + + return $result; } /** @@ -177,7 +313,7 @@ abstract class ORMTable implements IORMTable { * @return array of array */ public function selectFields( $fields = null, array $conditions = array(), - array $options = array(), $collapse = true, $functionName = null ) { + array $options = array(), $collapse = true, $functionName = null ) { $objects = array(); $result = $this->rawSelect( $fields, $conditions, $options, $functionName ); @@ -223,7 +359,7 @@ abstract class ORMTable implements IORMTable { $objects = $this->select( $fields, $conditions, $options, $functionName ); - return $objects->isEmpty() ? false : $objects->current(); + return ( !$objects || $objects->isEmpty() ) ? false : $objects->current(); } /** @@ -241,15 +377,18 @@ abstract class ORMTable implements IORMTable { */ public function rawSelectRow( array $fields, array $conditions = array(), array $options = array(), $functionName = null ) { - $dbr = wfGetDB( $this->getReadDb() ); + $dbr = $this->getReadDbConnection(); - return $dbr->selectRow( + $result = $dbr->selectRow( $this->getName(), $fields, $conditions, is_null( $functionName ) ? __METHOD__ : $functionName, $options ); + + $this->releaseConnection( $dbr ); + return $result; } /** @@ -292,6 +431,21 @@ abstract class ORMTable implements IORMTable { return $this->selectRow( array( 'id' ), $conditions ) !== false; } + /** + * Checks if the table exists + * + * @since 1.21 + * + * @return boolean + */ + public function exists() { + $dbr = $this->getReadDbConnection(); + $exists = $dbr->tableExists( $this->getName() ); + $this->releaseConnection( $dbr ); + + return $exists; + } + /** * Returns the amount of matching records. * Condition field names get prefixed. @@ -310,7 +464,8 @@ abstract class ORMTable implements IORMTable { $res = $this->rawSelectRow( array( 'rowcount' => 'COUNT(*)' ), $this->getPrefixedValues( $conditions ), - $options + $options, + __METHOD__ ); return $res->rowcount; @@ -327,13 +482,18 @@ abstract class ORMTable implements IORMTable { * @return boolean Success indicator */ public function delete( array $conditions, $functionName = null ) { - return wfGetDB( DB_MASTER )->delete( + $dbw = $this->getWriteDbConnection(); + + $result = $dbw->delete( $this->getName(), $conditions === array() ? '*' : $this->getPrefixedValues( $conditions ), - $functionName + is_null( $functionName ) ? __METHOD__ : $functionName ) !== false; // DatabaseBase::delete does not always return true for success as documented... + + $this->releaseConnection( $dbw ); + return $result; } - + /** * Get API parameters for the fields supported by this object. * @@ -397,7 +557,7 @@ abstract class ORMTable implements IORMTable { } /** - * Get the database type used for read operations. + * Get the database ID used for read operations. * * @since 1.20 * @@ -408,7 +568,7 @@ abstract class ORMTable implements IORMTable { } /** - * Set the database type to use for read operations. + * Set the database ID to use for read operations, use DB_XXX constants or an index to the load balancer setup. * * @param integer $db * @@ -418,6 +578,70 @@ abstract class ORMTable implements IORMTable { $this->readDb = $db; } + /** + * Get the ID of the any foreign wiki to use as a target for database operations + * + * @since 1.20 + * + * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used) + */ + public function getTargetWiki() { + return $this->wiki; + } + + /** + * Set the ID of the any foreign wiki to use as a target for database operations + * + * @param string|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used) + * + * @since 1.20 + */ + public function setTargetWiki( $wiki ) { + $this->wiki = $wiki; + } + + /** + * Get the database type used for read operations. + * This is to be used instead of wfGetDB. + * + * @see LoadBalancer::getConnection + * + * @since 1.20 + * + * @return DatabaseBase The database object + */ + public function getReadDbConnection() { + return $this->getConnection( $this->getReadDb(), array() ); + } + + /** + * Get the database type used for read operations. + * This is to be used instead of wfGetDB. + * + * @see LoadBalancer::getConnection + * + * @since 1.20 + * + * @return DatabaseBase The database object + */ + public function getWriteDbConnection() { + return $this->getConnection( DB_MASTER, array() ); + } + + /** + * Releases the lease on the given database connection. This is useful mainly + * for connections to a foreign wiki. It does nothing for connections to the local wiki. + * + * @see LoadBalancer::reuseConnection + * + * @param DatabaseBase $db the database + * + * @since 1.20 + */ + public function releaseConnection( DatabaseBase $db ) { + parent::releaseConnection( $db ); // just make it public + } + /** * Update the records matching the provided conditions by * setting the fields that are keys in the $values param to @@ -431,14 +655,17 @@ abstract class ORMTable implements IORMTable { * @return boolean Success indicator */ public function update( array $values, array $conditions = array() ) { - $dbw = wfGetDB( DB_MASTER ); + $dbw = $this->getWriteDbConnection(); - return $dbw->update( + $result = $dbw->update( $this->getName(), $this->getPrefixedValues( $values ), $this->getPrefixedValues( $conditions ), __METHOD__ ) !== false; // DatabaseBase::update does not always return true for success as documented... + + $this->releaseConnection( $dbw ); + return $result; } /** @@ -450,6 +677,7 @@ abstract class ORMTable implements IORMTable { * @param array $conditions */ public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) { + $slave = $this->getReadDb(); $this->setReadDb( DB_MASTER ); /** @@ -461,7 +689,7 @@ abstract class ORMTable implements IORMTable { $item->save(); } - $this->setReadDb( DB_SLAVE ); + $this->setReadDb( $slave ); } /** @@ -559,6 +787,7 @@ abstract class ORMTable implements IORMTable { * Get an instance of this class. * * @since 1.20 + * @deprecated since 1.21 * * @return IORMTable */ -- cgit v1.2.2