From c1f9b1f7b1b77776192048005dcc66dcf3df2bfb Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 27 Dec 2014 15:41:37 +0100 Subject: Update to MediaWiki 1.24.1 --- includes/db/Database.php | 1325 ++++++++++++++++++++++++++++------------------ 1 file changed, 823 insertions(+), 502 deletions(-) (limited to 'includes/db/Database.php') diff --git a/includes/db/Database.php b/includes/db/Database.php index 10645608..9b783a99 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -1,4 +1,5 @@ getServerVersion(); } /** - * @return string: command delimiter used by this database engine + * @return string Command delimiter used by this database engine */ public function getDelimiter() { return $this->delimiter; @@ -318,12 +351,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Boolean, controls output of large amounts of debug information. - * @param $debug bool|null + * @param bool|null $debug * - true to enable debugging * - false to disable debugging * - omitted or null to do nothing * - * @return bool|null previous value of the flag + * @return bool|null Previous value of the flag */ public function debug( $debug = null ) { return wfSetBit( $this->mFlags, DBO_DEBUG, $debug ); @@ -347,8 +380,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * split up queries into batches using a LIMIT clause than to switch off * buffering. * - * @param $buffer null|bool - * + * @param null|bool $buffer * @return null|bool The previous value of the flag */ public function bufferResults( $buffer = null ) { @@ -368,8 +400,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * * Do not use this function outside of the Database classes. * - * @param $ignoreErrors bool|null - * + * @param null|bool $ignoreErrors * @return bool The previous value of the flag. */ public function ignoreErrors( $ignoreErrors = null ) { @@ -377,16 +408,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * Gets or sets the current transaction level. + * Gets the current transaction level. * * Historically, transactions were allowed to be "nested". This is no * longer supported, so this function really only returns a boolean. * - * @param int $level An integer (0 or 1), or omitted to leave it unchanged. * @return int The previous value */ - public function trxLevel( $level = null ) { - return wfSetVar( $this->mTrxLevel, $level ); + public function trxLevel() { + return $this->mTrxLevel; } /** @@ -407,10 +437,19 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { return wfSetVar( $this->mTablePrefix, $prefix ); } + /** + * Get/set the db schema. + * @param string $schema The database schema to set, or omitted to leave it unchanged. + * @return string The previous db schema. + */ + public function dbSchema( $schema = null ) { + return wfSetVar( $this->mSchema, $schema ); + } + /** * Set the filehandle to copy write statements to. * - * @param $fh filehandle + * @param resource $fh File handle */ public function setFileHandle( $fh ) { $this->fileHandle = $fh; @@ -423,7 +462,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @param string $name The entry of the info array to get, or null to get the * whole array * - * @return LoadBalancer|null + * @return array|mixed|null */ public function getLBInfo( $name = null ) { if ( is_null( $name ) ) { @@ -442,8 +481,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * the LB info array is set to that parameter. If it is called with two * parameters, the member with the given name is set to the given value. * - * @param $name - * @param $value + * @param string $name + * @param array $value */ public function setLBInfo( $name, $value = null ) { if ( is_null( $value ) ) { @@ -456,19 +495,19 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Set lag time in seconds for a fake slave * - * @param $lag int + * @param mixed $lag Valid values for this parameter are determined by the + * subclass, but should be a PHP scalar or array that would be sensible + * as part of $wgLBFactoryConf. */ public function setFakeSlaveLag( $lag ) { - $this->mFakeSlaveLag = $lag; } /** * Make this connection a fake master * - * @param $enabled bool + * @param bool $enabled */ public function setFakeMaster( $enabled = true ) { - $this->mFakeMaster = $enabled; } /** @@ -548,7 +587,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Return the last query that went through DatabaseBase::query() - * @return String + * @return string */ public function lastQuery() { return $this->mLastQuery; @@ -561,7 +600,18 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @return bool */ public function doneWrites() { - return $this->mDoneWrites; + return (bool)$this->mDoneWrites; + } + + /** + * Returns the last time the connection may have been used for write queries. + * Should return a timestamp if unsure. + * + * @return int|float UNIX timestamp or false + * @since 1.24 + */ + public function lastDoneWrites() { + return $this->mDoneWrites ?: false; } /** @@ -578,7 +628,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Is a connection to the database open? - * @return Boolean + * @return bool */ public function isOpen() { return $this->mOpened; @@ -587,7 +637,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Set a flag for this connection * - * @param $flag Integer: DBO_* constants from Defines.php: + * @param int $flag DBO_* constants from Defines.php: * - DBO_DEBUG: output some debug info (same as debug()) * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) * - DBO_TRX: automatically start transactions @@ -598,15 +648,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { public function setFlag( $flag ) { global $wgDebugDBTransactions; $this->mFlags |= $flag; - if ( ( $flag & DBO_TRX ) & $wgDebugDBTransactions ) { - wfDebug( "Implicit transactions are now disabled.\n" ); + if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { + wfDebug( "Implicit transactions are now enabled.\n" ); } } /** * Clear a flag for this connection * - * @param $flag: same as setFlag()'s $flag param + * @param int $flag DBO_* constants from Defines.php: + * - DBO_DEBUG: output some debug info (same as debug()) + * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) + * - DBO_TRX: automatically start transactions + * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode + * and removes it in command line mode + * - DBO_PERSISTENT: use persistant database connection */ public function clearFlag( $flag ) { global $wgDebugDBTransactions; @@ -619,8 +675,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Returns a boolean whether the flag $flag is set for this connection * - * @param $flag: same as setFlag()'s $flag param - * @return Boolean + * @param int $flag DBO_* constants from Defines.php: + * - DBO_DEBUG: output some debug info (same as debug()) + * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) + * - DBO_TRX: automatically start transactions + * - DBO_PERSISTENT: use persistant database connection + * @return bool */ public function getFlag( $flag ) { return !!( $this->mFlags & $flag ); @@ -629,8 +689,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * General read-only accessor * - * @param $name string - * + * @param string $name * @return string */ public function getProperty( $name ) { @@ -649,19 +708,42 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * Return a path to the DBMS-specific schema file, otherwise default to tables.sql + * Return a path to the DBMS-specific SQL file if it exists, + * otherwise default SQL file * + * @param string $filename * @return string */ - public function getSchemaPath() { + private function getSqlFilePath( $filename ) { global $IP; - if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) { - return "$IP/maintenance/" . $this->getType() . "/tables.sql"; + $dbmsSpecificFilePath = "$IP/maintenance/" . $this->getType() . "/$filename"; + if ( file_exists( $dbmsSpecificFilePath ) ) { + return $dbmsSpecificFilePath; } else { - return "$IP/maintenance/tables.sql"; + return "$IP/maintenance/$filename"; } } + /** + * Return a path to the DBMS-specific schema file, + * otherwise default to tables.sql + * + * @return string + */ + public function getSchemaPath() { + return $this->getSqlFilePath( 'tables.sql' ); + } + + /** + * Return a path to the DBMS-specific update key file, + * otherwise default to update-keys.sql + * + * @return string + */ + public function getUpdateKeysPath() { + return $this->getSqlFilePath( 'update-keys.sql' ); + } + # ------------------------------------------------------------------------------ # Other functions # ------------------------------------------------------------------------------ @@ -673,28 +755,39 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * connection object, by specifying no parameters to __construct(). This * feature is deprecated and should be removed. * - * FIXME: The long list of formal parameters here is not really appropriate - * for MySQL, and not at all appropriate for any other DBMS. It should be - * replaced by named parameters as in DatabaseBase::factory(). - * * DatabaseBase subclasses should not be constructed directly in external * code. DatabaseBase::factory() should be used instead. * - * @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 string $tablePrefix database table prefixes. By default use the prefix gave in LocalSettings.php - * @param bool $foreign disable some operations specific to local databases + * @param array $params Parameters passed from DatabaseBase::factory() */ - function __construct( $server = false, $user = false, $password = false, $dbName = false, - $flags = 0, $tablePrefix = 'get from global', $foreign = false - ) { - global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions; + function __construct( $params = null ) { + global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions; - $this->mFlags = $flags; + $this->mTrxAtomicLevels = new SplStack; + + if ( is_array( $params ) ) { // MW 1.22 + $server = $params['host']; + $user = $params['user']; + $password = $params['password']; + $dbName = $params['dbname']; + $flags = $params['flags']; + $tablePrefix = $params['tablePrefix']; + $schema = $params['schema']; + $foreign = $params['foreign']; + } else { // legacy calling pattern + wfDeprecated( __METHOD__ . " method called without parameter array.", "1.23" ); + $args = func_get_args(); + $server = isset( $args[0] ) ? $args[0] : false; + $user = isset( $args[1] ) ? $args[1] : false; + $password = isset( $args[2] ) ? $args[2] : false; + $dbName = isset( $args[3] ) ? $args[3] : false; + $flags = isset( $args[4] ) ? $args[4] : 0; + $tablePrefix = isset( $args[5] ) ? $args[5] : 'get from global'; + $schema = 'get from global'; + $foreign = isset( $args[6] ) ? $args[6] : false; + } + $this->mFlags = $flags; if ( $this->mFlags & DBO_DEFAULT ) { if ( $wgCommandLineMode ) { $this->mFlags &= ~DBO_TRX; @@ -716,6 +809,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { $this->mTablePrefix = $tablePrefix; } + /** Get the database schema*/ + if ( $schema == 'get from global' ) { + $this->mSchema = $wgDBmwschema; + } else { + $this->mSchema = $schema; + } + $this->mForeign = $foreign; if ( $user ) { @@ -729,13 +829,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * not restored on unserialize. */ public function __sleep() { - throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' ); + throw new MWException( 'Database serialization may cause problems, since ' . + 'the connection is not restored on wakeup.' ); } /** * Given a DB type, construct the name of the appropriate child class of * DatabaseBase. This is designed to replace all of the manual stuff like: - * $class = 'Database' . ucfirst( strtolower( $dbType ) ); + * $class = 'Database' . ucfirst( strtolower( $dbType ) ); * as well as validate against the canonical list of DB types we have * * This factory function is mostly useful for when you need to connect to a @@ -744,22 +845,23 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * database. Example uses in core: * @see LoadBalancer::reallyOpenConnection() * @see ForeignDBRepo::getMasterDB() - * @see WebInstaller_DBConnect::execute() + * @see WebInstallerDBConnect::execute() * * @since 1.18 * * @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, driver - * @return DatabaseBase subclass or null + * Valid options are: host, user, password, dbname, flags, tablePrefix, schema, driver + * @throws MWException If the database driver or extension cannot be found + * @return DatabaseBase|null DatabaseBase subclass or null */ final public static function factory( $dbType, $p = array() ) { $canonicalDBTypes = array( - 'mysql' => array( 'mysqli', 'mysql' ), + 'mysql' => array( 'mysqli', 'mysql' ), 'postgres' => array(), - 'sqlite' => array(), - 'oracle' => array(), - 'mssql' => array(), + 'sqlite' => array(), + 'oracle' => array(), + 'mssql' => array(), ); $driver = false; @@ -789,17 +891,32 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { " no viable database extension found for type '$dbType'" ); } + // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema, + // and everything else doesn't use a schema (e.g. null) + // Although postgres and oracle support schemas, we don't use them (yet) + // to maintain backwards compatibility + $defaultSchemas = array( + 'mysql' => null, + 'postgres' => null, + 'sqlite' => null, + 'oracle' => null, + 'mssql' => 'get from global', + ); + $class = 'Database' . ucfirst( $driver ); if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) { - return new $class( - isset( $p['host'] ) ? $p['host'] : false, - isset( $p['user'] ) ? $p['user'] : false, - isset( $p['password'] ) ? $p['password'] : false, - isset( $p['dbname'] ) ? $p['dbname'] : false, - isset( $p['flags'] ) ? $p['flags'] : 0, - isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global', - isset( $p['foreign'] ) ? $p['foreign'] : false + $params = array( + 'host' => isset( $p['host'] ) ? $p['host'] : false, + 'user' => isset( $p['user'] ) ? $p['user'] : false, + 'password' => isset( $p['password'] ) ? $p['password'] : false, + 'dbname' => isset( $p['dbname'] ) ? $p['dbname'] : false, + 'flags' => isset( $p['flags'] ) ? $p['flags'] : 0, + 'tablePrefix' => isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global', + 'schema' => isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType], + 'foreign' => isset( $p['foreign'] ) ? $p['foreign'] : false ); + + return new $class( $params ); } else { return null; } @@ -822,6 +939,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { if ( $this->mPHPError ) { $error = preg_replace( '!\[\]!', '', $this->mPHPError ); $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error ); + return $error; } else { return false; @@ -829,9 +947,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * @param $errno - * @param $errstr - * @access private + * @param int $errno + * @param string $errstr */ public function connectionErrorHandler( $errno, $errstr ) { $this->mPHPError = $errstr; @@ -842,13 +959,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * if it is open : commits any open transactions * * @throws MWException - * @return Bool operation success. true if already closed. + * @return bool Operation success. true if already closed. */ public function close() { if ( count( $this->mTrxIdleCallbacks ) ) { // sanity throw new MWException( "Transaction idle callbacks still pending." ); } - $this->mOpened = false; if ( $this->mConn ) { if ( $this->trxLevel() ) { if ( !$this->mTrxAutomatic ) { @@ -859,23 +975,25 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { $this->commit( __METHOD__, 'flush' ); } - $ret = $this->closeConnection(); + $closed = $this->closeConnection(); $this->mConn = false; - return $ret; } else { - return true; + $closed = true; } + $this->mOpened = false; + + return $closed; } /** * Closes underlying database connection * @since 1.20 - * @return bool: Whether connection was closed successfully + * @return bool Whether connection was closed successfully */ abstract protected function closeConnection(); /** - * @param string $error 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' ) { @@ -891,8 +1009,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * The DBMS-dependent part of query() * - * @param $sql String: SQL query. - * @return ResultWrapper Result object to feed to fetchObject, fetchRow, ...; or false on failure + * @param string $sql SQL query. + * @return ResultWrapper|bool Result object to feed to fetchObject, + * fetchRow, ...; or false on failure */ abstract protected function doQuery( $sql ); @@ -900,8 +1019,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Determine whether a query writes to the DB. * Should return true if unsure. * - * @param $sql string - * + * @param string $sql * @return bool */ public function isWriteQuery( $sql ) { @@ -921,23 +1039,23 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * * However, the query wrappers themselves should call this function. * - * @param $sql String: SQL query - * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST + * @param string $sql SQL query + * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST * comment (you can use __METHOD__ or add some extra info) - * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors... + * @param bool $tempIgnore 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 + * @return bool|ResultWrapper True for a successful write query, ResultWrapper object * for a successful read query, or false on failure if $tempIgnore set */ public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) { - global $wgUser, $wgDebugDBTransactions; + global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength; $this->mLastQuery = $sql; - if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) { + if ( $this->isWriteQuery( $sql ) ) { # Set a flag indicating that writes have been done - wfDebug( __METHOD__ . ": Writes done: $sql\n" ); - $this->mDoneWrites = true; + wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" ); + $this->mDoneWrites = microtime( true ); } # Add a comment for easy SHOW PROCESSLIST interpretation @@ -957,8 +1075,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { # If DBO_TRX is set, start a transaction if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel && - $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) - { + $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 @@ -975,10 +1093,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { # Keep track of whether the transaction has write queries pending if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) { $this->mTrxDoneWrites = true; - Profiler::instance()->transactionWritingIn( $this->mServer, $this->mDBname ); + Profiler::instance()->transactionWritingIn( + $this->mServer, $this->mDBname, $this->mTrxShortId ); } + $queryProf = ''; + $totalProf = ''; $isMaster = !is_null( $this->getLBInfo( 'master' ) ); + if ( !Profiler::instance()->isStub() ) { # generalizeSQL will probably cut down the query to reasonable # logging size most of the time. The substr is really just a sanity check. @@ -989,6 +1111,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); $totalProf = 'DatabaseBase::query'; } + # Include query transaction state + $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : ""; + + $trx = $this->mTrxLevel ? 'TRX=yes' : 'TRX=no'; wfProfileIn( $totalProf ); wfProfileIn( $queryProf ); } @@ -997,7 +1123,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { static $cnt = 0; $cnt++; - $sqlx = substr( $commentedSql, 0, 500 ); + $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength ) + : $commentedSql; $sqlx = strtr( $sqlx, "\t\n", ' ' ); $master = $isMaster ? 'master' : 'slave'; @@ -1006,6 +1133,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { $queryId = MWDebug::query( $sql, $fname, $isMaster ); + # Avoid fatals if close() was called + if ( !$this->isOpen() ) { + throw new DBUnexpectedError( $this, "DB connection was already closed." ); + } + # Do the query and handle errors $ret = $this->doQuery( $commentedSql ); @@ -1014,22 +1146,33 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { # Try reconnecting if the connection was lost if ( false === $ret && $this->wasErrorReissuable() ) { # Transaction is gone, like it or not + $hadTrx = $this->mTrxLevel; // possible lost transaction $this->mTrxLevel = 0; - $this->mTrxIdleCallbacks = array(); // cancel - $this->mTrxPreCommitCallbacks = array(); // cancel + $this->mTrxIdleCallbacks = array(); // bug 65263 + $this->mTrxPreCommitCallbacks = array(); // bug 65263 wfDebug( "Connection lost, reconnecting...\n" ); - + # Stash the last error values since ping() might clear them + $lastError = $this->lastError(); + $lastErrno = $this->lastErrno(); if ( $this->ping() ) { + global $wgRequestTime; wfDebug( "Reconnected\n" ); - $sqlx = substr( $commentedSql, 0, 500 ); + $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength ) + : $commentedSql; $sqlx = strtr( $sqlx, "\t\n", ' ' ); - global $wgRequestTime; $elapsed = round( microtime( true ) - $wgRequestTime, 3 ); if ( $elapsed < 300 ) { # Not a database error to lose a transaction after a minute or two - wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" ); + wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" ); + } + if ( $hadTrx ) { + # Leave $ret as false and let an error be reported. + # Callers may catch the exception and continue to use the DB. + $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore ); + } else { + # Should be safe to silently retry (no trx and thus no callbacks) + $ret = $this->doQuery( $commentedSql ); } - $ret = $this->doQuery( $commentedSql ); } else { wfDebug( "Failed\n" ); } @@ -1051,11 +1194,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Report a query error. Log the error, and if neither the object ignore * flag nor the $tempIgnore flag is set, throw a DBQueryError. * - * @param $error String - * @param $errno Integer - * @param $sql String - * @param $fname String - * @param $tempIgnore Boolean + * @param string $error + * @param int $errno + * @param string $sql + * @param string $fname + * @param bool $tempIgnore * @throws DBQueryError */ public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { @@ -1067,8 +1210,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { wfDebug( "SQL ERROR (ignored): $error\n" ); $this->ignoreErrors( $ignore ); } else { - $sql1line = str_replace( "\n", "\\n", $sql ); - wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" ); + $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 ); + wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" ); wfDebug( "SQL ERROR: " . $error . "\n" ); throw new DBQueryError( $this, $error, $errno, $sql, $fname ); } @@ -1083,21 +1226,22 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * & = filename; reads the file and inserts as a blob * (we don't use this though...) * - * @param $sql string - * @param $func string + * @param string $sql + * @param string $func * * @return array */ protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) { /* MySQL doesn't support prepared statements (yet), so just - pack up the query for reference. We'll manually replace - the bits later. */ + * pack up the query for reference. We'll manually replace + * the bits later. + */ return array( 'query' => $sql, 'func' => $func ); } /** * Free a prepared query, generated by prepare(). - * @param $prepared + * @param string $prepared */ protected function freePrepared( $prepared ) { /* No-op by default */ @@ -1105,8 +1249,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Execute a prepared query with the various arguments - * @param string $prepared the prepared sql - * @param $args Mixed: Either an array here, or put scalars as varargs + * @param string $prepared The prepared sql + * @param mixed $args Either an array here, or put scalars as varargs * * @return ResultWrapper */ @@ -1125,9 +1269,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * For faking prepared SQL statements on DBs that don't support it directly. * - * @param string $preparedQuery a 'preparable' SQL statement - * @param array $args of arguments to fill it with - * @return string executable SQL + * @param string $preparedQuery A 'preparable' SQL statement + * @param array $args Array of Arguments to fill it with + * @return string Executable SQL */ public function fillPrepared( $preparedQuery, $args ) { reset( $args ); @@ -1142,9 +1286,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * The arguments should be in $this->preparedArgs and must not be touched * while we're doing this. * - * @param $matches Array + * @param array $matches * @throws DBUnexpectedError - * @return String + * @return string */ protected function fillPreparedArg( $matches ) { switch ( $matches[1] ) { @@ -1165,9 +1309,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { return $arg; case '&': # return $this->addQuotes( file_get_contents( $arg ) ); - throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); + throw new DBUnexpectedError( + $this, + '& mode is not implemented. If it\'s really needed, uncomment the line above.' + ); default: - throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' ); + throw new DBUnexpectedError( + $this, + 'Received invalid match. This should never happen!' + ); } } @@ -1176,7 +1326,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * necessary to call this, just use unset() or let the variable holding * the result object go out of scope. * - * @param $res Mixed: A SQL result + * @param mixed $res A SQL result */ public function freeResult( $res ) { } @@ -1226,9 +1376,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Returns an optional USE INDEX clause to go after the table, and a * string to go at the end of the query. * - * @param array $options associative array of options to be turned into - * an SQL query, valid keys are listed in the function. - * @return Array + * @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() */ public function makeSelectOptions( $options ) { @@ -1310,7 +1460,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Returns an optional GROUP BY with an optional HAVING * - * @param array $options associative array of options + * @param array $options Associative array of options * @return string * @see DatabaseBase::select() * @since 1.21 @@ -1329,13 +1479,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { : $options['HAVING']; $sql .= ' HAVING ' . $having; } + return $sql; } /** * Returns an optional ORDER BY * - * @param array $options associative array of options + * @param array $options Associative array of options * @return string * @see DatabaseBase::select() * @since 1.21 @@ -1345,8 +1496,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { $ob = is_array( $options['ORDER BY'] ) ? implode( ',', $options['ORDER BY'] ) : $options['ORDER BY']; + return ' ORDER BY ' . $ob; } + return ''; } @@ -1359,9 +1512,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @param string|array $conds Conditions * @param string $fname Caller function name * @param array $options Query options - * @param $join_conds Array Join conditions + * @param array $join_conds Join conditions * - * @param $table string|array + * + * @param string|array $table * * May be either an array of table names, or a single string holding a table * name. If an array is given, table aliases can be specified, for example: @@ -1376,7 +1530,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * added, and various other table name mappings to be performed. * * - * @param $vars string|array + * @param string|array $vars * * May be either a field name or an array of field names. The field names * can be complete fragments of SQL, for direct inclusion into the SELECT @@ -1390,7 +1544,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * DBMS-independent. * * - * @param $conds string|array + * @param string|array $conds * * May be either a string containing a single condition, or an array of * conditions. If an array is given, the conditions constructed from each @@ -1415,7 +1569,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * - DatabaseBase::conditional() * * - * @param $options string|array + * @param string|array $options * * Optional: Array of query options. Boolean options are specified by * including them in the array as a string value with a numeric key, for @@ -1471,7 +1625,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * - SQL_NO_CACHE * * - * @param $join_conds string|array + * @param string|array $join_conds * * Optional associative array of table-specific join conditions. In the * most common case, this is unnecessary, since the join condition can be @@ -1484,7 +1638,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * * array( 'page' => array( 'LEFT JOIN', 'page_latest=rev_id' ) ) * - * @return ResultWrapper. If the query returned no rows, a ResultWrapper + * @return ResultWrapper|bool If the query returned no rows, a ResultWrapper * with no rows in it will be returned. If there was a query error, a * DBQueryError exception will be thrown, except if the "ignore errors" * option was set, in which case false will be returned. @@ -1507,14 +1661,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @param string|array $conds Conditions * @param string $fname Caller function name * @param string|array $options Query options - * @param $join_conds string|array Join conditions + * @param string|array $join_conds Join conditions * * @return string SQL query string. * @see DatabaseBase::select() */ public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, - $options = array(), $join_conds = array() ) - { + $options = array(), $join_conds = array() + ) { if ( is_array( $vars ) ) { $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) ); } @@ -1573,13 +1727,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @param array $conds Conditions * @param string $fname Caller function name * @param string|array $options Query options - * @param $join_conds array|string Join conditions + * @param array|string $join_conds Join conditions * - * @return object|bool + * @return stdClass|bool */ public function selectRow( $table, $vars, $conds, $fname = __METHOD__, - $options = array(), $join_conds = array() ) - { + $options = array(), $join_conds = array() + ) { $options = (array)$options; $options['LIMIT'] = 1; $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds ); @@ -1598,7 +1752,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * Estimate rows in dataset. + * Estimate the number of rows in dataset * * MySQL allows you to estimate the number of rows that would be returned * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using @@ -1610,16 +1764,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * * Takes the same arguments as DatabaseBase::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 = '', - $fname = __METHOD__, $options = array() ) - { + * @param string $table Table name + * @param 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 int Row count + */ + public function estimateRowCount( + $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() + ) { $rows = 0; $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options ); @@ -1631,6 +1785,36 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { return $rows; } + /** + * Get the number of rows in dataset + * + * This is useful when trying to do COUNT(*) but with a LIMIT for performance. + * + * Takes the same arguments as DatabaseBase::select(). + * + * @param string $table Table name + * @param 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 int Row count + * @since 1.24 + */ + public function selectRowCount( + $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() + ) { + $rows = 0; + $sql = $this->selectSQLText( $table, '1', $conds, $fname, $options ); + $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count" ); + + if ( $res ) { + $row = $this->fetchRow( $res ); + $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; + } + + return $rows; + } + /** * 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. @@ -1653,9 +1837,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { # All newlines, tabs, etc replaced by single space $sql = preg_replace( '/\s+/', ' ', $sql ); - # All numbers => N + # All numbers => N, + # except the ones surrounded by characters, e.g. l10n $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql ); - $sql = preg_replace( '/-?\d+/s', 'N', $sql ); + $sql = preg_replace( '/(?fieldInfo( $table, $field ); @@ -1679,10 +1864,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Usually throws a DBQueryError on failure * If errors are explicitly ignored, returns NULL on failure * - * @param $table - * @param $index - * @param $fname string - * + * @param string $table + * @param string $index + * @param string $fname * @return bool|null */ public function indexExists( $table, $index, $fname = __METHOD__ ) { @@ -1701,9 +1885,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Query whether a given table exists * - * @param $table string - * @param $fname string - * + * @param string $table + * @param string $fname * @return bool */ public function tableExists( $table, $fname = __METHOD__ ) { @@ -1715,25 +1898,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { return (bool)$res; } - /** - * mysql_field_type() wrapper - * @param $res - * @param $index - * @return string - */ - public function fieldType( $res, $index ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - - return mysql_field_type( $res, $index ); - } - /** * Determines if a given index is unique * - * @param $table string - * @param $index string + * @param string $table + * @param string $index * * @return bool */ @@ -1750,7 +1919,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Helper for DatabaseBase::insert(). * - * @param $options array + * @param array $options * @return string */ protected function makeInsertOptions( $options ) { @@ -1782,11 +1951,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * possible to determine how many rows were successfully inserted using * DatabaseBase::affectedRows(). * - * @param $table String Table name. This will be passed through - * DatabaseBase::tableName(). - * @param $a Array of rows to insert - * @param $fname String Calling function name (use __METHOD__) for logs/profiling - * @param array $options of options + * @param string $table Table name. This will be passed through + * DatabaseBase::tableName(). + * @param array $a Array of rows to insert + * @param string $fname Calling function name (use __METHOD__) for logs/profiling + * @param array $options Array of options * * @return bool */ @@ -1843,12 +2012,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * Make UPDATE options for the DatabaseBase::update function + * Make UPDATE options array for DatabaseBase::makeUpdateOptions * - * @param array $options The options passed to DatabaseBase::update - * @return string + * @param array $options + * @return array */ - protected function makeUpdateOptions( $options ) { + protected function makeUpdateOptionsArray( $options ) { if ( !is_array( $options ) ) { $options = array( $options ); } @@ -1863,31 +2032,38 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { $opts[] = 'IGNORE'; } + return $opts; + } + + /** + * Make UPDATE options for the DatabaseBase::update function + * + * @param array $options The options passed to DatabaseBase::update + * @return string + */ + protected function makeUpdateOptions( $options ) { + $opts = $this->makeUpdateOptionsArray( $options ); + return implode( ' ', $opts ); } /** * UPDATE wrapper. Takes a condition array and a SET array. * - * @param $table String name of the table to UPDATE. This will be passed through - * DatabaseBase::tableName(). - * - * @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(). - * - * @param $conds Array: An array of conditions (WHERE). See - * DatabaseBase::select() for the details of the format of - * condition arrays. Use '*' to update all rows. - * - * @param $fname String: The function name of the caller (from __METHOD__), - * for logging and profiling. - * + * @param string $table Name of the table to UPDATE. This will be passed through + * DatabaseBase::tableName(). + * @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(). + * @param array $conds An array of conditions (WHERE). See + * DatabaseBase::select() for the details of the format of condition + * arrays. Use '*' to update all rows. + * @param string $fname The function name of the caller (from __METHOD__), + * for logging and profiling. * @param array $options An array of UPDATE options, can be: - * - IGNORE: Ignore unique key conflicts - * - LOW_PRIORITY: MySQL-specific, see MySQL manual. - * @return Boolean + * - IGNORE: Ignore unique key conflicts + * - LOW_PRIORITY: MySQL-specific, see MySQL manual. + * @return bool */ function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) { $table = $this->tableName( $table ); @@ -1903,15 +2079,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Makes an encoded list of strings from an array - * @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(). - * - 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 * + * @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(). + * - 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 * @throws MWException|DBUnexpectedError * @return string */ @@ -1974,11 +2150,11 @@ abstract class DatabaseBase implements IDatabase, 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 array $data organized as 2-d - * array(baseKeyVal => array(subKeyVal => [ignored], ...), ...) - * @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. + * @param array $data Organized as 2-d + * array(baseKeyVal => array(subKeyVal => [ignored], ...), ...) + * @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 string|bool SQL fragment, or false if no items in array */ public function makeWhereFrom2d( $data, $baseKey, $subKey ) { $conds = array(); @@ -2002,8 +2178,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Return aggregated value alias * - * @param $valuedata - * @param $valuename string + * @param array $valuedata + * @param string $valuename * * @return string */ @@ -2012,7 +2188,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * @param $field + * @param string $field * @return string */ public function bitNot( $field ) { @@ -2020,8 +2196,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * @param $fieldLeft - * @param $fieldRight + * @param string $fieldLeft + * @param string $fieldRight * @return string */ public function bitAnd( $fieldLeft, $fieldRight ) { @@ -2029,8 +2205,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * @param $fieldLeft - * @param $fieldRight + * @param string $fieldLeft + * @param string $fieldRight * @return string */ public function bitOr( $fieldLeft, $fieldRight ) { @@ -2039,32 +2215,59 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Build a concatenation list to feed into a SQL query - * @param array $stringList list of raw SQL expressions; caller is responsible for any quoting - * @return String + * @param array $stringList List of raw SQL expressions; caller is + * responsible for any quoting + * @return string */ public function buildConcat( $stringList ) { return 'CONCAT(' . implode( ',', $stringList ) . ')'; } + /** + * Build a GROUP_CONCAT or equivalent statement for a query. + * + * This is useful for combining a field for several rows into a single string. + * NULL values will not appear in the output, duplicated values will appear, + * and the resulting delimiter-separated values have no defined sort order. + * Code using the results may need to use the PHP unique() or sort() methods. + * + * @param string $delim Glue to bind the results together + * @param string|array $table Table name + * @param string $field Field name + * @param string|array $conds Conditions + * @param string|array $join_conds Join conditions + * @return string SQL text + * @since 1.23 + */ + public function buildGroupConcatField( + $delim, $table, $field, $conds = '', $join_conds = array() + ) { + $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')'; + + return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')'; + } + /** * Change the current database * * @todo Explain what exactly will fail if this is not overridden. * - * @param $db + * @param string $db * * @return bool Success or failure */ public function selectDB( $db ) { - # Stub. Shouldn't cause serious problems if it's not overridden, but + # Stub. Shouldn't cause serious problems if it's not overridden, but # if your database engine supports a concept similar to MySQL's # databases you may as well. $this->mDBname = $db; + return true; } /** * Get the current DB name + * @return string */ public function getDBname() { return $this->mDBname; @@ -2072,6 +2275,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Get the server hostname or IP address + * @return string */ public function getServer() { return $this->mServer; @@ -2087,15 +2291,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * themselves. Pass the canonical name to such functions. This is only needed * when calling query() directly. * - * @param string $name database table name + * @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 - * @return String: full database name + * @return string Full database name */ public function tableName( $name, $format = 'quoted' ) { - global $wgSharedDB, $wgSharedPrefix, $wgSharedTables; + global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema; # Skip the entire process when we have a string quoted on both ends. # Note that we check the end so that we will still quote any use of # use of `database`.table. But won't break things if someone wants @@ -2119,31 +2323,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { # We reverse the explode so that database.table and table both output # the correct table. $dbDetails = explode( '.', $name, 2 ); - if ( count( $dbDetails ) == 2 ) { + if ( count( $dbDetails ) == 3 ) { + list( $database, $schema, $table ) = $dbDetails; + # We don't want any prefix added in this case + $prefix = ''; + } elseif ( count( $dbDetails ) == 2 ) { list( $database, $table ) = $dbDetails; # We don't want any prefix added in this case + # In dbs that support it, $database may actually be the schema + # but that doesn't affect any of the functionality here $prefix = ''; + $schema = null; } else { list( $table ) = $dbDetails; if ( $wgSharedDB !== null # We have a shared database && $this->mForeign == false # We're not working on a foreign database - && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`' + && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`' && in_array( $table, $wgSharedTables ) # A shared table is selected ) { $database = $wgSharedDB; + $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema; $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix; } else { $database = null; + $schema = $this->mSchema; # Default schema $prefix = $this->mTablePrefix; # Default prefix } } # Quote $table and apply the prefix if not quoted. + # $tableName might be empty if this is called from Database::replaceVars() $tableName = "{$prefix}{$table}"; - if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) ) { + if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) { $tableName = $this->addIdentifierQuotes( $tableName ); } + # Quote $schema and merge it with the table name if needed + if ( $schema !== null ) { + if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) { + $schema = $this->addIdentifierQuotes( $schema ); + } + $tableName = $schema . '.' . $tableName; + } + # Quote $database and merge it with the table name if needed if ( $database !== null ) { if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { @@ -2218,8 +2440,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Gets an array of aliased table names * - * @param $tables array( [alias] => table ) - * @return array of strings, see tableNameWithAlias() + * @param array $tables Array( [alias] => table ) + * @return string[] See tableNameWithAlias() */ public function tableNamesWithAlias( $tables ) { $retval = array(); @@ -2229,6 +2451,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } $retval[] = $this->tableNameWithAlias( $table, $alias ); } + return $retval; } @@ -2251,8 +2474,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Gets an array of aliased field names * - * @param $fields array( [alias] => field ) - * @return array of strings, see fieldNameWithAlias() + * @param array $fields Array( [alias] => field ) + * @return string[] See fieldNameWithAlias() */ public function fieldNamesWithAlias( $fields ) { $retval = array(); @@ -2262,6 +2485,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } $retval[] = $this->fieldNameWithAlias( $field, $alias ); } + return $retval; } @@ -2270,8 +2494,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * which might have a JOIN and/or USE INDEX clause * * @param array $tables ( [alias] => table ) - * @param $use_index array Same as for select() - * @param $join_conds array Same as for select() + * @param array $use_index Same as for select() + * @param array $join_conds Same as for select() * @return string */ protected function tableNamesWithUseIndexOrJOIN( @@ -2304,11 +2528,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } $retJOIN[] = $tableClause; - // Is there an INDEX clause for this table? } elseif ( isset( $use_index[$alias] ) ) { + // Is there an INDEX clause for this table? $tableClause = $this->tableNameWithAlias( $table, $alias ); $tableClause .= ' ' . $this->useIndexClause( - implode( ',', (array)$use_index[$alias] ) ); + implode( ',', (array)$use_index[$alias] ) + ); $ret[] = $tableClause; } else { @@ -2329,8 +2554,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Get the name of an index in a given table * - * @param $index - * + * @param string $index * @return string */ protected function indexName( $index ) { @@ -2351,8 +2575,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Adds quotes and backslashes. * - * @param $s string - * + * @param string $s * @return string */ public function addQuotes( $s ) { @@ -2373,8 +2596,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Since MySQL is the odd one out here the double quotes are our generic * and we implement backticks in DatabaseMysql. * - * @param $s string - * + * @param string $s * @return string */ public function addIdentifierQuotes( $s ) { @@ -2385,37 +2607,36 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Returns if the given identifier looks quoted or not according to * the database convention for quoting identifiers . * - * @param $name string - * - * @return boolean + * @param string $name + * @return bool */ public function isQuotedIdentifier( $name ) { return $name[0] == '"' && substr( $name, -1, 1 ) == '"'; } /** - * @param $s string + * @param string $s * @return string */ protected function escapeLikeInternal( $s ) { - $s = str_replace( '\\', '\\\\', $s ); - $s = $this->strencode( $s ); - $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s ); - - return $s; + return addcslashes( $s, '\%_' ); } /** - * LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match - * containing either string literals that will be escaped or tokens returned by anyChar() or anyString(). - * Alternatively, the function could be provided with an array of aforementioned parameters. + * LIKE statement wrapper, receives a variable-length argument list with + * parts of pattern to match containing either string literals that will be + * escaped or tokens returned by anyChar() or anyString(). Alternatively, + * the function could be provided with an array of aforementioned + * parameters. * - * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns a LIKE clause that searches - * for subpages of 'My page title'. - * Alternatively: $pattern = array( 'My_page_title/', $dbr->anyString() ); $query .= $dbr->buildLike( $pattern ); + * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns + * a LIKE clause that searches for subpages of 'My page title'. + * Alternatively: + * $pattern = array( 'My_page_title/', $dbr->anyString() ); + * $query .= $dbr->buildLike( $pattern ); * * @since 1.16 - * @return String: fully built LIKE statement + * @return string Fully built LIKE statement */ public function buildLike() { $params = func_get_args(); @@ -2434,7 +2655,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } } - return " LIKE '" . $s . "' "; + return " LIKE {$this->addQuotes( $s )} "; } /** @@ -2463,21 +2684,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Any implementation of this function should *not* involve reusing * sequence numbers created for rolled-back transactions. * See http://bugs.mysql.com/bug.php?id=30767 for details. - * @param $seqName string - * @return null + * @param string $seqName + * @return null|int */ public function nextSequenceValue( $seqName ) { return null; } /** - * USE INDEX clause. Unlikely to be useful for anything but MySQL. This + * USE INDEX clause. Unlikely to be useful for anything but MySQL. This * is only needed because a) MySQL must be as efficient as possible due to * its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about - * which index to pick. Anyway, other databases might have different - * indexes on a given table. So don't bother overriding this unless you're + * which index to pick. Anyway, other databases might have different + * indexes on a given table. So don't bother overriding this unless you're * MySQL. - * @param $index + * @param string $index * @return string */ public function useIndexClause( $index ) { @@ -2500,10 +2721,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * errors which wouldn't have occurred in MySQL. * * @param string $table The table to replace the row(s) in. + * @param array $uniqueIndexes Is an array of indexes. Each element may be either + * a field name or an array of field names * @param array $rows Can be either a single row to insert, or multiple rows, * in the same format as for DatabaseBase::insert() - * @param array $uniqueIndexes is an array of indexes. Each element may be either - * a field name or an array of field names * @param string $fname Calling function name (use __METHOD__) for logs/profiling */ public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) { @@ -2558,7 +2779,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * statement. * * @param string $table Table name - * @param array $rows Rows to insert + * @param array|string $rows Row(s) to insert * @param string $fname Caller function name * * @return ResultWrapper @@ -2609,26 +2830,28 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Usually throws a DBQueryError on failure. If errors are explicitly ignored, * returns success. * + * @since 1.22 + * * @param string $table Table name. This will be passed through DatabaseBase::tableName(). * @param array $rows A single row or list of rows to insert * @param array $uniqueIndexes List of single field names or field name tuples - * @param array $set 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(). + * @param array $set 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(). * @param string $fname Calling function name (use __METHOD__) for logs/profiling - * @param array $options of options - * + * @throws Exception * @return bool - * @since 1.22 */ - public function upsert( - $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__ + public function upsert( $table, array $rows, array $uniqueIndexes, array $set, + $fname = __METHOD__ ) { if ( !count( $rows ) ) { return true; // nothing to do } - $rows = is_array( reset( $rows ) ) ? $rows : array( $rows ); + + if ( !is_array( reset( $rows ) ) ) { + $rows = array( $rows ); + } if ( count( $uniqueIndexes ) ) { $clauses = array(); // list WHERE clauses that each identify a single row @@ -2684,19 +2907,18 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * * DO NOT put the join condition in $conds. * - * @param $delTable String: The table to delete from. - * @param $joinTable String: The other table. - * @param $delVar String: The variable to join on, in the first table. - * @param $joinVar String: The variable to join on, in the second table. - * @param $conds Array: Condition array of field names mapped to variables, - * ANDed together in the WHERE clause - * @param $fname String: Calling function name (use __METHOD__) for - * logs/profiling + * @param string $delTable The table to delete from. + * @param string $joinTable The other table. + * @param string $delVar The variable to join on, in the first table. + * @param string $joinVar The variable to join on, in the second table. + * @param array $conds Condition array of field names mapped to variables, + * ANDed together in the WHERE clause + * @param string $fname Calling function name (use __METHOD__) for logs/profiling * @throws DBUnexpectedError */ public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, - $fname = __METHOD__ ) - { + $fname = __METHOD__ + ) { if ( !$conds ) { throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' ); @@ -2716,9 +2938,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Returns the size of a text field, or -1 for "unlimited" * - * @param $table string - * @param $field string - * + * @param string $table + * @param string $field * @return int */ public function textFieldSize( $table, $field ) { @@ -2740,7 +2961,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * A string to insert into queries to show that they're low-priority, like - * MySQL's LOW_PRIORITY. If no such feature exists, return an empty + * MySQL's LOW_PRIORITY. If no such feature exists, return an empty * string and nothing bad should happen. * * @return string Returns the text of the low priority option if it is @@ -2754,10 +2975,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * DELETE query wrapper. * * @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 string $fname name of the calling function - * + * @param string|array $conds Array of conditions. See $conds in DatabaseBase::select() + * for the format. Use $conds == "*" to delete all rows + * @param string $fname Name of the calling function * @throws DBUnexpectedError * @return bool|ResultWrapper */ @@ -2787,7 +3007,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @param string|array $srcTable May be either a table name, or an array of table names * to include in a join. * - * @param array $varMap 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() @@ -2807,14 +3027,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { */ public function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__, - $insertOptions = array(), $selectOptions = array() ) - { + $insertOptions = array(), $selectOptions = array() + ) { $destTable = $this->tableName( $destTable ); - if ( is_array( $insertOptions ) ) { - $insertOptions = implode( ' ', $insertOptions ); + if ( !is_array( $insertOptions ) ) { + $insertOptions = array( $insertOptions ); } + $insertOptions = $this->makeInsertOptions( $insertOptions ); + if ( !is_array( $selectOptions ) ) { $selectOptions = array( $selectOptions ); } @@ -2844,22 +3066,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * Construct a LIMIT query with optional offset. This is used for query - * pages. The SQL should be adjusted so that only the first $limit rows - * are returned. If $offset is provided as well, then the first $offset + * Construct a LIMIT query with optional offset. This is used for query + * pages. The SQL should be adjusted so that only the first $limit rows + * are returned. If $offset is provided as well, then the first $offset * rows should be discarded, and the next $limit rows should be returned. * If the result of the query is not ordered, then the rows to be returned * are theoretically arbitrary. * * $sql is expected to be a SELECT, if that makes a difference. * - * The version provided by default works in MySQL and SQLite. It will very + * The version provided by default works in MySQL and SQLite. It will very * likely need to be overridden for most other DBMSes. * * @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) - * + * @param int $limit The SQL limit + * @param int|bool $offset The SQL offset (default false) * @throws DBUnexpectedError * @return string */ @@ -2867,6 +3088,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { if ( !is_numeric( $limit ) ) { throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); } + return "$sql LIMIT " . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" ) . "{$limit} "; @@ -2875,7 +3097,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries * within the UNION construct. - * @return Boolean + * @return bool */ public function unionSupportsOrderAndLimit() { return true; // True for almost every DB supported @@ -2886,27 +3108,29 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * This is used for providing overload point for other DB abstractions * not compatible with the MySQL syntax. * @param array $sqls SQL statements to combine - * @param $all Boolean: use UNION ALL - * @return String: SQL fragment + * @param bool $all Use UNION ALL + * @return string SQL fragment */ public function unionQueries( $sqls, $all ) { $glue = $all ? ') UNION ALL (' : ') UNION ('; + return '(' . implode( $glue, $sqls ) . ')'; } /** - * Returns an SQL expression for a simple conditional. This doesn't need + * Returns an SQL expression for a simple conditional. This doesn't need * to be overridden unless CASE isn't supported in your DBMS. * * @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 + * @return string SQL fragment */ public function conditional( $cond, $trueVal, $falseVal ) { if ( is_array( $cond ) ) { $cond = $this->makeList( $cond, LIST_AND ); } + return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; } @@ -2914,9 +3138,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Returns a comand for str_replace function in SQL query. * Uses REPLACE() in MySQL * - * @param string $orig column to modify - * @param string $old column to seek - * @param string $new 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 */ @@ -3027,9 +3251,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { if ( $tries <= 0 ) { $this->rollback( __METHOD__ ); $this->reportQueryError( $error, $errno, $sql, $fname ); + return false; } else { $this->commit( __METHOD__ ); + return $retVal; } } @@ -3037,38 +3263,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Wait for the slave to catch up to a given master position. * - * @param $pos DBMasterPos object - * @param $timeout Integer: the maximum number of seconds to wait for + * @param DBMasterPos $pos + * @param int $timeout The maximum number of seconds to wait for * synchronisation - * - * @return integer: zero if the slave was past that position already, + * @return int Zero if the slave was past that position already, * greater than zero if we waited for some period of time, less than * zero if we timed out. */ public function masterPosWait( DBMasterPos $pos, $timeout ) { - wfProfileIn( __METHOD__ ); - - if ( !is_null( $this->mFakeSlaveLag ) ) { - $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 ); - - if ( $wait > $timeout * 1e6 ) { - wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" ); - wfProfileOut( __METHOD__ ); - return -1; - } elseif ( $wait > 0 ) { - wfDebug( "Fake slave waiting $wait us\n" ); - usleep( $wait ); - wfProfileOut( __METHOD__ ); - return 1; - } else { - wfDebug( "Fake slave up to date ($wait us)\n" ); - wfProfileOut( __METHOD__ ); - return 0; - } - } - - wfProfileOut( __METHOD__ ); - # Real waits are implemented in the subclass. return 0; } @@ -3076,30 +3278,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Get the replication position of this slave * - * @return DBMasterPos, or false if this is not a slave. + * @return DBMasterPos|bool False if this is not a slave. */ public function getSlavePos() { - if ( !is_null( $this->mFakeSlaveLag ) ) { - $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag ); - wfDebug( __METHOD__ . ": fake slave pos = $pos\n" ); - return $pos; - } else { - # Stub - return false; - } + # Stub + return false; } /** * Get the position of this master * - * @return DBMasterPos, or false if this is not a master + * @return DBMasterPos|bool False if this is not a master */ public function getMasterPos() { - if ( $this->mFakeMaster ) { - return new MySQLMasterPos( 'fake', microtime( true ) ); - } else { - return false; - } + # Stub + return false; } /** @@ -3150,7 +3343,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { protected function runOnTransactionIdleCallbacks() { $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? - $e = null; // last exception + $e = $ePrior = null; // last exception do { // callbacks may add callbacks :) $callbacks = $this->mTrxIdleCallbacks; $this->mTrxIdleCallbacks = array(); // recursion guard @@ -3160,7 +3353,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { $this->clearFlag( DBO_TRX ); // make each query its own transaction call_user_func( $phpCallback ); $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin() - } catch ( Exception $e ) {} + } catch ( Exception $e ) { + if ( $ePrior ) { + MWExceptionHandler::logException( $ePrior ); + } + $ePrior = $e; + } } } while ( count( $this->mTrxIdleCallbacks ) ); @@ -3175,7 +3373,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @since 1.22 */ protected function runOnTransactionPreCommitCallbacks() { - $e = null; // last exception + $e = $ePrior = null; // last exception do { // callbacks may add callbacks :) $callbacks = $this->mTrxPreCommitCallbacks; $this->mTrxPreCommitCallbacks = array(); // recursion guard @@ -3183,7 +3381,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { try { list( $phpCallback ) = $callback; call_user_func( $phpCallback ); - } catch ( Exception $e ) {} + } catch ( Exception $e ) { + if ( $ePrior ) { + MWExceptionHandler::logException( $ePrior ); + } + $ePrior = $e; + } } } while ( count( $this->mTrxPreCommitCallbacks ) ); @@ -3193,22 +3396,91 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { } /** - * Begin a transaction. If a transaction is already in progress, that transaction will be committed before the - * new transaction is started. + * Begin an atomic section of statements + * + * If a transaction has been started already, just keep track of the given + * section name to make sure the transaction is not committed pre-maturely. + * This function can be used in layers (with sub-sections), so use a stack + * to keep track of the different atomic sections. If there is no transaction, + * start one implicitly. + * + * The goal of this function is to create an atomic section of SQL queries + * without having to start a new transaction if it already exists. * - * 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. + * Atomic sections are more strict than transactions. With transactions, + * attempting to begin a new transaction when one is already running results + * in MediaWiki issuing a brief warning and doing an implicit commit. All + * atomic levels *must* be explicitly closed using DatabaseBase::endAtomic(), + * and any database transactions cannot be began or committed until all atomic + * levels are closed. There is no such thing as implicitly opening or closing + * an atomic section. + * + * @since 1.23 + * @param string $fname + * @throws DBError + */ + final public function startAtomic( $fname = __METHOD__ ) { + if ( !$this->mTrxLevel ) { + $this->begin( $fname ); + $this->mTrxAutomatic = true; + $this->mTrxAutomaticAtomic = true; + } + + $this->mTrxAtomicLevels->push( $fname ); + } + + /** + * Ends an atomic section of SQL statements * - * 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. + * Ends the next section of atomic SQL statements and commits the transaction + * if necessary. * - * @param $fname string + * @since 1.23 + * @see DatabaseBase::startAtomic + * @param string $fname + * @throws DBError + */ + final public function endAtomic( $fname = __METHOD__ ) { + if ( !$this->mTrxLevel ) { + throw new DBUnexpectedError( $this, 'No atomic transaction is open.' ); + } + if ( $this->mTrxAtomicLevels->isEmpty() || + $this->mTrxAtomicLevels->pop() !== $fname + ) { + throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' ); + } + + if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) { + $this->commit( $fname, 'flush' ); + } + } + + /** + * 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 string $fname + * @throws DBError */ final public function begin( $fname = __METHOD__ ) { global $wgDebugDBTransactions; if ( $this->mTrxLevel ) { // implicit commit - if ( !$this->mTrxAutomatic ) { + if ( !$this->mTrxAtomicLevels->isEmpty() ) { + // If the current transaction was an automatic atomic one, then we definitely have + // a problem. Same if there is any unclosed atomic level. + throw new DBUnexpectedError( $this, + "Attempted to start explicit transaction when atomic levels are still open." + ); + } elseif ( !$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}), " . @@ -3228,22 +3500,33 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { $this->runOnTransactionPreCommitCallbacks(); $this->doCommit( $fname ); if ( $this->mTrxDoneWrites ) { - Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); + Profiler::instance()->transactionWritingOut( + $this->mServer, $this->mDBname, $this->mTrxShortId ); } $this->runOnTransactionIdleCallbacks(); } + # Avoid fatals if close() was called + if ( !$this->isOpen() ) { + throw new DBUnexpectedError( $this, "DB connection was already closed." ); + } + $this->doBegin( $fname ); $this->mTrxFname = $fname; $this->mTrxDoneWrites = false; $this->mTrxAutomatic = false; + $this->mTrxAutomaticAtomic = false; + $this->mTrxAtomicLevels = new SplStack; + $this->mTrxIdleCallbacks = array(); + $this->mTrxPreCommitCallbacks = array(); + $this->mTrxShortId = wfRandomString( 12 ); } /** * Issues the BEGIN command to the database server. * * @see DatabaseBase::begin() - * @param type $fname + * @param string $fname */ protected function doBegin( $fname ) { $this->query( 'BEGIN', $fname ); @@ -3256,33 +3539,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * * Nesting of transactions is not supported. * - * @param $fname string - * @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. + * @param string $fname + * @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. + * @throws DBUnexpectedError */ final public function commit( $fname = __METHOD__, $flush = '' ) { - if ( $flush != 'flush' ) { + if ( !$this->mTrxAtomicLevels->isEmpty() ) { + // There are still atomic sections open. This cannot be ignored + throw new DBUnexpectedError( + $this, + "Attempted to commit transaction while atomic sections are still open" + ); + } + + 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!" ); + return; // nothing to do + } elseif ( !$this->mTrxAutomatic ) { + wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); } } else { if ( !$this->mTrxLevel ) { + wfWarn( "$fname: No transaction to commit, something got out of sync!" ); return; // nothing to do - } elseif ( !$this->mTrxAutomatic ) { - wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); + } elseif ( $this->mTrxAutomatic ) { + wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" ); } } + # Avoid fatals if close() was called + if ( !$this->isOpen() ) { + throw new DBUnexpectedError( $this, "DB connection was already closed." ); + } + $this->runOnTransactionPreCommitCallbacks(); $this->doCommit( $fname ); if ( $this->mTrxDoneWrites ) { - Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); + Profiler::instance()->transactionWritingOut( + $this->mServer, $this->mDBname, $this->mTrxShortId ); } - $this->mTrxDoneWrites = false; $this->runOnTransactionIdleCallbacks(); } @@ -3290,7 +3589,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Issues the COMMIT command to the database server. * * @see DatabaseBase::commit() - * @param type $fname + * @param string $fname */ protected function doCommit( $fname ) { if ( $this->mTrxLevel ) { @@ -3305,26 +3604,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * * No-op on non-transactional databases. * - * @param $fname string + * @param string $fname + * @param string $flush Flush flag, set to 'flush' to disable warnings about + * calling rollback 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. + * @since 1.23 Added $flush parameter */ - final public function rollback( $fname = __METHOD__ ) { - if ( !$this->mTrxLevel ) { - wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); + final public function rollback( $fname = __METHOD__, $flush = '' ) { + if ( $flush !== 'flush' ) { + if ( !$this->mTrxLevel ) { + wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); + return; // nothing to do + } elseif ( $this->mTrxAutomatic ) { + wfWarn( "$fname: Explicit rollback 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!" ); + } } + + # Avoid fatals if close() was called + if ( !$this->isOpen() ) { + throw new DBUnexpectedError( $this, "DB connection was already closed." ); + } + $this->doRollback( $fname ); $this->mTrxIdleCallbacks = array(); // cancel $this->mTrxPreCommitCallbacks = array(); // cancel + $this->mTrxAtomicLevels = new SplStack; if ( $this->mTrxDoneWrites ) { - Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); + Profiler::instance()->transactionWritingOut( + $this->mServer, $this->mDBname, $this->mTrxShortId ); } - $this->mTrxDoneWrites = false; } /** * Issues the ROLLBACK command to the database server. * * @see DatabaseBase::rollback() - * @param type $fname + * @param string $fname */ protected function doRollback( $fname ) { if ( $this->mTrxLevel ) { @@ -3341,12 +3663,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * The table names passed to this function shall not be quoted (this * function calls addIdentifierQuotes when needed). * - * @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 string $fname calling function name + * @param string $oldName Name of table whose structure should be copied + * @param string $newName Name of table to be created + * @param bool $temporary Whether the new table should be temporary + * @param string $fname Calling function name * @throws MWException - * @return Boolean: true if operation was successful + * @return bool True if operation was successful */ public function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ @@ -3359,7 +3681,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * List all tables on the database * * @param string $prefix Only show tables with this prefix, e.g. mw_ - * @param string $fname calling function name + * @param string $fname Calling function name * @throws MWException */ function listTables( $prefix = null, $fname = __METHOD__ ) { @@ -3380,8 +3702,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * For caching purposes the list of all views should be stored in * $this->allViews. The process cache can be cleared with clearViewsCache() * - * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_ - * @param string $fname Name of calling function + * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_ + * @param string $fname Name of calling function * @throws MWException * @since 1.22 */ @@ -3392,7 +3714,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Differentiates between a TABLE and a VIEW * - * @param $name string: Name of the database-structure to test. + * @param string $name Name of the database-structure to test. * @throws MWException * @since 1.22 */ @@ -3407,7 +3729,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * The result is unquoted, and needs to be passed through addQuotes() * before it can be included in raw SQL. * - * @param $ts string|int + * @param string|int $ts * * @return string */ @@ -3424,7 +3746,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * The result is unquoted, and needs to be passed through addQuotes() * before it can be included in raw SQL. * - * @param $ts string|int + * @param string|int $ts * * @return string */ @@ -3447,8 +3769,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * callers, so this is unnecessary in external code. For compatibility with * old code, ResultWrapper objects are passed through unaltered. * - * @param $result bool|ResultWrapper - * + * @param bool|ResultWrapper|resource $result * @return bool|ResultWrapper */ public function resultObject( $result ) { @@ -3470,7 +3791,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @return bool Success or failure */ public function ping() { - # Stub. Not essential to override. + # Stub. Not essential to override. return true; } @@ -3484,7 +3805,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @return int Database replication lag in seconds */ public function getLag() { - return intval( $this->mFakeSlaveLag ); + return 0; } /** @@ -3501,7 +3822,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * don't allow simple quoted strings to be inserted. To insert into such * a field, pass the data through this function before passing it to * DatabaseBase::insert(). - * @param $b string + * + * @param string $b * @return string */ public function encodeBlob( $b ) { @@ -3512,7 +3834,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Some DBMSs return a special placeholder object representing blob fields * in result objects. Pass the object through this function to return the * original string. - * @param $b string + * + * @param string $b * @return string */ public function decodeBlob( $b ) { @@ -3526,7 +3849,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * full-wiki dumps, where a single query reads out over * hours or days. * - * @param $options Array + * @param array $options * @return void */ public function setSessionOptions( array $options ) { @@ -3542,9 +3865,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * @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 + * generated dynamically using $filename + * @param bool|callable $inputCallback Optional function called for each + * complete line sent * @throws Exception|MWException * @return bool|string */ @@ -3565,8 +3888,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { try { $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback ); - } - catch ( MWException $e ) { + } catch ( MWException $e ) { fclose( $fp ); throw $e; } @@ -3582,7 +3904,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * it fails back to MySQL if no DB-specific patch can be found * * @param string $patch The name of the patch, like patch-something.sql - * @return String Full path to patch file + * @return string Full path to patch file */ public function patchPath( $patch ) { global $IP; @@ -3600,7 +3922,7 @@ abstract class DatabaseBase implements IDatabase, 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 bool|array $vars mapping variable name to value. + * @param bool|array $vars Mapping variable name to value. */ public function setSchemaVars( $vars ) { $this->mSchemaVars = $vars; @@ -3612,16 +3934,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Returns true on success, error string or exception on failure (depending * on object's error ignore settings). * - * @param $fp Resource: File handle - * @param $lineCallback Callback: Optional function called before reading each query - * @param $resultCallback Callback: Optional function called for each MySQL result + * @param resource $fp File handle + * @param bool|callable $lineCallback Optional function called before reading each query + * @param bool|callable $resultCallback Optional function called for each MySQL result * @param string $fname Calling function name - * @param $inputCallback Callback: Optional function called for each complete query sent + * @param bool|callable $inputCallback Optional function called for each complete query sent * @return bool|string */ public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, - $fname = __METHOD__, $inputCallback = false ) - { + $fname = __METHOD__, $inputCallback = false + ) { $cmd = ''; while ( !feof( $fp ) ) { @@ -3659,6 +3981,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { if ( false === $res ) { $err = $this->lastError(); + return "Query \"{$cmd}\" failed with error code \"$err\".\n"; } } @@ -3674,7 +3997,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * * @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 + * @return bool Whether $newLine contains end of the statement */ public function streamStatementEnd( &$sql, &$newLine ) { if ( $this->delimiter ) { @@ -3684,6 +4007,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { return true; } } + return false; } @@ -3702,7 +4026,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * table options its use should be avoided. * * @param string $ins SQL statement to replace variables in - * @return String The new SQL statement with variables replaced + * @return string The new SQL statement with variables replaced */ protected function replaceSchemaVars( $ins ) { $vars = $this->getSchemaVars(); @@ -3714,14 +4038,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { // replace /*$var*/ $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins ); } + return $ins; } /** * Replace variables in sourced SQL * - * @param $ins string - * + * @param string $ins * @return string */ protected function replaceVars( $ins ) { @@ -3767,8 +4091,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Table name callback * - * @param $matches array - * + * @param array $matches * @return string */ protected function tableNameCallback( $matches ) { @@ -3778,8 +4101,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Index name callback * - * @param $matches array - * + * @param array $matches * @return string */ protected function indexNameCallback( $matches ) { @@ -3789,9 +4111,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Check to see if a named lock is available. This is non-blocking. * - * @param string $lockName name of lock to poll - * @param string $method name of method calling us - * @return Boolean + * @param string $lockName Name of lock to poll + * @param string $method Name of method calling us + * @return bool * @since 1.20 */ public function lockIsFree( $lockName, $method ) { @@ -3804,10 +4126,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Abstracted from Filestore::lock() so child classes can implement for * their own needs. * - * @param string $lockName name of lock to aquire - * @param string $method name of method calling us - * @param $timeout Integer: timeout - * @return Boolean + * @param string $lockName Name of lock to aquire + * @param string $method Name of method calling us + * @param int $timeout + * @return bool */ public function lock( $lockName, $method, $timeout = 5 ) { return true; @@ -3830,11 +4152,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Lock specific tables * - * @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 array $read Array of tables to lock for read access + * @param array $write Array 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 */ public function lockTables( $read, $write, $method, $lowPriority = true ) { @@ -3844,8 +4165,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Unlock specific tables * - * @param string $method the caller - * + * @param string $method The caller * @return bool */ public function unlockTables( $method ) { @@ -3854,8 +4174,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Delete a table - * @param $tableName string - * @param $fName string + * @param string $tableName + * @param string $fName * @return bool|ResultWrapper * @since 1.18 */ @@ -3867,6 +4187,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { if ( $this->cascadingDeletes() ) { $sql .= " CASCADE"; } + return $this->query( $sql, $fName ); } @@ -3874,7 +4195,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Get search engine class. All subclasses of this need to implement this * if they wish to use searching. * - * @return String + * @return string */ public function getSearchEngine() { return 'SearchEngineDummy'; @@ -3885,7 +4206,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * keyword for timestamps in PostgreSQL, and works with CHAR(14) as well * because "i" sorts after all numbers. * - * @return String + * @return string */ public function getInfinity() { return 'infinity'; @@ -3894,8 +4215,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * Encode an expiry time into the DBMS dependent format * - * @param string $expiry timestamp for expiry, or the 'infinity' string - * @return String + * @param string $expiry Timestamp for expiry, or the 'infinity' string + * @return string */ public function encodeExpiry( $expiry ) { return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) @@ -3907,8 +4228,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * Decode an expiry time into a DBMS independent format * * @param string $expiry DB timestamp field value for expiry - * @param $format integer: TS_* constant, defaults to TS_MW - * @return String + * @param int $format TS_* constant, defaults to TS_MW + * @return string */ public function decodeExpiry( $expiry, $format = TS_MW ) { return ( $expiry == '' || $expiry == $this->getInfinity() ) @@ -3922,7 +4243,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * * This is a MySQL-specific feature. * - * @param $value Mixed: true for allow, false for deny, or "default" to + * @param bool|string $value True for allow, false for deny, or "default" to * restore the initial value */ public function setBigSelects( $value = true ) { @@ -3931,6 +4252,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { /** * @since 1.19 + * @return string */ public function __toString() { return (string)$this->mConn; @@ -3947,10 +4269,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { $callers = array(); foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) { $callers[] = $callbackInfo[1]; - } $callers = implode( ', ', $callers ); - trigger_error( "DB transaction callbacks still pending (from $callers)." ); + trigger_error( "DB transaction callbacks still pending (from $callers)." ); } } } -- cgit v1.2.2