summaryrefslogtreecommitdiff
path: root/includes/db
diff options
context:
space:
mode:
Diffstat (limited to 'includes/db')
-rw-r--r--includes/db/Database.php856
-rw-r--r--includes/db/DatabaseError.php6
-rw-r--r--includes/db/DatabaseMssql.php86
-rw-r--r--includes/db/DatabaseMysql.php7
-rw-r--r--includes/db/DatabaseMysqlBase.php125
-rw-r--r--includes/db/DatabaseMysqli.php14
-rw-r--r--includes/db/DatabaseOracle.php18
-rw-r--r--includes/db/DatabasePostgres.php23
-rw-r--r--includes/db/DatabaseSqlite.php163
-rw-r--r--includes/db/DatabaseUtility.php27
-rw-r--r--includes/db/IORMTable.php18
-rw-r--r--includes/db/LBFactory.php78
-rw-r--r--includes/db/LBFactoryMulti.php58
-rw-r--r--includes/db/LBFactorySingle.php20
-rw-r--r--includes/db/LoadBalancer.php390
-rw-r--r--includes/db/LoadMonitor.php4
-rw-r--r--includes/db/ORMTable.php50
17 files changed, 1058 insertions, 885 deletions
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 9b783a99..0c0248da 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -26,186 +26,6 @@
*/
/**
- * Base interface for all DBMS-specific code. At a bare minimum, all of the
- * following must be implemented to support MediaWiki
- *
- * @file
- * @ingroup Database
- */
-interface DatabaseType {
- /**
- * Get the type of the DBMS, as it appears in $wgDBtype.
- *
- * @return string
- */
- function getType();
-
- /**
- * Open a connection to the database. Usually aborts on failure
- *
- * @param string $server Database server host
- * @param string $user Database user name
- * @param string $password Database user password
- * @param string $dbName Database name
- * @return bool
- * @throws DBConnectionError
- */
- function open( $server, $user, $password, $dbName );
-
- /**
- * Fetch the next row from the given result object, in object form.
- * Fields can be retrieved with $row->fieldname, with fields acting like
- * member variables.
- * If no more rows are available, false is returned.
- *
- * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc.
- * @return stdClass|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchObject( $res );
-
- /**
- * Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
- * If no more rows are available, false is returned.
- *
- * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc.
- * @return array|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchRow( $res );
-
- /**
- * Get the number of rows in a result object
- *
- * @param mixed $res A SQL result
- * @return int
- */
- function numRows( $res );
-
- /**
- * Get the number of fields in a result object
- * @see http://www.php.net/mysql_num_fields
- *
- * @param mixed $res A SQL result
- * @return int
- */
- function numFields( $res );
-
- /**
- * Get a field name in a result object
- * @see http://www.php.net/mysql_field_name
- *
- * @param mixed $res A SQL result
- * @param int $n
- * @return string
- */
- function fieldName( $res, $n );
-
- /**
- * Get the inserted value of an auto-increment row
- *
- * The value inserted should be fetched from nextSequenceValue()
- *
- * Example:
- * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
- * $dbw->insert( 'page', array( 'page_id' => $id ) );
- * $id = $dbw->insertId();
- *
- * @return int
- */
- function insertId();
-
- /**
- * Change the position of the cursor in a result object
- * @see http://www.php.net/mysql_data_seek
- *
- * @param mixed $res A SQL result
- * @param int $row
- */
- function dataSeek( $res, $row );
-
- /**
- * Get the last error number
- * @see http://www.php.net/mysql_errno
- *
- * @return int
- */
- function lastErrno();
-
- /**
- * Get a description of the last error
- * @see http://www.php.net/mysql_error
- *
- * @return string
- */
- function lastError();
-
- /**
- * mysql_fetch_field() wrapper
- * Returns false if the field doesn't exist
- *
- * @param string $table Table name
- * @param string $field Field name
- *
- * @return Field
- */
- function fieldInfo( $table, $field );
-
- /**
- * Get information about an index into an object
- * @param string $table Table name
- * @param string $index Index name
- * @param string $fname Calling function name
- * @return mixed Database-specific index description class or false if the index does not exist
- */
- function indexInfo( $table, $index, $fname = __METHOD__ );
-
- /**
- * Get the number of rows affected by the last write query
- * @see http://www.php.net/mysql_affected_rows
- *
- * @return int
- */
- function affectedRows();
-
- /**
- * Wrapper for addslashes()
- *
- * @param string $s String to be slashed.
- * @return string Slashed string.
- */
- function strencode( $s );
-
- /**
- * Returns a wikitext link to the DB's website, e.g.,
- * return "[http://www.mysql.com/ MySQL]";
- * Should at least contain plain text, if for some reason
- * your database has no website.
- *
- * @return string Wikitext of a link to the server software's web site
- */
- function getSoftwareLink();
-
- /**
- * A string describing the current software version, like from
- * mysql_get_server_info().
- *
- * @return string Version information from the database server.
- */
- function getServerVersion();
-
- /**
- * A string describing the current software version, and possibly
- * other details in a user-friendly way. Will be listed on Special:Version, etc.
- * Use getServerVersion() to get machine-friendly information.
- *
- * @return string Version information from the database server
- */
- function getServerInfo();
-}
-
-/**
* Interface for classes that implement or wrap DatabaseBase
* @ingroup Database
*/
@@ -216,7 +36,7 @@ interface IDatabase {
* Database abstraction object
* @ingroup Database
*/
-abstract class DatabaseBase implements IDatabase, DatabaseType {
+abstract class DatabaseBase implements IDatabase {
/** Number of times to re-try an operation in case of deadlock */
const DEADLOCK_TRIES = 4;
@@ -226,10 +46,6 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/** Maximum time to wait before retry */
const DEADLOCK_DELAY_MAX = 1500000;
-# ------------------------------------------------------------------------------
-# Variables
-# ------------------------------------------------------------------------------
-
protected $mLastQuery = '';
protected $mDoneWrites = false;
protected $mPHPError = false;
@@ -272,10 +88,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Either a short hexidecimal string if a transaction is active or ""
*
* @var string
+ * @see DatabaseBase::mTrxLevel
*/
protected $mTrxShortId = '';
/**
+ * The UNIX time that the transaction started. Callers can assume that if
+ * snapshot isolation is used, then the data is *at least* up to date to that
+ * point (possibly more up-to-date since the first SELECT defines the snapshot).
+ *
+ * @var float|null
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxTimestamp = null;
+
+ /**
* Remembers the function name given for starting the most recent transaction via begin().
* Used to provide additional context for error reporting.
*
@@ -326,10 +153,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*/
protected $allViews = null;
-# ------------------------------------------------------------------------------
-# Accessors
-# ------------------------------------------------------------------------------
- # These optionally set a variable and return the previous state
+ /** @var TransactionProfiler */
+ protected $trxProfiler;
/**
* A string describing the current software version, and possibly
@@ -420,6 +245,19 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Get the UNIX timestamp of the time that the transaction was established
+ *
+ * This can be used to reason about the staleness of SELECT data
+ * in REPEATABLE-READ transaction isolation level.
+ *
+ * @return float|null Returns null if there is not active transaction
+ * @since 1.25
+ */
+ public function trxTimestamp() {
+ return $this->mTrxLevel ? $this->mTrxTimestamp : null;
+ }
+
+ /**
* Get/set the number of errors logged. Only useful when errors are ignored
* @param int $count The count to set, or omitted to leave it unchanged.
* @return int The error count
@@ -511,6 +349,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * @return TransactionProfiler
+ */
+ protected function getTransactionProfiler() {
+ return $this->trxProfiler
+ ? $this->trxProfiler
+ : Profiler::instance()->getTransactionProfiler();
+ }
+
+ /**
* Returns true if this database supports (and uses) cascading deletes
*
* @return bool
@@ -744,9 +591,167 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
return $this->getSqlFilePath( 'update-keys.sql' );
}
-# ------------------------------------------------------------------------------
-# Other functions
-# ------------------------------------------------------------------------------
+ /**
+ * Get the type of the DBMS, as it appears in $wgDBtype.
+ *
+ * @return string
+ */
+ abstract function getType();
+
+ /**
+ * Open a connection to the database. Usually aborts on failure
+ *
+ * @param string $server Database server host
+ * @param string $user Database user name
+ * @param string $password Database user password
+ * @param string $dbName Database name
+ * @return bool
+ * @throws DBConnectionError
+ */
+ abstract function open( $server, $user, $password, $dbName );
+
+ /**
+ * Fetch the next row from the given result object, in object form.
+ * Fields can be retrieved with $row->fieldname, with fields acting like
+ * member variables.
+ * If no more rows are available, false is returned.
+ *
+ * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc.
+ * @return stdClass|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ abstract function fetchObject( $res );
+
+ /**
+ * Fetch the next row from the given result object, in associative array
+ * form. Fields are retrieved with $row['fieldname'].
+ * If no more rows are available, false is returned.
+ *
+ * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc.
+ * @return array|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ abstract function fetchRow( $res );
+
+ /**
+ * Get the number of rows in a result object
+ *
+ * @param mixed $res A SQL result
+ * @return int
+ */
+ abstract function numRows( $res );
+
+ /**
+ * Get the number of fields in a result object
+ * @see http://www.php.net/mysql_num_fields
+ *
+ * @param mixed $res A SQL result
+ * @return int
+ */
+ abstract function numFields( $res );
+
+ /**
+ * Get a field name in a result object
+ * @see http://www.php.net/mysql_field_name
+ *
+ * @param mixed $res A SQL result
+ * @param int $n
+ * @return string
+ */
+ abstract function fieldName( $res, $n );
+
+ /**
+ * Get the inserted value of an auto-increment row
+ *
+ * The value inserted should be fetched from nextSequenceValue()
+ *
+ * Example:
+ * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
+ * $dbw->insert( 'page', array( 'page_id' => $id ) );
+ * $id = $dbw->insertId();
+ *
+ * @return int
+ */
+ abstract function insertId();
+
+ /**
+ * Change the position of the cursor in a result object
+ * @see http://www.php.net/mysql_data_seek
+ *
+ * @param mixed $res A SQL result
+ * @param int $row
+ */
+ abstract function dataSeek( $res, $row );
+
+ /**
+ * Get the last error number
+ * @see http://www.php.net/mysql_errno
+ *
+ * @return int
+ */
+ abstract function lastErrno();
+
+ /**
+ * Get a description of the last error
+ * @see http://www.php.net/mysql_error
+ *
+ * @return string
+ */
+ abstract function lastError();
+
+ /**
+ * mysql_fetch_field() wrapper
+ * Returns false if the field doesn't exist
+ *
+ * @param string $table Table name
+ * @param string $field Field name
+ *
+ * @return Field
+ */
+ abstract function fieldInfo( $table, $field );
+
+ /**
+ * Get information about an index into an object
+ * @param string $table Table name
+ * @param string $index Index name
+ * @param string $fname Calling function name
+ * @return mixed Database-specific index description class or false if the index does not exist
+ */
+ abstract function indexInfo( $table, $index, $fname = __METHOD__ );
+
+ /**
+ * Get the number of rows affected by the last write query
+ * @see http://www.php.net/mysql_affected_rows
+ *
+ * @return int
+ */
+ abstract function affectedRows();
+
+ /**
+ * Wrapper for addslashes()
+ *
+ * @param string $s String to be slashed.
+ * @return string Slashed string.
+ */
+ abstract function strencode( $s );
+
+ /**
+ * Returns a wikitext link to the DB's website, e.g.,
+ * return "[http://www.mysql.com/ MySQL]";
+ * Should at least contain plain text, if for some reason
+ * your database has no website.
+ *
+ * @return string Wikitext of a link to the server software's web site
+ */
+ abstract function getSoftwareLink();
+
+ /**
+ * A string describing the current software version, like from
+ * mysql_get_server_info().
+ *
+ * @return string Version information from the database server.
+ */
+ abstract function getServerVersion();
/**
* Constructor.
@@ -760,32 +765,19 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* @param array $params Parameters passed from DatabaseBase::factory()
*/
- function __construct( $params = null ) {
+ function __construct( array $params ) {
global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions;
$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;
- }
+ $server = $params['host'];
+ $user = $params['user'];
+ $password = $params['password'];
+ $dbName = $params['dbname'];
+ $flags = $params['flags'];
+ $tablePrefix = $params['tablePrefix'];
+ $schema = $params['schema'];
+ $foreign = $params['foreign'];
$this->mFlags = $flags;
if ( $this->mFlags & DBO_DEFAULT ) {
@@ -818,6 +810,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->mForeign = $foreign;
+ if ( isset( $params['trxProfiler'] ) ) {
+ $this->trxProfiler = $params['trxProfiler']; // override
+ }
+
if ( $user ) {
$this->open( $server, $user, $password, $dbName );
}
@@ -905,18 +901,17 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$class = 'Database' . ucfirst( $driver );
if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
- $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 );
+ // Resolve some defaults for b/c
+ $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
+ $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
+ $p['password'] = isset( $p['password'] ) ? $p['password'] : false;
+ $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false;
+ $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0;
+ $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global';
+ $p['schema'] = isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType];
+ $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
+
+ return new $class( $p );
} else {
return null;
}
@@ -955,6 +950,23 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Create a log context to pass to wfLogDBError or other logging functions.
+ *
+ * @param array $extras Additional data to add to context
+ * @return array
+ */
+ protected function getLogContext( array $extras = array() ) {
+ return array_merge(
+ array(
+ 'db_server' => $this->mServer,
+ 'db_name' => $this->mDBname,
+ 'db_user' => $this->mUser,
+ ),
+ $extras
+ );
+ }
+
+ /**
* Closes a database connection.
* if it is open : commits any open transactions
*
@@ -1027,6 +1039,20 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Determine whether a SQL statement is sensitive to isolation level.
+ * A SQL statement is considered transactable if its result could vary
+ * depending on the transaction isolation level. Operational commands
+ * such as 'SET' and 'SHOW' are not considered to be transactable.
+ *
+ * @param string $sql
+ * @return bool
+ */
+ public function isTransactableQuery( $sql ) {
+ $verb = substr( $sql, 0, strcspn( $sql, " \t\r\n" ) );
+ return !in_array( $verb, array( 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ) );
+ }
+
+ /**
* Run an SQL query and return the result. Normally throws a DBQueryError
* on failure. If errors are ignored, returns false instead.
*
@@ -1052,9 +1078,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength;
$this->mLastQuery = $sql;
- if ( $this->isWriteQuery( $sql ) ) {
+
+ $isWriteQuery = $this->isWriteQuery( $sql );
+ if ( $isWriteQuery ) {
+ if ( !$this->mDoneWrites ) {
+ wfDebug( __METHOD__ . ': Writes done: ' .
+ DatabaseBase::generalizeSQL( $sql ) . "\n" );
+ }
# Set a flag indicating that writes have been done
- wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" );
$this->mDoneWrites = microtime( true );
}
@@ -1073,50 +1104,38 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
// Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
$commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
- # If DBO_TRX is set, start a transaction
- if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK'
- ) {
- # Avoid establishing transactions for SHOW and SET statements too -
- # that would delay transaction initializations to once connection
- # is really used by application
- $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
- if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
- if ( $wgDebugDBTransactions ) {
- wfDebug( "Implicit transaction start.\n" );
- }
- $this->begin( __METHOD__ . " ($fname)" );
- $this->mTrxAutomatic = true;
+ if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX ) && $this->isTransactableQuery( $sql ) ) {
+ if ( $wgDebugDBTransactions ) {
+ wfDebug( "Implicit transaction start.\n" );
}
+ $this->begin( __METHOD__ . " ($fname)" );
+ $this->mTrxAutomatic = true;
}
# Keep track of whether the transaction has write queries pending
- if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
+ if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWriteQuery ) {
$this->mTrxDoneWrites = true;
- Profiler::instance()->transactionWritingIn(
+ $this->getTransactionProfiler()->transactionWritingIn(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
- $queryProf = '';
- $totalProf = '';
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
+ # generalizeSQL will probably cut down the query to reasonable
+ # logging size most of the time. The substr is really just a sanity check.
+ if ( $isMaster ) {
+ $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query-master';
+ } else {
+ $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query';
+ }
+ # Include query transaction state
+ $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
- 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.
- if ( $isMaster ) {
- $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'DatabaseBase::query-master';
- } else {
- $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 );
+ $profiler = Profiler::instance();
+ if ( !$profiler instanceof ProfilerStub ) {
+ $totalProfSection = $profiler->scopedProfileIn( $totalProf );
+ $queryProfSection = $profiler->scopedProfileIn( $queryProf );
}
if ( $this->debug() ) {
@@ -1139,7 +1158,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
# Do the query and handle errors
+ $startTime = microtime( true );
$ret = $this->doQuery( $commentedSql );
+ # Log the query time and feed it into the DB trx profiler
+ $this->getTransactionProfiler()->recordQueryCompletion(
+ $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
MWDebug::queryTime( $queryId );
@@ -1155,23 +1178,22 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$lastError = $this->lastError();
$lastErrno = $this->lastErrno();
if ( $this->ping() ) {
- global $wgRequestTime;
wfDebug( "Reconnected\n" );
- $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
- : $commentedSql;
- $sqlx = strtr( $sqlx, "\t\n", ' ' );
- $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" );
- }
+ $server = $this->getServer();
+ $msg = __METHOD__ . ": lost connection to $server; reconnected";
+ wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
+
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)
+ $startTime = microtime( true );
$ret = $this->doQuery( $commentedSql );
+ # Log the query time and feed it into the DB trx profiler
+ $this->getTransactionProfiler()->recordQueryCompletion(
+ $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
}
} else {
wfDebug( "Failed\n" );
@@ -1179,15 +1201,17 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
if ( false === $ret ) {
- $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
+ $this->reportQueryError(
+ $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
}
- if ( !Profiler::instance()->isStub() ) {
- wfProfileOut( $queryProf );
- wfProfileOut( $totalProf );
- }
+ $res = $this->resultObject( $ret );
+
+ // Destroy profile sections in the opposite order to their creation
+ $queryProfSection = false;
+ $totalProfSection = false;
- return $this->resultObject( $ret );
+ return $res;
}
/**
@@ -1202,16 +1226,22 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @throws DBQueryError
*/
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- # Ignore errors during error handling to avoid infinite recursion
- $ignore = $this->ignoreErrors( true );
++$this->mErrorCount;
- if ( $ignore || $tempIgnore ) {
+ if ( $this->ignoreErrors() || $tempIgnore ) {
wfDebug( "SQL ERROR (ignored): $error\n" );
- $this->ignoreErrors( $ignore );
} else {
$sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
- wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" );
+ wfLogDBError(
+ "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ 'errno' => $errno,
+ 'error' => $error,
+ 'sql1line' => $sql1line,
+ 'fname' => $fname,
+ ) )
+ );
wfDebug( "SQL ERROR: " . $error . "\n" );
throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
@@ -1348,9 +1378,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* @return bool|mixed The value from the field, or false on failure.
*/
- public function selectField( $table, $var, $cond = '', $fname = __METHOD__,
- $options = array()
+ public function selectField(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = array()
) {
+ if ( $var === '*' ) { // sanity
+ throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
+ }
+
if ( !is_array( $options ) ) {
$options = array( $options );
}
@@ -1358,7 +1392,6 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$options['LIMIT'] = 1;
$res = $this->select( $table, $var, $cond, $fname, $options );
-
if ( $res === false || !$this->numRows( $res ) ) {
return false;
}
@@ -1373,6 +1406,48 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * A SELECT wrapper which returns a list of single field values from result rows.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly
+ * ignored, returns false on failure.
+ *
+ * If no result rows are returned from the query, false is returned.
+ *
+ * @param string|array $table Table name. See DatabaseBase::select() for details.
+ * @param string $var The field name to select. This must be a valid SQL
+ * fragment: do not use unvalidated user input.
+ * @param string|array $cond The condition array. See DatabaseBase::select() for details.
+ * @param string $fname The function name of the caller.
+ * @param string|array $options The query options. See DatabaseBase::select() for details.
+ *
+ * @return bool|array The values from the field, or false on failure
+ * @since 1.25
+ */
+ public function selectFieldValues(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = array()
+ ) {
+ if ( $var === '*' ) { // sanity
+ throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
+ }
+
+ if ( !is_array( $options ) ) {
+ $options = array( $options );
+ }
+
+ $res = $this->select( $table, $var, $cond, $fname, $options );
+ if ( $res === false ) {
+ return false;
+ }
+
+ $values = array();
+ foreach ( $res as $row ) {
+ $values[] = $row->$var;
+ }
+
+ return $values;
+ }
+
+ /**
* Returns an optional USE INDEX clause to go after the table, and a
* string to go at the end of the query.
*
@@ -1558,9 +1633,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* - If the value of such an array element is a scalar (such as a
* string), it will be treated as data and thus quoted appropriately.
* If it is null, an IS NULL clause will be added.
- * - If the value is an array, an IN(...) clause will be constructed,
- * such that the field name may match any of the elements in the
- * array. The elements of the array will be quoted.
+ * - If the value is an array, an IN (...) clause will be constructed
+ * from its non-null elements, and an IS NULL clause will be added
+ * if null is present, such that the field may match any of the
+ * elements in the array. The non-null elements will be quoted.
*
* Note that expressions are often DBMS-dependent in their syntax.
* DBMS-independent wrappers are provided for constructing several types of
@@ -1779,7 +1855,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $res ) {
$row = $this->fetchRow( $res );
- $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
+ $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
}
return $rows;
@@ -1809,7 +1885,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $res ) {
$row = $this->fetchRow( $res );
- $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
+ $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
}
return $rows;
@@ -2117,16 +2193,36 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
} elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
$list .= "$value";
} elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
- if ( count( $value ) == 0 ) {
+ // Remove null from array to be handled separately if found
+ $includeNull = false;
+ foreach ( array_keys( $value, null, true ) as $nullKey ) {
+ $includeNull = true;
+ unset( $value[$nullKey] );
+ }
+ if ( count( $value ) == 0 && !$includeNull ) {
throw new MWException( __METHOD__ . ": empty input for field $field" );
- } elseif ( count( $value ) == 1 ) {
- // Special-case single values, as IN isn't terribly efficient
- // Don't necessarily assume the single key is 0; we don't
- // enforce linear numeric ordering on other arrays here.
- $value = array_values( $value );
- $list .= $field . " = " . $this->addQuotes( $value[0] );
+ } elseif ( count( $value ) == 0 ) {
+ // only check if $field is null
+ $list .= "$field IS NULL";
} else {
- $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+ // IN clause contains at least one valid element
+ if ( $includeNull ) {
+ // Group subconditions to ensure correct precedence
+ $list .= '(';
+ }
+ if ( count( $value ) == 1 ) {
+ // Special-case single values, as IN isn't terribly efficient
+ // Don't necessarily assume the single key is 0; we don't
+ // enforce linear numeric ordering on other arrays here.
+ $value = array_values( $value );
+ $list .= $field . " = " . $this->addQuotes( $value[0] );
+ } else {
+ $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+ }
+ // if null present in array, append IS NULL
+ if ( $includeNull ) {
+ $list .= " OR $field IS NULL)";
+ }
}
} elseif ( $value === null ) {
if ( $mode == LIST_AND || $mode == LIST_OR ) {
@@ -2322,7 +2418,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# Split database and table into proper variables.
# We reverse the explode so that database.table and table both output
# the correct table.
- $dbDetails = explode( '.', $name, 2 );
+ $dbDetails = explode( '.', $name, 3 );
if ( count( $dbDetails ) == 3 ) {
list( $database, $schema, $table ) = $dbDetails;
# We don't want any prefix added in this case
@@ -2552,12 +2648,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Get the name of an index in a given table
+ * Get the name of an index in a given table.
*
+ * @protected Don't use outside of DatabaseBase and childs
* @param string $index
* @return string
*/
- protected function indexName( $index ) {
+ public function indexName( $index ) {
+ // @FIXME: Make this protected once we move away from PHP 5.3
+ // Needs to be public because of usage in closure (in DatabaseBase::replaceVars)
+
// Backwards-compatibility hack
$renamed = array(
'ar_usertext_timestamp' => 'usertext_timestamp',
@@ -2575,10 +2675,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Adds quotes and backslashes.
*
- * @param string $s
+ * @param string|Blob $s
* @return string
*/
public function addQuotes( $s ) {
+ if ( $s instanceof Blob ) {
+ $s = $s->fetch();
+ }
if ( $s === null ) {
return 'NULL';
} else {
@@ -3218,41 +3321,40 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @return bool
*/
public function deadlockLoop() {
- $this->begin( __METHOD__ );
$args = func_get_args();
$function = array_shift( $args );
- $oldIgnore = $this->ignoreErrors( true );
$tries = self::DEADLOCK_TRIES;
-
if ( is_array( $function ) ) {
$fname = $function[0];
} else {
$fname = $function;
}
- do {
- $retVal = call_user_func_array( $function, $args );
- $error = $this->lastError();
- $errno = $this->lastErrno();
- $sql = $this->lastQuery();
+ $this->begin( __METHOD__ );
- if ( $errno ) {
+ $e = null;
+ do {
+ try {
+ $retVal = call_user_func_array( $function, $args );
+ break;
+ } catch ( DBQueryError $e ) {
+ $error = $this->lastError();
+ $errno = $this->lastErrno();
+ $sql = $this->lastQuery();
if ( $this->wasDeadlock() ) {
- # Retry
+ // Retry after a randomized delay
usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
} else {
- $this->reportQueryError( $error, $errno, $sql, $fname );
+ // Throw the error back up
+ throw $e;
}
}
- } while ( $this->wasDeadlock() && --$tries > 0 );
-
- $this->ignoreErrors( $oldIgnore );
+ } while ( --$tries > 0 );
if ( $tries <= 0 ) {
+ // Too many deadlocks; give up
$this->rollback( __METHOD__ );
- $this->reportQueryError( $error, $errno, $sql, $fname );
-
- return false;
+ throw $e;
} else {
$this->commit( __METHOD__ );
@@ -3486,7 +3588,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
" performing implicit commit!";
wfWarn( $msg );
- wfLogDBError( $msg );
+ wfLogDBError( $msg,
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ 'fname' => $fname,
+ ) )
+ );
} else {
// if the transaction was automatic and has done write operations,
// log it if $wgDebugDBTransactions is enabled.
@@ -3500,7 +3607,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut(
+ $this->mDoneWrites = microtime( true );
+ $this->getTransactionProfiler()->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
$this->runOnTransactionIdleCallbacks();
@@ -3512,6 +3620,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
$this->doBegin( $fname );
+ $this->mTrxTimestamp = microtime( true );
$this->mTrxFname = $fname;
$this->mTrxDoneWrites = false;
$this->mTrxAutomatic = false;
@@ -3579,7 +3688,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut(
+ $this->mDoneWrites = microtime( true );
+ $this->getTransactionProfiler()->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
$this->runOnTransactionIdleCallbacks();
@@ -3609,6 +3719,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* 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.
+ * @throws DBUnexpectedError
* @since 1.23 Added $flush parameter
*/
final public function rollback( $fname = __METHOD__, $flush = '' ) {
@@ -3637,7 +3748,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->mTrxPreCommitCallbacks = array(); // cancel
$this->mTrxAtomicLevels = new SplStack;
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut(
+ $this->getTransactionProfiler()->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
}
@@ -3835,10 +3946,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* in result objects. Pass the object through this function to return the
* original string.
*
- * @param string $b
+ * @param string|Blob $b
* @return string
*/
public function decodeBlob( $b ) {
+ if ( $b instanceof Blob ) {
+ $b = $b->fetch();
+ }
return $b;
}
@@ -3888,7 +4002,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
try {
$error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
fclose( $fp );
throw $e;
}
@@ -4019,47 +4133,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* - '{$var}' should be used for text and is passed through the database's
* addQuotes method.
- * - `{$var}` should be used for identifiers (eg: table and database names),
- * it is passed through the database's addIdentifierQuotes method which
+ * - `{$var}` should be used for identifiers (e.g. table and database names).
+ * It is passed through the database's addIdentifierQuotes method which
* can be overridden if the database uses something other than backticks.
- * - / *$var* / is just encoded, besides traditional table prefix and
- * table options its use should be avoided.
+ * - / *_* / or / *$wgDBprefix* / passes the name that follows through the
+ * database's tableName method.
+ * - / *i* / passes the name that follows through the database's indexName method.
+ * - In all other cases, / *$var* / is left unencoded. Except for table options,
+ * its use should be avoided. In 1.24 and older, string encoding was applied.
*
* @param string $ins SQL statement to replace variables in
* @return string The new SQL statement with variables replaced
*/
- protected function replaceSchemaVars( $ins ) {
- $vars = $this->getSchemaVars();
- foreach ( $vars as $var => $value ) {
- // replace '{$var}'
- $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
- // replace `{$var}`
- $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
- // replace /*$var*/
- $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
- }
-
- return $ins;
- }
-
- /**
- * Replace variables in sourced SQL
- *
- * @param string $ins
- * @return string
- */
protected function replaceVars( $ins ) {
- $ins = $this->replaceSchemaVars( $ins );
-
- // Table prefixes
- $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
- array( $this, 'tableNameCallback' ), $ins );
-
- // Index names
- $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
- array( $this, 'indexNameCallback' ), $ins );
-
- return $ins;
+ $that = $this;
+ $vars = $this->getSchemaVars();
+ return preg_replace_callback(
+ '!
+ /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
+ \'\{\$ (\w+) }\' | # 3. addQuotes
+ `\{\$ (\w+) }` | # 4. addIdentifierQuotes
+ /\*\$ (\w+) \*/ # 5. leave unencoded
+ !x',
+ function ( $m ) use ( $that, $vars ) {
+ // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
+ // check for both nonexistent keys *and* the empty string.
+ if ( isset( $m[1] ) && $m[1] !== '' ) {
+ if ( $m[1] === 'i' ) {
+ return $that->indexName( $m[2] );
+ } else {
+ return $that->tableName( $m[2] );
+ }
+ } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
+ return $that->addQuotes( $vars[$m[3]] );
+ } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
+ return $that->addIdentifierQuotes( $vars[$m[4]] );
+ } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
+ return $vars[$m[5]];
+ } else {
+ return $m[0];
+ }
+ },
+ $ins
+ );
}
/**
@@ -4089,26 +4205,6 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Table name callback
- *
- * @param array $matches
- * @return string
- */
- protected function tableNameCallback( $matches ) {
- return $this->tableName( $matches[1] );
- }
-
- /**
- * Index name callback
- *
- * @param array $matches
- * @return string
- */
- protected function indexNameCallback( $matches ) {
- return $this->indexName( $matches[1] );
- }
-
- /**
* Check to see if a named lock is available. This is non-blocking.
*
* @param string $lockName Name of lock to poll
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
index 2dfec41d..86950a89 100644
--- a/includes/db/DatabaseError.php
+++ b/includes/db/DatabaseError.php
@@ -168,12 +168,12 @@ class DBConnectionError extends DBExpectedError {
if ( $wgShowHostnames || $wgShowSQLErrors ) {
$info = str_replace(
'$1', Html::element( 'span', array( 'dir' => 'ltr' ), $this->error ),
- htmlspecialchars( $this->msg( 'dberr-info', '(Cannot contact the database server: $1)' ) )
+ htmlspecialchars( $this->msg( 'dberr-info', '(Cannot access the database: $1)' ) )
);
} else {
$info = htmlspecialchars( $this->msg(
'dberr-info-hidden',
- '(Cannot contact the database server)'
+ '(Cannot access the database)'
) );
}
@@ -229,7 +229,7 @@ class DBConnectionError extends DBExpectedError {
return;
}
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// Do nothing, just use the default page
}
}
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index af3cc72d..2b8f395f 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -380,6 +380,9 @@ class DatabaseMssql extends DatabaseBase {
* (optional) (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
* @return mixed Database result resource (feed to Database::fetchObject
* or whatever), or false on failure
+ * @throws DBQueryError
+ * @throws DBUnexpectedError
+ * @throws Exception
*/
public function select( $table, $vars, $conds = '', $fname = __METHOD__,
$options = array(), $join_conds = array()
@@ -515,7 +518,7 @@ class DatabaseMssql extends DatabaseBase {
$row = $this->fetchRow( $res );
if ( isset( $row['EstimateRows'] ) ) {
- $rows = $row['EstimateRows'];
+ $rows = (int)$row['EstimateRows'];
}
}
@@ -574,8 +577,8 @@ class DatabaseMssql extends DatabaseBase {
* @param array $arrToInsert
* @param string $fname
* @param array $options
- * @throws DBQueryError
* @return bool
+ * @throws Exception
*/
public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = array() ) {
# No rows to insert, easy just return now
@@ -692,7 +695,7 @@ class DatabaseMssql extends DatabaseBase {
if ( !is_null( $identity ) ) {
// then we want to get the identity column value we were assigned and save it off
$row = $ret->fetchObject();
- if( is_object( $row ) ){
+ if ( is_object( $row ) ) {
$this->mInsertId = $row->$identity;
}
}
@@ -713,8 +716,8 @@ class DatabaseMssql extends DatabaseBase {
* @param string $fname
* @param array $insertOptions
* @param array $selectOptions
- * @throws DBQueryError
* @return null|ResultWrapper
+ * @throws Exception
*/
public function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
$insertOptions = array(), $selectOptions = array()
@@ -761,6 +764,9 @@ class DatabaseMssql extends DatabaseBase {
* - IGNORE: Ignore unique key conflicts
* - LOW_PRIORITY: MySQL-specific, see MySQL manual.
* @return bool
+ * @throws DBUnexpectedError
+ * @throws Exception
+ * @throws MWException
*/
function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
$table = $this->tableName( $table );
@@ -806,64 +812,27 @@ class DatabaseMssql extends DatabaseBase {
'DatabaseBase::makeList called with incorrect parameters' );
}
- $first = true;
- $list = '';
+ if ( $mode != LIST_NAMES ) {
+ // In MS SQL, values need to be specially encoded when they are
+ // inserted into binary fields. Perform this necessary encoding
+ // for the specified set of columns.
+ foreach ( array_keys( $a ) as $field ) {
+ if ( !isset( $binaryColumns[$field] ) ) {
+ continue;
+ }
- foreach ( $a as $field => $value ) {
- if ( $mode != LIST_NAMES && isset( $binaryColumns[$field] ) ) {
- if ( is_array( $value ) ) {
- foreach ( $value as &$v ) {
+ if ( is_array( $a[$field] ) ) {
+ foreach ( $a[$field] as &$v ) {
$v = new MssqlBlob( $v );
}
+ unset( $v );
} else {
- $value = new MssqlBlob( $value );
- }
- }
-
- if ( !$first ) {
- if ( $mode == LIST_AND ) {
- $list .= ' AND ';
- } elseif ( $mode == LIST_OR ) {
- $list .= ' OR ';
- } else {
- $list .= ',';
+ $a[$field] = new MssqlBlob( $a[$field] );
}
- } else {
- $first = false;
- }
-
- if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
- $list .= "($value)";
- } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
- $list .= "$value";
- } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
- if ( count( $value ) == 0 ) {
- throw new MWException( __METHOD__ . ": empty input for field $field" );
- } elseif ( count( $value ) == 1 ) {
- // Special-case single values, as IN isn't terribly efficient
- // Don't necessarily assume the single key is 0; we don't
- // enforce linear numeric ordering on other arrays here.
- $value = array_values( $value );
- $list .= $field . " = " . $this->addQuotes( $value[0] );
- } else {
- $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
- }
- } elseif ( $value === null ) {
- if ( $mode == LIST_AND || $mode == LIST_OR ) {
- $list .= "$field IS ";
- } elseif ( $mode == LIST_SET ) {
- $list .= "$field = ";
- }
- $list .= 'NULL';
- } else {
- if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
- $list .= "$field = ";
- }
- $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
}
}
- return $list;
+ return parent::makeList( $a, $mode );
}
/**
@@ -893,6 +862,7 @@ class DatabaseMssql extends DatabaseBase {
* @param int $limit The SQL limit
* @param bool|int $offset The SQL offset (default false)
* @return array|string
+ * @throws DBUnexpectedError
*/
public function limitResult( $sql, $limit, $offset = false ) {
if ( $offset === false || $offset == 0 ) {
@@ -953,12 +923,8 @@ class DatabaseMssql extends DatabaseBase {
// Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
$pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
if ( preg_match( $pattern, $sql, $matches ) ) {
- // row_count = $matches[4]
$row_count = $matches[4];
- // offset = $matches[3] OR $matches[6]
- $offset = $matches[3] or
- $offset = $matches[6] or
- $offset = false;
+ $offset = $matches[3] ?: $matches[6] ?: false;
// strip the matching LIMIT clause out
$sql = str_replace( $matches[0], '', $sql );
@@ -1128,7 +1094,7 @@ class DatabaseMssql extends DatabaseBase {
}
/**
- * @param string $s
+ * @param string|Blob $s
* @return string
*/
public function addQuotes( $s ) {
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index dc4a67df..823d9b67 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -142,13 +142,6 @@ class DatabaseMysql extends DatabaseMysqlBase {
return mysql_select_db( $db, $this->mConn );
}
- /**
- * @return string
- */
- function getServerVersion() {
- return mysql_get_server_info( $this->mConn );
- }
-
protected function mysqlFreeResult( $res ) {
return mysql_free_result( $res );
}
diff --git a/includes/db/DatabaseMysqlBase.php b/includes/db/DatabaseMysqlBase.php
index ba0f39ff..aac95a8c 100644
--- a/includes/db/DatabaseMysqlBase.php
+++ b/includes/db/DatabaseMysqlBase.php
@@ -38,6 +38,9 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
protected $mFakeMaster = false;
+ /** @var string|null */
+ private $serverVersion = null;
+
/**
* @return string
*/
@@ -55,7 +58,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
function open( $server, $user, $password, $dbName ) {
global $wgAllDBsAreLocalhost, $wgSQLMode;
- wfProfileIn( __METHOD__ );
# Debugging hack -- fake cluster
if ( $wgAllDBsAreLocalhost ) {
@@ -69,8 +71,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->mPassword = $password;
$this->mDBname = $dbName;
- wfProfileIn( "dbconnect-$server" );
-
# The kernel's default SYN retransmission period is far too slow for us,
# so we use a short timeout plus a manual retry. Retrying means that a small
# but finite rate of SYN packet loss won't cause user-visible errors.
@@ -79,27 +79,27 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
try {
$this->mConn = $this->mysqlConnect( $realServer );
} catch ( Exception $ex ) {
- wfProfileOut( "dbconnect-$server" );
- wfProfileOut( __METHOD__ );
$this->restoreErrorHandler();
throw $ex;
}
$error = $this->restoreErrorHandler();
- wfProfileOut( "dbconnect-$server" );
-
# Always log connection errors
if ( !$this->mConn ) {
if ( !$error ) {
$error = $this->lastError();
}
- wfLogDBError( "Error connecting to {$this->mServer}: $error" );
+ wfLogDBError(
+ "Error connecting to {db_server}: {error}",
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ 'error' => $error,
+ ) )
+ );
wfDebug( "DB connection error\n" .
"Server: $server, User: $user, Password: " .
substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
- wfProfileOut( __METHOD__ );
-
$this->reportConnectionError( $error );
}
@@ -108,12 +108,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$success = $this->selectDB( $dbName );
wfRestoreWarnings();
if ( !$success ) {
- wfLogDBError( "Error selecting database $dbName on server {$this->mServer}" );
+ wfLogDBError(
+ "Error selecting database {db_name} on server {db_server}",
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ ) )
+ );
wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
"from client host " . wfHostname() . "\n" );
- wfProfileOut( __METHOD__ );
-
$this->reportConnectionError( "Error selecting database $dbName" );
}
}
@@ -123,20 +126,29 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->reportConnectionError( "Error setting character set" );
}
+ // Abstract over any insane MySQL defaults
+ $set = array( 'group_concat_max_len = 262144' );
// Set SQL mode, default is turning them all off, can be overridden or skipped with null
if ( is_string( $wgSQLMode ) ) {
- $mode = $this->addQuotes( $wgSQLMode );
+ $set[] = 'sql_mode = ' . $this->addQuotes( $wgSQLMode );
+ }
+
+ if ( $set ) {
// Use doQuery() to avoid opening implicit transactions (DBO_TRX)
- $success = $this->doQuery( "SET sql_mode = $mode", __METHOD__ );
+ $success = $this->doQuery( 'SET ' . implode( ', ', $set ), __METHOD__ );
if ( !$success ) {
- wfLogDBError( "Error setting sql_mode to $mode on server {$this->mServer}" );
- wfProfileOut( __METHOD__ );
- $this->reportConnectionError( "Error setting sql_mode to $mode" );
+ wfLogDBError(
+ 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ ) )
+ );
+ $this->reportConnectionError(
+ 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)' );
}
}
$this->mOpened = true;
- wfProfileOut( __METHOD__ );
return true;
}
@@ -456,7 +468,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
}
- return $rows;
+ return (int)$rows;
}
/**
@@ -652,7 +664,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
return '0'; // http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html
}
- wfProfileIn( __METHOD__ );
# Commit any open transactions
$this->commit( __METHOD__, 'flush' );
@@ -661,18 +672,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
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;
}
@@ -692,8 +700,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
}
- wfProfileOut( __METHOD__ );
-
return $status;
}
@@ -763,9 +769,9 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* @return string
*/
public function getSoftwareLink() {
- // MariaDB includes its name in its version string (sent when the connection is opened),
- // and this is how MariaDB's version of the mysql command-line client identifies MariaDB
- // servers (see the mariadb_connection() function in libmysql/libmysql.c).
+ // MariaDB includes its name in its version string; this is how MariaDB's version of
+ // the mysql command-line client identifies MariaDB servers (see mariadb_connection()
+ // in libmysql/libmysql.c).
$version = $this->getServerVersion();
if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
return '[{{int:version-db-mariadb-url}} MariaDB]';
@@ -778,6 +784,19 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
+ * @return string
+ */
+ public function getServerVersion() {
+ // Not using mysql_get_server_info() or similar for consistency: in the handshake,
+ // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
+ // it off (see RPL_VERSION_HACK in include/mysql_com.h).
+ if ( $this->serverVersion === null ) {
+ $this->serverVersion = $this->selectField( '', 'VERSION()', '', __METHOD__ );
+ }
+ return $this->serverVersion;
+ }
+
+ /**
* @param array $options
*/
public function setSessionOptions( array $options ) {
@@ -1169,7 +1188,8 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
class MySQLField implements Field {
private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary;
+ $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary,
+ $is_numeric, $is_blob, $is_unsigned, $is_zerofill;
function __construct( $info ) {
$this->name = $info->name;
@@ -1183,6 +1203,10 @@ class MySQLField implements Field {
$this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
$this->type = $info->type;
$this->binary = isset( $info->binary ) ? $info->binary : false;
+ $this->is_numeric = isset( $info->numeric ) ? $info->numeric : false;
+ $this->is_blob = isset( $info->blob ) ? $info->blob : false;
+ $this->is_unsigned = isset( $info->unsigned ) ? $info->unsigned : false;
+ $this->is_zerofill = isset( $info->zerofill ) ? $info->zerofill : false;
}
/**
@@ -1231,21 +1255,54 @@ class MySQLField implements Field {
return $this->is_multiple;
}
+ /**
+ * @return bool
+ */
function isBinary() {
return $this->binary;
}
+
+ /**
+ * @return bool
+ */
+ function isNumeric() {
+ return $this->is_numeric;
+ }
+
+ /**
+ * @return bool
+ */
+ function isBlob() {
+ return $this->is_blob;
+ }
+
+ /**
+ * @return bool
+ */
+ function isUnsigned() {
+ return $this->is_unsigned;
+ }
+
+ /**
+ * @return bool
+ */
+ function isZerofill() {
+ return $this->is_zerofill;
+ }
}
class MySQLMasterPos implements DBMasterPos {
/** @var string */
public $file;
-
- /** @var int Timestamp */
+ /** @var int Position */
public $pos;
+ /** @var float UNIX timestamp */
+ public $asOfTime = 0.0;
function __construct( $file, $pos ) {
$this->file = $file;
$this->pos = $pos;
+ $this->asOfTime = microtime( true );
}
function __toString() {
@@ -1271,4 +1328,8 @@ class MySQLMasterPos implements DBMasterPos {
return ( $thisPos && $thatPos && $thisPos >= $thatPos );
}
+
+ function asOfTime() {
+ return $this->asOfTime;
+ }
}
diff --git a/includes/db/DatabaseMysqli.php b/includes/db/DatabaseMysqli.php
index a03c9aaf..ad12e196 100644
--- a/includes/db/DatabaseMysqli.php
+++ b/includes/db/DatabaseMysqli.php
@@ -166,13 +166,6 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
/**
- * @return string
- */
- function getServerVersion() {
- return $this->mConn->server_info;
- }
-
- /**
* @param mysqli $res
* @return bool
*/
@@ -231,11 +224,18 @@ class DatabaseMysqli extends DatabaseMysqlBase {
*/
protected function mysqlFetchField( $res, $n ) {
$field = $res->fetch_field_direct( $n );
+
+ // Add missing properties to result (using flags property)
+ // which will be part of function mysql-fetch-field for backward compatibility
$field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
$field->primary_key = $field->flags & MYSQLI_PRI_KEY_FLAG;
$field->unique_key = $field->flags & MYSQLI_UNIQUE_KEY_FLAG;
$field->multiple_key = $field->flags & MYSQLI_MULTIPLE_KEY_FLAG;
$field->binary = $field->flags & MYSQLI_BINARY_FLAG;
+ $field->numeric = $field->flags & MYSQLI_NUM_FLAG;
+ $field->blob = $field->flags & MYSQLI_BLOB_FLAG;
+ $field->unsigned = $field->flags & MYSQLI_UNSIGNED_FLAG;
+ $field->zerofill = $field->flags & MYSQLI_ZEROFILL_FLAG;
return $field;
}
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index f031f780..9b00fbd1 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -207,29 +207,15 @@ class DatabaseOracle extends DatabaseBase {
/** @var array */
private $mFieldInfoCache = array();
- function __construct( $p = null ) {
+ function __construct( array $p ) {
global $wgDBprefix;
- if ( !is_array( $p ) ) { // legacy calling pattern
- wfDeprecated( __METHOD__ . " method called without parameter array.", "1.22" );
- $args = func_get_args();
- $p = array(
- 'host' => 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
- );
- }
if ( $p['tablePrefix'] == 'get from global' ) {
$p['tablePrefix'] = $wgDBprefix;
}
$p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
parent::__construct( $p );
- wfRunHooks( 'DatabaseOraclePostInit', array( $this ) );
+ Hooks::run( 'DatabaseOraclePostInit', array( $this ) );
}
function __destruct() {
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index ce14d7a9..9287f7a6 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -530,10 +530,12 @@ class DatabasePostgres extends DatabaseBase {
return;
}
}
- /* Transaction stays in the ERROR state until rolledback */
+ /* Transaction stays in the ERROR state until rolled back */
if ( $this->mTrxLevel ) {
+ $ignore = $this->ignoreErrors( true );
$this->rollback( __METHOD__ );
- };
+ $this->ignoreErrors( $ignore );
+ }
parent::reportQueryError( $error, $errno, $sql, $fname, false );
}
@@ -712,7 +714,7 @@ class DatabasePostgres extends DatabaseBase {
$row = $this->fetchRow( $res );
$count = array();
if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
- $rows = $count[1];
+ $rows = (int)$count[1];
}
}
@@ -1495,12 +1497,14 @@ SQL;
* @return Blob
*/
function encodeBlob( $b ) {
- return new Blob( pg_escape_bytea( $this->mConn, $b ) );
+ return new PostgresBlob( pg_escape_bytea( $b ) );
}
function decodeBlob( $b ) {
- if ( $b instanceof Blob ) {
+ if ( $b instanceof PostgresBlob ) {
$b = $b->fetch();
+ } elseif ( $b instanceof Blob ) {
+ return $b->fetch();
}
return pg_unescape_bytea( $b );
@@ -1520,7 +1524,12 @@ SQL;
} elseif ( is_bool( $s ) ) {
return intval( $s );
} elseif ( $s instanceof Blob ) {
- return "'" . $s->fetch( $s ) . "'";
+ if ( $s instanceof PostgresBlob ) {
+ $s = $s->fetch();
+ } else {
+ $s = pg_escape_bytea( $this->mConn, $s->fetch() );
+ }
+ return "'$s'";
}
return "'" . pg_escape_string( $this->mConn, $s ) . "'";
@@ -1692,3 +1701,5 @@ SQL;
return wfBaseConvert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
}
} // end DatabasePostgres class
+
+class PostgresBlob extends Blob {}
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index dd2e813e..ed86bab1 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -29,8 +29,14 @@ class DatabaseSqlite extends DatabaseBase {
/** @var bool Whether full text is enabled */
private static $fulltextEnabled = null;
+ /** @var string Directory */
+ protected $dbDir;
+
/** @var string File name for SQLite database file */
- public $mDatabaseFile;
+ protected $dbPath;
+
+ /** @var string Transaction mode */
+ protected $trxMode;
/** @var int The number of rows affected as an integer */
protected $mAffectedRows;
@@ -44,35 +50,63 @@ class DatabaseSqlite extends DatabaseBase {
/** @var FSLockManager (hopefully on the same server as the DB) */
protected $lockMgr;
- function __construct( $p = null ) {
+ /**
+ * Additional params include:
+ * - dbDirectory : directory containing the DB and the lock file directory
+ * [defaults to $wgSQLiteDataDir]
+ * - dbFilePath : use this to force the path of the DB file
+ * - trxMode : one of (deferred, immediate, exclusive)
+ * @param array $p
+ */
+ function __construct( array $p ) {
global $wgSharedDB, $wgSQLiteDataDir;
- if ( !is_array( $p ) ) { // legacy calling pattern
- wfDeprecated( __METHOD__ . " method called without parameter array.", "1.22" );
- $args = func_get_args();
- $p = array(
- 'host' => 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->mDBname = $p['dbname'];
- parent::__construct( $p );
- // parent doesn't open when $user is false, but we can work with $dbName
- if ( $p['dbname'] && !$this->isOpen() ) {
- if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
- if ( $wgSharedDB ) {
- $this->attachDatabase( $wgSharedDB );
+ $this->dbDir = isset( $p['dbDirectory'] ) ? $p['dbDirectory'] : $wgSQLiteDataDir;
+
+ if ( isset( $p['dbFilePath'] ) ) {
+ $this->mFlags = isset( $p['flags'] ) ? $p['flags'] : 0;
+ // Standalone .sqlite file mode
+ $this->openFile( $p['dbFilePath'] );
+ // @FIXME: clean up base constructor so this can call super instead
+ $this->mTrxAtomicLevels = new SplStack;
+ } else {
+ $this->mDBname = $p['dbname'];
+ // Stock wiki mode using standard file names per DB
+ parent::__construct( $p );
+ // parent doesn't open when $user is false, but we can work with $dbName
+ if ( $p['dbname'] && !$this->isOpen() ) {
+ if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
+ if ( $wgSharedDB ) {
+ $this->attachDatabase( $wgSharedDB );
+ }
}
}
}
- $this->lockMgr = new FSLockManager( array( 'lockDirectory' => "$wgSQLiteDataDir/locks" ) );
+ $this->trxMode = isset( $p['trxMode'] ) ? strtoupper( $p['trxMode'] ) : null;
+ if ( $this->trxMode &&
+ !in_array( $this->trxMode, array( 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ) )
+ ) {
+ $this->trxMode = null;
+ wfWarn( "Invalid SQLite transaction mode provided." );
+ }
+
+ $this->lockMgr = new FSLockManager( array( 'lockDirectory' => "{$this->dbDir}/locks" ) );
+ }
+
+ /**
+ * @param string $filename
+ * @param array $p Options map; supports:
+ * - flags : (same as __construct counterpart)
+ * - trxMode : (same as __construct counterpart)
+ * - dbDirectory : (same as __construct counterpart)
+ * @return DatabaseSqlite
+ * @since 1.25
+ */
+ public static function newStandaloneInstance( $filename, array $p = array() ) {
+ $p['dbFilePath'] = $filename;
+
+ return new self( $p );
}
/**
@@ -103,10 +137,8 @@ class DatabaseSqlite extends DatabaseBase {
* @return PDO
*/
function open( $server, $user, $pass, $dbName ) {
- global $wgSQLiteDataDir;
-
$this->close();
- $fileName = self::generateFileName( $wgSQLiteDataDir, $dbName );
+ $fileName = self::generateFileName( $this->dbDir, $dbName );
if ( !is_readable( $fileName ) ) {
$this->mConn = false;
throw new DBConnectionError( $this, "SQLite database not accessible" );
@@ -123,10 +155,10 @@ class DatabaseSqlite extends DatabaseBase {
* @throws DBConnectionError
* @return PDO|bool SQL connection or false if failed
*/
- function openFile( $fileName ) {
+ protected function openFile( $fileName ) {
$err = false;
- $this->mDatabaseFile = $fileName;
+ $this->dbPath = $fileName;
try {
if ( $this->mFlags & DBO_PERSISTENT ) {
$this->mConn = new PDO( "sqlite:$fileName", '', '',
@@ -144,8 +176,8 @@ class DatabaseSqlite extends DatabaseBase {
}
$this->mOpened = !!$this->mConn;
- # set error codes only, don't raise exceptions
if ( $this->mOpened ) {
+ # Set error codes only, don't raise exceptions
$this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
# Enforce LIKE to be case sensitive, just like MySQL
$this->query( 'PRAGMA case_sensitive_like = 1' );
@@ -157,6 +189,14 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
+ * @return string SQLite DB file path
+ * @since 1.25
+ */
+ public function getDbFilePath() {
+ return $this->dbPath;
+ }
+
+ /**
* Does not actually close the connection, just destroys the reference for GC to do its work
* @return bool
*/
@@ -206,8 +246,7 @@ class DatabaseSqlite extends DatabaseBase {
$cachedResult = false;
$table = 'dummy_search_test';
- $db = new DatabaseSqliteStandalone( ':memory:' );
-
+ $db = self::newStandaloneInstance( ':memory:' );
if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
$cachedResult = 'FTS3';
}
@@ -223,14 +262,13 @@ class DatabaseSqlite extends DatabaseBase {
* @param string $name Database name to be used in queries like
* SELECT foo FROM dbname.table
* @param bool|string $file Database file name. If omitted, will be generated
- * using $name and $wgSQLiteDataDir
+ * using $name and configured data directory
* @param string $fname Calling function name
* @return ResultWrapper
*/
function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
- global $wgSQLiteDataDir;
if ( !$file ) {
- $file = self::generateFileName( $wgSQLiteDataDir, $name );
+ $file = self::generateFileName( $this->dbDir, $name );
}
$file = $this->addQuotes( $file );
@@ -712,37 +750,14 @@ class DatabaseSqlite extends DatabaseBase {
}
protected function doBegin( $fname = '' ) {
- if ( $this->mTrxLevel == 1 ) {
- $this->commit( __METHOD__ );
- }
- try {
- $this->mConn->beginTransaction();
- } catch ( PDOException $e ) {
- throw new DBUnexpectedError( $this, 'Error in BEGIN query: ' . $e->getMessage() );
+ if ( $this->trxMode ) {
+ $this->query( "BEGIN {$this->trxMode}", $fname );
+ } else {
+ $this->query( 'BEGIN', $fname );
}
$this->mTrxLevel = 1;
}
- protected function doCommit( $fname = '' ) {
- if ( $this->mTrxLevel == 0 ) {
- return;
- }
- try {
- $this->mConn->commit();
- } catch ( PDOException $e ) {
- throw new DBUnexpectedError( $this, 'Error in COMMIT query: ' . $e->getMessage() );
- }
- $this->mTrxLevel = 0;
- }
-
- protected function doRollback( $fname = '' ) {
- if ( $this->mTrxLevel == 0 ) {
- return;
- }
- $this->mConn->rollBack();
- $this->mTrxLevel = 0;
- }
-
/**
* @param string $s
* @return string
@@ -787,6 +802,10 @@ class DatabaseSqlite extends DatabaseBase {
// https://bugs.php.net/bug.php?id=63419
// There was already a similar report for SQLite3::escapeString, bug #62361:
// https://bugs.php.net/bug.php?id=62361
+ // There is an additional bug regarding sorting this data after insert
+ // on older versions of sqlite shipped with ubuntu 12.04
+ // https://bugzilla.wikimedia.org/show_bug.cgi?id=72367
+ wfDebugLog( __CLASS__, __FUNCTION__ . ': Quoting value containing null byte. For consistency all binary data should have been first processed with self::encodeBlob()' );
return "x'" . bin2hex( $s ) . "'";
} else {
return $this->mConn->quote( $s );
@@ -882,11 +901,9 @@ class DatabaseSqlite extends DatabaseBase {
}
public function lock( $lockName, $method, $timeout = 5 ) {
- global $wgSQLiteDataDir;
-
- if ( !is_dir( "$wgSQLiteDataDir/locks" ) ) { // create dir as needed
- if ( !is_writable( $wgSQLiteDataDir ) || !mkdir( "$wgSQLiteDataDir/locks" ) ) {
- throw new DBError( "Cannot create directory \"$wgSQLiteDataDir/locks\"." );
+ if ( !is_dir( "{$this->dbDir}/locks" ) ) { // create dir as needed
+ if ( !is_writable( $this->dbDir ) || !mkdir( "{$this->dbDir}/locks" ) ) {
+ throw new DBError( "Cannot create directory \"{$this->dbDir}/locks\"." );
}
}
@@ -981,18 +998,6 @@ class DatabaseSqlite extends DatabaseBase {
} // end DatabaseSqlite class
/**
- * This class allows simple acccess to a SQLite database independently from main database settings
- * @ingroup Database
- */
-class DatabaseSqliteStandalone extends DatabaseSqlite {
- public function __construct( $fileName, $flags = 0 ) {
- $this->mFlags = $flags;
- $this->tablePrefix( null );
- $this->openFile( $fileName );
- }
-}
-
-/**
* @ingroup Database
*/
class SQLiteField implements Field {
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
index c1e80d33..9a520ff9 100644
--- a/includes/db/DatabaseUtility.php
+++ b/includes/db/DatabaseUtility.php
@@ -22,29 +22,6 @@
*/
/**
- * Utility class.
- * @ingroup Database
- */
-class DBObject {
- public $mData;
-
- function __construct( $data ) {
- $this->mData = $data;
- }
-
- /**
- * @return bool
- */
- function isLOB() {
- return false;
- }
-
- function data() {
- return $this->mData;
- }
-}
-
-/**
* Utility class
* @ingroup Database
*
@@ -339,4 +316,8 @@ class LikeMatch {
* The implementation details of this opaque type are up to the database subclass.
*/
interface DBMasterPos {
+ /**
+ * @return float UNIX timestamp
+ */
+ public function asOfTime();
}
diff --git a/includes/db/IORMTable.php b/includes/db/IORMTable.php
index 4dc693ac..b2527f95 100644
--- a/includes/db/IORMTable.php
+++ b/includes/db/IORMTable.php
@@ -94,7 +94,7 @@ interface IORMTable {
public function getSummaryFields();
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
* @see DatabaseBase::select()
@@ -113,7 +113,7 @@ interface IORMTable {
array $options = array(), $functionName = null );
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
* @since 1.20
@@ -145,7 +145,7 @@ interface IORMTable {
array $options = array(), $functionName = null );
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as associative arrays.
* Provided field names get prefixed.
* Returned field names will not have a prefix.
@@ -170,7 +170,7 @@ interface IORMTable {
array $options = array(), $collapse = true, $functionName = null );
/**
- * Selects the the specified fields of the first matching record.
+ * Selects the specified fields of the first matching record.
* Field names get prefixed.
*
* @since 1.20
@@ -186,7 +186,7 @@ interface IORMTable {
array $options = array(), $functionName = null );
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions. Field names do NOT get prefixed.
*
* @since 1.20
@@ -202,7 +202,7 @@ interface IORMTable {
array $options = array(), $functionName = null );
/**
- * Selects the the specified fields of the first record matching the provided
+ * Selects the specified fields of the first record matching the provided
* conditions and returns it as an associative array, or false when nothing matches.
* This method makes use of selectFields and expects the same parameters and
* returns the same results (if there are any, if there are none, this method returns false).
@@ -442,10 +442,11 @@ interface IORMTable {
* Takes an array of field names with prefix and returns the unprefixed equivalent.
*
* @since 1.20
+ * @deprecated since 1.25, will be removed
*
- * @param array $fieldNames
+ * @param string[] $fieldNames
*
- * @return array
+ * @return string[]
*/
public function unprefixFieldNames( array $fieldNames );
@@ -453,6 +454,7 @@ interface IORMTable {
* Takes a field name with prefix and returns the unprefixed equivalent.
*
* @since 1.20
+ * @deprecated since 1.25, will be removed
*
* @param string $fieldName
*
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index 73456e23..4551e2d7 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -27,7 +27,7 @@
*/
abstract class LBFactory {
/** @var LBFactory */
- protected static $instance;
+ private static $instance;
/**
* Disables all access to the load balancer, will cause all database access
@@ -43,7 +43,7 @@ abstract class LBFactory {
*
* @return LBFactory
*/
- static function &singleton() {
+ public static function singleton() {
global $wgLBFactoryConf;
if ( is_null( self::$instance ) ) {
@@ -87,7 +87,7 @@ abstract class LBFactory {
/**
* Shut down, close connections and destroy the cached instance.
*/
- static function destroyInstance() {
+ public static function destroyInstance() {
if ( self::$instance ) {
self::$instance->shutdown();
self::$instance->forEachLBCallMethod( 'closeAll' );
@@ -100,7 +100,7 @@ abstract class LBFactory {
*
* @param LBFactory $instance
*/
- static function setInstance( $instance ) {
+ public static function setInstance( $instance ) {
self::destroyInstance();
self::$instance = $instance;
}
@@ -109,7 +109,7 @@ abstract class LBFactory {
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
* @param array $conf
*/
- abstract function __construct( $conf );
+ abstract public function __construct( array $conf );
/**
* Create a new load balancer object. The resulting object will be untracked,
@@ -118,7 +118,7 @@ abstract class LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- abstract function newMainLB( $wiki = false );
+ abstract public function newMainLB( $wiki = false );
/**
* Get a cached (tracked) load balancer object.
@@ -126,7 +126,7 @@ abstract class LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- abstract function getMainLB( $wiki = false );
+ abstract public function getMainLB( $wiki = false );
/**
* Create a new load balancer for external storage. The resulting object will be
@@ -137,7 +137,7 @@ abstract class LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- abstract function newExternalLB( $cluster, $wiki = false );
+ abstract protected function newExternalLB( $cluster, $wiki = false );
/**
* Get a cached (tracked) load balancer for external storage
@@ -146,7 +146,7 @@ abstract class LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- abstract function &getExternalLB( $cluster, $wiki = false );
+ abstract public function &getExternalLB( $cluster, $wiki = false );
/**
* Execute a function for each tracked load balancer
@@ -156,13 +156,13 @@ abstract class LBFactory {
* @param callable $callback
* @param array $params
*/
- abstract function forEachLB( $callback, $params = array() );
+ abstract public function forEachLB( $callback, array $params = array() );
/**
* Prepare all tracked load balancers for shutdown
* STUB
*/
- function shutdown() {
+ public function shutdown() {
}
/**
@@ -171,24 +171,16 @@ abstract class LBFactory {
* @param string $methodName
* @param array $args
*/
- function forEachLBCallMethod( $methodName, $args = array() ) {
- $this->forEachLB( array( $this, 'callMethod' ), array( $methodName, $args ) );
- }
-
- /**
- * Private helper for forEachLBCallMethod
- * @param LoadBalancer $loadBalancer
- * @param string $methodName
- * @param array $args
- */
- function callMethod( $loadBalancer, $methodName, $args ) {
- call_user_func_array( array( $loadBalancer, $methodName ), $args );
+ private function forEachLBCallMethod( $methodName, array $args = array() ) {
+ $this->forEachLB( function ( LoadBalancer $loadBalancer, $methodName, array $args ) {
+ call_user_func_array( array( $loadBalancer, $methodName ), $args );
+ }, array( $methodName, $args ) );
}
/**
* Commit changes on all master connections
*/
- function commitMasterChanges() {
+ public function commitMasterChanges() {
$this->forEachLBCallMethod( 'commitMasterChanges' );
}
@@ -196,7 +188,7 @@ abstract class LBFactory {
* Rollback changes on all master connections
* @since 1.23
*/
- function rollbackMasterChanges() {
+ public function rollbackMasterChanges() {
$this->forEachLBCallMethod( 'rollbackMasterChanges' );
}
@@ -205,7 +197,7 @@ abstract class LBFactory {
* @since 1.23
* @return bool
*/
- function hasMasterChanges() {
+ public function hasMasterChanges() {
$ret = false;
$this->forEachLB( function ( $lb ) use ( &$ret ) {
$ret = $ret || $lb->hasMasterChanges();
@@ -219,15 +211,15 @@ abstract class LBFactory {
*/
class LBFactorySimple extends LBFactory {
/** @var LoadBalancer */
- protected $mainLB;
+ private $mainLB;
/** @var LoadBalancer[] */
- protected $extLBs = array();
+ private $extLBs = array();
/** @var ChronologyProtector */
- protected $chronProt;
+ private $chronProt;
- function __construct( $conf ) {
+ public function __construct( array $conf ) {
$this->chronProt = new ChronologyProtector;
}
@@ -235,7 +227,7 @@ class LBFactorySimple extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function newMainLB( $wiki = false ) {
+ public function newMainLB( $wiki = false ) {
global $wgDBservers;
if ( $wgDBservers ) {
$servers = $wgDBservers;
@@ -274,7 +266,7 @@ class LBFactorySimple extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function getMainLB( $wiki = false ) {
+ public function getMainLB( $wiki = false ) {
if ( !isset( $this->mainLB ) ) {
$this->mainLB = $this->newMainLB( $wiki );
$this->mainLB->parentInfo( array( 'id' => 'main' ) );
@@ -290,7 +282,7 @@ class LBFactorySimple extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function newExternalLB( $cluster, $wiki = false ) {
+ protected function newExternalLB( $cluster, $wiki = false ) {
global $wgExternalServers;
if ( !isset( $wgExternalServers[$cluster] ) ) {
throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
@@ -306,7 +298,7 @@ class LBFactorySimple extends LBFactory {
* @param bool|string $wiki
* @return array
*/
- function &getExternalLB( $cluster, $wiki = false ) {
+ public function &getExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
@@ -324,7 +316,7 @@ class LBFactorySimple extends LBFactory {
* @param callable $callback
* @param array $params
*/
- function forEachLB( $callback, $params = array() ) {
+ public function forEachLB( $callback, array $params = array() ) {
if ( isset( $this->mainLB ) ) {
call_user_func_array( $callback, array_merge( array( $this->mainLB ), $params ) );
}
@@ -333,7 +325,7 @@ class LBFactorySimple extends LBFactory {
}
}
- function shutdown() {
+ public function shutdown() {
if ( $this->mainLB ) {
$this->chronProt->shutdownLB( $this->mainLB );
}
@@ -352,26 +344,26 @@ class LBFactorySimple extends LBFactory {
* LBFactory::enableBackend() to return to normal behavior
*/
class LBFactoryFake extends LBFactory {
- function __construct( $conf ) {
+ public function __construct( array $conf ) {
}
- function newMainLB( $wiki = false ) {
+ public function newMainLB( $wiki = false ) {
throw new DBAccessError;
}
- function getMainLB( $wiki = false ) {
+ public function getMainLB( $wiki = false ) {
throw new DBAccessError;
}
- function newExternalLB( $cluster, $wiki = false ) {
+ protected function newExternalLB( $cluster, $wiki = false ) {
throw new DBAccessError;
}
- function &getExternalLB( $cluster, $wiki = false ) {
+ public function &getExternalLB( $cluster, $wiki = false ) {
throw new DBAccessError;
}
- function forEachLB( $callback, $params = array() ) {
+ public function forEachLB( $callback, array $params = array() ) {
}
}
@@ -379,7 +371,7 @@ class LBFactoryFake extends LBFactory {
* Exception class for attempted DB access
*/
class DBAccessError extends MWException {
- function __construct() {
+ public function __construct() {
parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
"This is not allowed." );
}
diff --git a/includes/db/LBFactoryMulti.php b/includes/db/LBFactoryMulti.php
index bac96523..aa305ab1 100644
--- a/includes/db/LBFactoryMulti.php
+++ b/includes/db/LBFactoryMulti.php
@@ -77,82 +77,82 @@ class LBFactoryMulti extends LBFactory {
// Required settings
/** @var array A map of database names to section names */
- protected $sectionsByDB;
+ private $sectionsByDB;
/**
* @var array A 2-d map. For each section, gives a map of server names to
* load ratios
*/
- protected $sectionLoads;
+ private $sectionLoads;
/**
* @var array A server info associative array as documented for
* $wgDBservers. The host, hostName and load entries will be
* overridden
*/
- protected $serverTemplate;
+ private $serverTemplate;
// Optional settings
/** @var array A 3-d map giving server load ratios for each section and group */
- protected $groupLoadsBySection = array();
+ private $groupLoadsBySection = array();
/** @var array A 3-d map giving server load ratios by DB name */
- protected $groupLoadsByDB = array();
+ private $groupLoadsByDB = array();
/** @var array A map of hostname to IP address */
- protected $hostsByName = array();
+ private $hostsByName = array();
/** @var array A map of external storage cluster name to server load map */
- protected $externalLoads = array();
+ private $externalLoads = array();
/**
* @var array A set of server info keys overriding serverTemplate for
* external storage
*/
- protected $externalTemplateOverrides;
+ private $externalTemplateOverrides;
/**
* @var array A 2-d map overriding serverTemplate and
* externalTemplateOverrides on a server-by-server basis. Applies to both
* core and external storage
*/
- protected $templateOverridesByServer;
+ private $templateOverridesByServer;
/** @var array A 2-d map overriding the server info by external storage cluster */
- protected $templateOverridesByCluster;
+ private $templateOverridesByCluster;
/** @var array An override array for all master servers */
- protected $masterTemplateOverrides;
+ private $masterTemplateOverrides;
/**
* @var array|bool A map of section name to read-only message. Missing or
* false for read/write
*/
- protected $readOnlyBySection = array();
+ private $readOnlyBySection = array();
// Other stuff
/** @var array Load balancer factory configuration */
- protected $conf;
+ private $conf;
/** @var LoadBalancer[] */
- protected $mainLBs = array();
+ private $mainLBs = array();
/** @var LoadBalancer[] */
- protected $extLBs = array();
+ private $extLBs = array();
/** @var string */
- protected $lastWiki;
+ private $lastWiki;
/** @var string */
- protected $lastSection;
+ private $lastSection;
/**
* @param array $conf
* @throws MWException
*/
- function __construct( $conf ) {
+ public function __construct( array $conf ) {
$this->chronProt = new ChronologyProtector;
$this->conf = $conf;
$required = array( 'sectionsByDB', 'sectionLoads', 'serverTemplate' );
@@ -186,7 +186,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki
* @return string
*/
- function getSectionForWiki( $wiki = false ) {
+ private function getSectionForWiki( $wiki = false ) {
if ( $this->lastWiki === $wiki ) {
return $this->lastSection;
}
@@ -206,7 +206,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function newMainLB( $wiki = false ) {
+ public function newMainLB( $wiki = false ) {
list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
$section = $this->getSectionForWiki( $wiki );
$groupLoads = array();
@@ -229,7 +229,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function getMainLB( $wiki = false ) {
+ public function getMainLB( $wiki = false ) {
$section = $this->getSectionForWiki( $wiki );
if ( !isset( $this->mainLBs[$section] ) ) {
$lb = $this->newMainLB( $wiki, $section );
@@ -247,7 +247,7 @@ class LBFactoryMulti extends LBFactory {
* @throws MWException
* @return LoadBalancer
*/
- function newExternalLB( $cluster, $wiki = false ) {
+ protected function newExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->externalLoads[$cluster] ) ) {
throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
@@ -267,7 +267,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- function &getExternalLB( $cluster, $wiki = false ) {
+ public function &getExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
@@ -285,7 +285,7 @@ class LBFactoryMulti extends LBFactory {
* @param array $groupLoads
* @return LoadBalancer
*/
- function newLoadBalancer( $template, $loads, $groupLoads ) {
+ private function newLoadBalancer( $template, $loads, $groupLoads ) {
$servers = $this->makeServerArray( $template, $loads, $groupLoads );
$lb = new LoadBalancer( array(
'servers' => $servers,
@@ -302,7 +302,7 @@ class LBFactoryMulti extends LBFactory {
* @param array $groupLoads
* @return array
*/
- function makeServerArray( $template, $loads, $groupLoads ) {
+ private function makeServerArray( $template, $loads, $groupLoads ) {
$servers = array();
$master = true;
$groupLoadsByServer = $this->reindexGroupLoads( $groupLoads );
@@ -344,7 +344,7 @@ class LBFactoryMulti extends LBFactory {
* @param array $groupLoads
* @return array
*/
- function reindexGroupLoads( $groupLoads ) {
+ private function reindexGroupLoads( $groupLoads ) {
$reindexed = array();
foreach ( $groupLoads as $group => $loads ) {
foreach ( $loads as $server => $load ) {
@@ -360,7 +360,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki
* @return array
*/
- function getDBNameAndPrefix( $wiki = false ) {
+ private function getDBNameAndPrefix( $wiki = false ) {
if ( $wiki === false ) {
global $wgDBname, $wgDBprefix;
@@ -377,7 +377,7 @@ class LBFactoryMulti extends LBFactory {
* @param callable $callback
* @param array $params
*/
- function forEachLB( $callback, $params = array() ) {
+ public function forEachLB( $callback, array $params = array() ) {
foreach ( $this->mainLBs as $lb ) {
call_user_func_array( $callback, array_merge( array( $lb ), $params ) );
}
@@ -386,7 +386,7 @@ class LBFactoryMulti extends LBFactory {
}
}
- function shutdown() {
+ public function shutdown() {
foreach ( $this->mainLBs as $lb ) {
$this->chronProt->shutdownLB( $lb );
}
diff --git a/includes/db/LBFactorySingle.php b/includes/db/LBFactorySingle.php
index 3a4d829b..a41dadfa 100644
--- a/includes/db/LBFactorySingle.php
+++ b/includes/db/LBFactorySingle.php
@@ -26,13 +26,13 @@
*/
class LBFactorySingle extends LBFactory {
/** @var LoadBalancerSingle */
- protected $lb;
+ private $lb;
/**
* @param array $conf An associative array with one member:
* - connection: The DatabaseBase connection object
*/
- function __construct( $conf ) {
+ public function __construct( array $conf ) {
$this->lb = new LoadBalancerSingle( $conf );
}
@@ -40,7 +40,7 @@ class LBFactorySingle extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancerSingle
*/
- function newMainLB( $wiki = false ) {
+ public function newMainLB( $wiki = false ) {
return $this->lb;
}
@@ -48,7 +48,7 @@ class LBFactorySingle extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancerSingle
*/
- function getMainLB( $wiki = false ) {
+ public function getMainLB( $wiki = false ) {
return $this->lb;
}
@@ -57,7 +57,7 @@ class LBFactorySingle extends LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancerSingle
*/
- function newExternalLB( $cluster, $wiki = false ) {
+ protected function newExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
@@ -66,7 +66,7 @@ class LBFactorySingle extends LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancerSingle
*/
- function &getExternalLB( $cluster, $wiki = false ) {
+ public function &getExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
@@ -74,7 +74,7 @@ class LBFactorySingle extends LBFactory {
* @param string|callable $callback
* @param array $params
*/
- function forEachLB( $callback, $params = array() ) {
+ public function forEachLB( $callback, array $params = array() ) {
call_user_func_array( $callback, array_merge( array( $this->lb ), $params ) );
}
}
@@ -84,12 +84,12 @@ class LBFactorySingle extends LBFactory {
*/
class LoadBalancerSingle extends LoadBalancer {
/** @var DatabaseBase */
- protected $db;
+ private $db;
/**
* @param array $params
*/
- function __construct( $params ) {
+ public function __construct( array $params ) {
$this->db = $params['connection'];
parent::__construct( array( 'servers' => array( array(
'type' => $this->db->getType(),
@@ -106,7 +106,7 @@ class LoadBalancerSingle extends LoadBalancer {
*
* @return DatabaseBase
*/
- function reallyOpenConnection( $server, $dbNameOverride = false ) {
+ protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
return $this->db;
}
}
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index e517a025..d9584e14 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -28,13 +28,13 @@
* @ingroup Database
*/
class LoadBalancer {
- /** @var array Map of (server index => server config array) */
+ /** @var array[] Map of (server index => server config array) */
private $mServers;
- /** @var array Map of (local/foreignUsed/foreignFree => server index => DatabaseBase array) */
+ /** @var array[] Map of (local/foreignUsed/foreignFree => server index => DatabaseBase array) */
private $mConns;
/** @var array Map of (server index => weight) */
private $mLoads;
- /** @var array Map of (group => server index => weight) */
+ /** @var array[] Map of (group => server index => weight) */
private $mGroupLoads;
/** @var bool Whether to disregard slave lag as a factor in slave selection */
private $mAllowLagged;
@@ -58,8 +58,13 @@ class LoadBalancer {
private $mLaggedSlaveMode;
/** @var string The last DB selection or connection error */
private $mLastError = 'Unknown error';
- /** @var array Process cache of LoadMonitor::getLagTimes() */
- private $mLagTimes;
+ /** @var integer Total connections opened */
+ private $connsOpened = 0;
+ /** @var ProcessCacheLRU */
+ private $mProcCache;
+
+ /** @var integer Warn when this many connection are held */
+ const CONN_HELD_WARN_THRESHOLD = 10;
/**
* @param array $params Array with keys:
@@ -67,7 +72,7 @@ class LoadBalancer {
* loadMonitor Name of a class used to fetch server lag and load.
* @throws MWException
*/
- function __construct( $params ) {
+ public function __construct( array $params ) {
if ( !isset( $params['servers'] ) ) {
throw new MWException( __CLASS__ . ': missing servers parameter' );
}
@@ -108,6 +113,8 @@ class LoadBalancer {
}
}
}
+
+ $this->mProcCache = new ProcessCacheLRU( 30 );
}
/**
@@ -115,7 +122,7 @@ class LoadBalancer {
*
* @return LoadMonitor
*/
- function getLoadMonitor() {
+ private function getLoadMonitor() {
if ( !isset( $this->mLoadMonitor ) ) {
$class = $this->mLoadMonitorClass;
$this->mLoadMonitor = new $class( $this );
@@ -129,7 +136,7 @@ class LoadBalancer {
* @param mixed $x
* @return mixed
*/
- function parentInfo( $x = null ) {
+ public function parentInfo( $x = null ) {
return wfSetVar( $this->mParentInfo, $x );
}
@@ -142,24 +149,30 @@ class LoadBalancer {
* @param array $weights
* @return bool|int|string
*/
- function pickRandom( $weights ) {
+ public function pickRandom( array $weights ) {
return ArrayUtils::pickRandom( $weights );
}
/**
* @param array $loads
* @param bool|string $wiki Wiki to get non-lagged for
+ * @param float $maxLag Restrict the maximum allowed lag to this many seconds
* @return bool|int|string
*/
- function getRandomNonLagged( $loads, $wiki = false ) {
- # Unset excessively lagged servers
+ private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = INF ) {
$lags = $this->getLagTimes( $wiki );
+
+ # Unset excessively lagged servers
foreach ( $lags as $i => $lag ) {
if ( $i != 0 ) {
+ $maxServerLag = $maxLag;
+ if ( isset( $this->mServers[$i]['max lag'] ) ) {
+ $maxServerLag = min( $maxServerLag, $this->mServers[$i]['max lag'] );
+ }
if ( $lag === false ) {
wfDebugLog( 'replication', "Server #$i is not replicating" );
unset( $loads[$i] );
- } elseif ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) {
+ } elseif ( $lag > $maxServerLag ) {
wfDebugLog( 'replication', "Server #$i is excessively lagged ($lag seconds)" );
unset( $loads[$i] );
}
@@ -195,12 +208,12 @@ class LoadBalancer {
* always return a consistent index during a given invocation
*
* Side effect: opens connections to databases
- * @param bool|string $group
- * @param bool|string $wiki
+ * @param string|bool $group Query group, or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
* @throws MWException
* @return bool|int|string
*/
- function getReaderIndex( $group = false, $wiki = false ) {
+ public function getReaderIndex( $group = false, $wiki = false ) {
global $wgReadOnly, $wgDBtype;
# @todo FIXME: For now, only go through all this for mysql databases
@@ -216,8 +229,6 @@ class LoadBalancer {
return $this->mReadIndex;
}
- $section = new ProfileSection( __METHOD__ );
-
# Find the relevant load array
if ( $group !== false ) {
if ( isset( $this->mGroupLoads[$group] ) ) {
@@ -250,7 +261,19 @@ class LoadBalancer {
if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
$i = ArrayUtils::pickRandom( $currentLoads );
} else {
- $i = $this->getRandomNonLagged( $currentLoads, $wiki );
+ $i = false;
+ if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
+ # ChronologyProtecter causes mWaitForPos to be set via sessions.
+ # This triggers doWait() after connect, so it's especially good to
+ # avoid lagged servers so as to avoid just blocking in that method.
+ $ago = microtime( true ) - $this->mWaitForPos->asOfTime();
+ # Aim for <= 1 second of waiting (being too picky can backfire)
+ $i = $this->getRandomNonLagged( $currentLoads, $wiki, $ago + 1 );
+ }
+ if ( $i === false ) {
+ # Any server with less lag than it's 'max lag' param is preferable
+ $i = $this->getRandomNonLagged( $currentLoads, $wiki );
+ }
if ( $i === false && count( $currentLoads ) != 0 ) {
# All slaves lagged. Switch to read-only mode
wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode" );
@@ -305,36 +328,23 @@ class LoadBalancer {
$this->mServers[$i]['slave pos'] = $conn->getSlavePos();
}
}
- if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group !== false ) {
+ if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
$this->mReadIndex = $i;
}
+ $serverName = $this->getServerName( $i );
+ wfDebug( __METHOD__ . ": using server $serverName for group '$group'\n" );
}
return $i;
}
/**
- * Wait for a specified number of microseconds, and return the period waited
- * @param int $t
- * @return int
- */
- function sleep( $t ) {
- wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__ . ": waiting $t us\n" );
- usleep( $t );
- wfProfileOut( __METHOD__ );
-
- return $t;
- }
-
- /**
* Set the master wait position
* If a DB_SLAVE connection has been opened already, waits
* Otherwise sets a variable telling it to wait if such a connection is opened
* @param DBMasterPos $pos
*/
public function waitFor( $pos ) {
- wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
$i = $this->mReadIndex;
@@ -344,7 +354,6 @@ class LoadBalancer {
$this->mLaggedSlaveMode = true;
}
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -354,7 +363,6 @@ class LoadBalancer {
* @return bool Success (able to connect and no timeouts reached)
*/
public function waitForAll( $pos, $timeout = null ) {
- wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
$serverCount = count( $this->mServers );
@@ -364,7 +372,6 @@ class LoadBalancer {
$ok = $this->doWait( $i, true, $timeout ) && $ok;
}
}
- wfProfileOut( __METHOD__ );
return $ok;
}
@@ -376,7 +383,7 @@ class LoadBalancer {
* @param int $i
* @return DatabaseBase|bool False on failure
*/
- function getAnyOpenConnection( $i ) {
+ public function getAnyOpenConnection( $i ) {
foreach ( $this->mConns as $conns ) {
if ( !empty( $conns[$i] ) ) {
return reset( $conns[$i] );
@@ -394,7 +401,9 @@ class LoadBalancer {
* @return bool
*/
protected function doWait( $index, $open = false, $timeout = null ) {
- # Find a connection to wait on
+ $close = false; // close the connection afterwards
+
+ # Find a connection to wait on, creating one if needed and allowed
$conn = $this->getAnyOpenConnection( $index );
if ( !$conn ) {
if ( !$open ) {
@@ -408,6 +417,9 @@ class LoadBalancer {
return false;
}
+ // Avoid connection spam in waitForAll() when connections
+ // are made just for the sake of doing this lag check.
+ $close = true;
}
}
@@ -417,14 +429,21 @@ class LoadBalancer {
if ( $result == -1 || is_null( $result ) ) {
# Timed out waiting for slave, use master instead
- wfDebug( __METHOD__ . ": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" );
-
- return false;
+ $server = $this->mServers[$index]['host'];
+ $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
+ wfDebug( "$msg\n" );
+ wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
+ $ok = false;
} else {
wfDebug( __METHOD__ . ": Done\n" );
+ $ok = true;
+ }
- return true;
+ if ( $close ) {
+ $this->closeConnection( $conn );
}
+
+ return $ok;
}
/**
@@ -432,17 +451,14 @@ class LoadBalancer {
* This is the main entry point for this class.
*
* @param int $i Server index
- * @param array $groups Query groups
- * @param bool|string $wiki Wiki ID
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
*
* @throws MWException
* @return DatabaseBase
*/
- public function &getConnection( $i, $groups = array(), $wiki = false ) {
- wfProfileIn( __METHOD__ );
-
+ public function getConnection( $i, $groups = array(), $wiki = false ) {
if ( $i === null || $i === false ) {
- wfProfileOut( __METHOD__ );
throw new MWException( 'Attempt to call ' . __METHOD__ .
' with invalid server index' );
}
@@ -451,22 +467,20 @@ class LoadBalancer {
$wiki = false;
}
- # Query groups
+ $groups = ( $groups === false || $groups === array() )
+ ? array( false ) // check one "group": the generic pool
+ : (array)$groups;
+
+ $masterOnly = ( $i == DB_MASTER || $i == $this->getWriterIndex() );
+ $oldConnsOpened = $this->connsOpened; // connections open now
+
if ( $i == DB_MASTER ) {
$i = $this->getWriterIndex();
- } elseif ( !is_array( $groups ) ) {
- $groupIndex = $this->getReaderIndex( $groups, $wiki );
- if ( $groupIndex !== false ) {
- $serverName = $this->getServerName( $groupIndex );
- wfDebug( __METHOD__ . ": using server $serverName for group $groups\n" );
- $i = $groupIndex;
- }
} else {
+ # Try to find an available server in any the query groups (in order)
foreach ( $groups as $group ) {
$groupIndex = $this->getReaderIndex( $group, $wiki );
if ( $groupIndex !== false ) {
- $serverName = $this->getServerName( $groupIndex );
- wfDebug( __METHOD__ . ": using server $serverName for group $group\n" );
$i = $groupIndex;
break;
}
@@ -476,11 +490,13 @@ class LoadBalancer {
# Operation-based index
if ( $i == DB_SLAVE ) {
$this->mLastError = 'Unknown error'; // reset error string
- $i = $this->getReaderIndex( false, $wiki );
+ # Try the general server pool if $groups are unavailable.
+ $i = in_array( false, $groups, true )
+ ? false // don't bother with this if that is what was tried above
+ : $this->getReaderIndex( false, $wiki );
# Couldn't find a working server in getReaderIndex()?
if ( $i === false ) {
$this->mLastError = 'No working slave server: ' . $this->mLastError;
- wfProfileOut( __METHOD__ );
return $this->reportConnectionError();
}
@@ -489,12 +505,17 @@ class LoadBalancer {
# Now we have an explicit index into the servers array
$conn = $this->openConnection( $i, $wiki );
if ( !$conn ) {
- wfProfileOut( __METHOD__ );
return $this->reportConnectionError();
}
- wfProfileOut( __METHOD__ );
+ # Profile any new connections that happen
+ if ( $this->connsOpened > $oldConnsOpened ) {
+ $host = $conn->getServer();
+ $dbname = $conn->getDBname();
+ $trxProf = Profiler::instance()->getTransactionProfiler();
+ $trxProf->recordConnection( $host, $dbname, $masterOnly );
+ }
return $conn;
}
@@ -556,8 +577,8 @@ class LoadBalancer {
* @see LoadBalancer::getConnection() for parameter information
*
* @param int $db
- * @param mixed $groups
- * @param bool|string $wiki
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
* @return DBConnRef
*/
public function getConnectionRef( $db, $groups = array(), $wiki = false ) {
@@ -572,8 +593,8 @@ class LoadBalancer {
* @see LoadBalancer::getConnection() for parameter information
*
* @param int $db
- * @param mixed $groups
- * @param bool|string $wiki
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
* @return DBConnRef
*/
public function getLazyConnectionRef( $db, $groups = array(), $wiki = false ) {
@@ -589,16 +610,14 @@ class LoadBalancer {
* error will be available via $this->mErrorConnection.
*
* @param int $i Server index
- * @param bool|string $wiki Wiki ID to open
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
* @return DatabaseBase
*
* @access private
*/
- function openConnection( $i, $wiki = false ) {
- wfProfileIn( __METHOD__ );
+ public function openConnection( $i, $wiki = false ) {
if ( $wiki !== false ) {
$conn = $this->openForeignConnection( $i, $wiki );
- wfProfileOut( __METHOD__ );
return $conn;
}
@@ -617,7 +636,6 @@ class LoadBalancer {
$conn = false;
}
}
- wfProfileOut( __METHOD__ );
return $conn;
}
@@ -640,8 +658,7 @@ class LoadBalancer {
* @param string $wiki Wiki ID to open
* @return DatabaseBase
*/
- function openForeignConnection( $i, $wiki ) {
- wfProfileIn( __METHOD__ );
+ private function openForeignConnection( $i, $wiki ) {
list( $dbName, $prefix ) = wfSplitWikiID( $wiki );
if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) {
// Reuse an already-used connection
@@ -658,7 +675,9 @@ class LoadBalancer {
$conn = reset( $this->mConns['foreignFree'][$i] );
$oldWiki = key( $this->mConns['foreignFree'][$i] );
- if ( !$conn->selectDB( $dbName ) ) {
+ // The empty string as a DB name means "don't care".
+ // DatabaseMysqlBase::open() already handle this on connection.
+ if ( $dbName !== '' && !$conn->selectDB( $dbName ) ) {
$this->mLastError = "Error selecting database $dbName on server " .
$conn->getServer() . " from client host " . wfHostname() . "\n";
$this->mErrorConnection = $conn;
@@ -692,7 +711,6 @@ class LoadBalancer {
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
}
- wfProfileOut( __METHOD__ );
return $conn;
}
@@ -704,7 +722,7 @@ class LoadBalancer {
* @access private
* @return bool
*/
- function isOpen( $index ) {
+ private function isOpen( $index ) {
if ( !is_integer( $index ) ) {
return false;
}
@@ -722,7 +740,7 @@ class LoadBalancer {
* @throws MWException
* @return DatabaseBase
*/
- function reallyOpenConnection( $server, $dbNameOverride = false ) {
+ protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
if ( !is_array( $server ) ) {
throw new MWException( 'You must update your load-balancing configuration. ' .
'See DefaultSettings.php entry for $wgDBservers.' );
@@ -732,6 +750,14 @@ class LoadBalancer {
$server['dbname'] = $dbNameOverride;
}
+ // Log when many connection are made on requests
+ if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
+ $masterAddr = $this->getServerName( 0 );
+ wfDebugLog( 'DBPerformance', __METHOD__ . ": " .
+ "{$this->connsOpened}+ connections made (master=$masterAddr)\n" .
+ wfBacktrace( true ) );
+ }
+
# Create object
try {
$db = DatabaseBase::factory( $server['type'], $server );
@@ -758,17 +784,27 @@ class LoadBalancer {
*/
private function reportConnectionError() {
$conn = $this->mErrorConnection; // The connection which caused the error
+ $context = array(
+ 'method' => __METHOD__,
+ 'last_error' => $this->mLastError,
+ );
if ( !is_object( $conn ) ) {
// No last connection, probably due to all servers being too busy
- wfLogDBError( "LB failure with no last connection. Connection error: {$this->mLastError}" );
+ wfLogDBError(
+ "LB failure with no last connection. Connection error: {last_error}",
+ $context
+ );
// If all servers were busy, mLastError will contain something sensible
throw new DBConnectionError( null, $this->mLastError );
} else {
- $server = $conn->getProperty( 'mServer' );
- wfLogDBError( "Connection error: {$this->mLastError} ({$server})" );
- $conn->reportConnectionError( "{$this->mLastError} ({$server})" ); // throws DBConnectionError
+ $context['db_server'] = $conn->getProperty( 'mServer' );
+ wfLogDBError(
+ "Connection error: {last_error} ({db_server})",
+ $context
+ );
+ $conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" ); // throws DBConnectionError
}
return false; /* not reached */
@@ -777,7 +813,7 @@ class LoadBalancer {
/**
* @return int
*/
- function getWriterIndex() {
+ private function getWriterIndex() {
return 0;
}
@@ -787,7 +823,7 @@ class LoadBalancer {
* @param string $i
* @return bool
*/
- function haveIndex( $i ) {
+ public function haveIndex( $i ) {
return array_key_exists( $i, $this->mServers );
}
@@ -797,7 +833,7 @@ class LoadBalancer {
* @param string $i
* @return bool
*/
- function isNonZeroLoad( $i ) {
+ public function isNonZeroLoad( $i ) {
return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
}
@@ -806,7 +842,7 @@ class LoadBalancer {
*
* @return int
*/
- function getServerCount() {
+ public function getServerCount() {
return count( $this->mServers );
}
@@ -816,7 +852,7 @@ class LoadBalancer {
* @param string $i
* @return string
*/
- function getServerName( $i ) {
+ public function getServerName( $i ) {
if ( isset( $this->mServers[$i]['hostName'] ) ) {
return $this->mServers[$i]['hostName'];
} elseif ( isset( $this->mServers[$i]['host'] ) ) {
@@ -831,7 +867,7 @@ class LoadBalancer {
* @param int $i
* @return array|bool
*/
- function getServerInfo( $i ) {
+ public function getServerInfo( $i ) {
if ( isset( $this->mServers[$i] ) ) {
return $this->mServers[$i];
} else {
@@ -845,7 +881,7 @@ class LoadBalancer {
* @param int $i
* @param array $serverInfo
*/
- function setServerInfo( $i, $serverInfo ) {
+ public function setServerInfo( $i, array $serverInfo ) {
$this->mServers[$i] = $serverInfo;
}
@@ -853,7 +889,7 @@ class LoadBalancer {
* Get the current master position for chronology control purposes
* @return mixed
*/
- function getMasterPos() {
+ public function getMasterPos() {
# If this entire request was served from a slave without opening a connection to the
# master (however unlikely that may be), then we can fetch the position from the slave.
$masterConn = $this->getAnyOpenConnection( 0 );
@@ -879,7 +915,7 @@ class LoadBalancer {
/**
* Close all open connections
*/
- function closeAll() {
+ public function closeAll() {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
/** @var DatabaseBase $conn */
@@ -893,6 +929,7 @@ class LoadBalancer {
'foreignFree' => array(),
'foreignUsed' => array(),
);
+ $this->connsOpened = 0;
}
/**
@@ -901,7 +938,7 @@ class LoadBalancer {
* If you use $conn->close() directly, the load balancer won't update its state.
* @param DatabaseBase $conn
*/
- function closeConnection( $conn ) {
+ public function closeConnection( $conn ) {
$done = false;
foreach ( $this->mConns as $i1 => $conns2 ) {
foreach ( $conns2 as $i2 => $conns3 ) {
@@ -909,6 +946,7 @@ class LoadBalancer {
if ( $conn === $candidateConn ) {
$conn->close();
unset( $this->mConns[$i1][$i2][$i3] );
+ --$this->connsOpened;
$done = true;
break;
}
@@ -923,7 +961,7 @@ class LoadBalancer {
/**
* Commit transactions on all open connections
*/
- function commitAll() {
+ public function commitAll() {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
/** @var DatabaseBase[] $conns3 */
@@ -939,8 +977,7 @@ class LoadBalancer {
/**
* Issue COMMIT only on master, only if queries were done on connection
*/
- function commitMasterChanges() {
- // Always 0, but who knows.. :)
+ public function commitMasterChanges() {
$masterIndex = $this->getWriterIndex();
foreach ( $this->mConns as $conns2 ) {
if ( empty( $conns2[$masterIndex] ) ) {
@@ -959,8 +996,9 @@ class LoadBalancer {
* Issue ROLLBACK only on master, only if queries were done on connection
* @since 1.23
*/
- function rollbackMasterChanges() {
- // Always 0, but who knows.. :)
+ public function rollbackMasterChanges() {
+ $failedServers = array();
+
$masterIndex = $this->getWriterIndex();
foreach ( $this->mConns as $conns2 ) {
if ( empty( $conns2[$masterIndex] ) ) {
@@ -969,28 +1007,36 @@ class LoadBalancer {
/** @var DatabaseBase $conn */
foreach ( $conns2[$masterIndex] as $conn ) {
if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
- $conn->rollback( __METHOD__, 'flush' );
+ try {
+ $conn->rollback( __METHOD__, 'flush' );
+ } catch ( DBError $e ) {
+ MWExceptionHandler::logException( $e );
+ $failedServers[] = $conn->getServer();
+ }
}
}
}
+
+ if ( $failedServers ) {
+ throw new DBExpectedError( null, "Rollback failed on server(s) " .
+ implode( ', ', array_unique( $failedServers ) ) );
+ }
}
/**
* @return bool Whether a master connection is already open
* @since 1.24
*/
- function hasMasterConnection() {
+ public function hasMasterConnection() {
return $this->isOpen( $this->getWriterIndex() );
}
/**
- * Determine if there are any pending changes that need to be rolled back
- * or committed.
+ * Determine if there are pending changes in a transaction by this thread
* @since 1.23
* @return bool
*/
- function hasMasterChanges() {
- // Always 0, but who knows.. :)
+ public function hasMasterChanges() {
$masterIndex = $this->getWriterIndex();
foreach ( $this->mConns as $conns2 ) {
if ( empty( $conns2[$masterIndex] ) ) {
@@ -1007,17 +1053,52 @@ class LoadBalancer {
}
/**
+ * Get the timestamp of the latest write query done by this thread
+ * @since 1.25
+ * @return float|bool UNIX timestamp or false
+ */
+ public function lastMasterChangeTimestamp() {
+ $lastTime = false;
+ $masterIndex = $this->getWriterIndex();
+ foreach ( $this->mConns as $conns2 ) {
+ if ( empty( $conns2[$masterIndex] ) ) {
+ continue;
+ }
+ /** @var DatabaseBase $conn */
+ foreach ( $conns2[$masterIndex] as $conn ) {
+ $lastTime = max( $lastTime, $conn->lastDoneWrites() );
+ }
+ }
+ return $lastTime;
+ }
+
+ /**
+ * Check if this load balancer object had any recent or still
+ * pending writes issued against it by this PHP thread
+ *
+ * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
+ * @return bool
+ * @since 1.25
+ */
+ public function hasOrMadeRecentMasterChanges( $age = null ) {
+ $age = ( $age === null ) ? $this->mWaitTimeout : $age;
+
+ return ( $this->hasMasterChanges()
+ || $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
+ }
+
+ /**
* @param mixed $value
* @return mixed
*/
- function waitTimeout( $value = null ) {
+ public function waitTimeout( $value = null ) {
return wfSetVar( $this->mWaitTimeout, $value );
}
/**
* @return bool
*/
- function getLaggedSlaveMode() {
+ public function getLaggedSlaveMode() {
return $this->mLaggedSlaveMode;
}
@@ -1026,7 +1107,7 @@ class LoadBalancer {
* @param null|bool $mode
* @return bool
*/
- function allowLagged( $mode = null ) {
+ public function allowLagged( $mode = null ) {
if ( $mode === null ) {
return $this->mAllowLagged;
}
@@ -1038,7 +1119,7 @@ class LoadBalancer {
/**
* @return bool
*/
- function pingAll() {
+ public function pingAll() {
$success = true;
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
@@ -1059,7 +1140,7 @@ class LoadBalancer {
* @param callable $callback
* @param array $params
*/
- function forEachOpenConnection( $callback, $params = array() ) {
+ public function forEachOpenConnection( $callback, array $params = array() ) {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
foreach ( $conns3 as $conn ) {
@@ -1071,7 +1152,8 @@ class LoadBalancer {
}
/**
- * Get the hostname and lag time of the most-lagged slave.
+ * Get the hostname and lag time of the most-lagged slave
+ *
* This is useful for maintenance scripts that need to throttle their updates.
* May attempt to open connections to slaves on the default DB. If there is
* no lag, the maximum lag will be reported as -1.
@@ -1079,72 +1161,50 @@ class LoadBalancer {
* @param bool|string $wiki Wiki ID, or false for the default database
* @return array ( host, max lag, index of max lagged host )
*/
- function getMaxLag( $wiki = false ) {
+ public function getMaxLag( $wiki = false ) {
$maxLag = -1;
$host = '';
$maxIndex = 0;
- if ( $this->getServerCount() <= 1 ) { // no replication = no lag
- return array( $host, $maxLag, $maxIndex );
+ if ( $this->getServerCount() <= 1 ) {
+ return array( $host, $maxLag, $maxIndex ); // no replication = no lag
}
- // Try to get the max lag info from the server cache
- $key = 'loadbalancer:maxlag:cluster:' . $this->mServers[0]['host'];
- $cache = ObjectCache::newAccelerator( array(), 'hash' );
- $maxLagInfo = $cache->get( $key ); // (host, lag, index)
-
- // Fallback to connecting to each slave and getting the lag
- if ( !$maxLagInfo ) {
- foreach ( $this->mServers as $i => $conn ) {
- if ( $i == $this->getWriterIndex() ) {
- continue; // nothing to check
- }
- $conn = false;
- if ( $wiki === false ) {
- $conn = $this->getAnyOpenConnection( $i );
- }
- if ( !$conn ) {
- $conn = $this->openConnection( $i, $wiki );
- }
- if ( !$conn ) {
- continue;
- }
- $lag = $conn->getLag();
- if ( $lag > $maxLag ) {
- $maxLag = $lag;
- $host = $this->mServers[$i]['host'];
- $maxIndex = $i;
- }
+ $lagTimes = $this->getLagTimes( $wiki );
+ foreach ( $lagTimes as $i => $lag ) {
+ if ( $lag > $maxLag ) {
+ $maxLag = $lag;
+ $host = $this->mServers[$i]['host'];
+ $maxIndex = $i;
}
- $maxLagInfo = array( $host, $maxLag, $maxIndex );
- $cache->set( $key, $maxLagInfo, 5 );
}
- return $maxLagInfo;
+ return array( $host, $maxLag, $maxIndex );
}
/**
* Get lag time for each server
- * Results are cached for a short time in memcached, and indefinitely in the process cache
+ *
+ * Results are cached for a short time in memcached/process cache
*
* @param string|bool $wiki
- * @return array
+ * @return int[] Map of (server index => seconds)
*/
- function getLagTimes( $wiki = false ) {
- # Try process cache
- if ( isset( $this->mLagTimes ) ) {
- return $this->mLagTimes;
+ public function getLagTimes( $wiki = false ) {
+ if ( $this->getServerCount() <= 1 ) {
+ return array( 0 => 0 ); // no replication = no lag
}
- if ( $this->getServerCount() == 1 ) {
- # No replication
- $this->mLagTimes = array( 0 => 0 );
- } else {
- # Send the request to the load monitor
- $this->mLagTimes = $this->getLoadMonitor()->getLagTimes(
- array_keys( $this->mServers ), $wiki );
+
+ if ( $this->mProcCache->has( 'slave_lag', 'times', 1 ) ) {
+ return $this->mProcCache->get( 'slave_lag', 'times' );
}
- return $this->mLagTimes;
+ # Send the request to the load monitor
+ $times = $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
+
+ $this->mProcCache->set( 'slave_lag', 'times', $times );
+
+ return $times;
}
/**
@@ -1161,7 +1221,7 @@ class LoadBalancer {
* @param DatabaseBase $conn
* @return int
*/
- function safeGetLag( $conn ) {
+ public function safeGetLag( $conn ) {
if ( $this->getServerCount() == 1 ) {
return 0;
} else {
@@ -1170,15 +1230,15 @@ class LoadBalancer {
}
/**
- * Clear the cache for getLagTimes
+ * Clear the cache for slag lag delay times
*/
- function clearLagTimeCache() {
- $this->mLagTimes = null;
+ public function clearLagTimeCache() {
+ $this->mProcCache->clear( 'slave_lag' );
}
}
/**
- * Helper class to handle automatically marking connectons as reusable (via RAII pattern)
+ * Helper class to handle automatically marking connections as reusable (via RAII pattern)
* as well handling deferring the actual network connection until the handle is used
*
* @ingroup Database
@@ -1186,13 +1246,13 @@ class LoadBalancer {
*/
class DBConnRef implements IDatabase {
/** @var LoadBalancer */
- protected $lb;
+ private $lb;
/** @var DatabaseBase|null */
- protected $conn;
+ private $conn;
/** @var array|null */
- protected $params;
+ private $params;
/**
* @param LoadBalancer $lb
@@ -1216,7 +1276,7 @@ class DBConnRef implements IDatabase {
return call_user_func_array( array( $this->conn, $name ), $arguments );
}
- function __destruct() {
+ public function __destruct() {
if ( $this->conn !== null ) {
$this->lb->reuseConnection( $this->conn );
}
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 7281485b..91840dd9 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -48,7 +48,7 @@ interface LoadMonitor {
* @param array $serverIndexes
* @param string $wiki
*
- * @return array
+ * @return array Map of (server index => seconds)
*/
public function getLagTimes( $serverIndexes, $wiki );
}
@@ -93,8 +93,6 @@ class LoadMonitorMySQL implements LoadMonitor {
return array( 0 => 0 );
}
- $section = new ProfileSection( __METHOD__ );
-
$expiry = 5;
$requestRate = 10;
diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php
index 2f898b75..562a8106 100644
--- a/includes/db/ORMTable.php
+++ b/includes/db/ORMTable.php
@@ -129,6 +129,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* Gets the db field prefix.
*
* @since 1.20
+ * @deprecated since 1.25, use the $this->fieldPrefix property instead
*
* @return string
*/
@@ -189,7 +190,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
* @since 1.20
@@ -210,7 +211,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
* @since 1.20
@@ -247,8 +248,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $options
* @param null|string $functionName
* @return ResultWrapper
- * @throws DBQueryError If the query failed (even if the database was in
- * ignoreErrors mode).
+ * @throws Exception
+ * @throws MWException
*/
public function rawSelect( $fields = null, array $conditions = array(),
array $options = array(), $functionName = null
@@ -295,7 +296,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as associative arrays.
* Provided field names get prefixed.
* Returned field names will not have a prefix.
@@ -345,7 +346,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the first matching record.
+ * Selects the specified fields of the first matching record.
* Field names get prefixed.
*
* @since 1.20
@@ -368,7 +369,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions. Field names do NOT get prefixed.
*
* @since 1.20
@@ -399,7 +400,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the first record matching the provided
+ * Selects the specified fields of the first record matching the provided
* conditions and returns it as an associative array, or false when nothing matches.
* This method makes use of selectFields and expects the same parameters and
* returns the same results (if there are any, if there are none, this method returns false).
@@ -770,33 +771,54 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @return string
*/
public function getPrefixedField( $field ) {
- return $this->getFieldPrefix() . $field;
+ return $this->fieldPrefix . $field;
}
/**
* Takes an array of field names with prefix and returns the unprefixed equivalent.
*
* @since 1.20
+ * @deprecated since 1.25, will be removed
*
- * @param array $fieldNames
+ * @param string[] $fieldNames
*
- * @return array
+ * @return string[]
*/
public function unprefixFieldNames( array $fieldNames ) {
- return array_map( array( $this, 'unprefixFieldName' ), $fieldNames );
+ wfDeprecated( __METHOD__, '1.25' );
+
+ return $this->stripFieldPrefix( $fieldNames );
+ }
+
+ /**
+ * Takes an array of field names with prefix and returns the unprefixed equivalent.
+ *
+ * @param string[] $fieldNames
+ *
+ * @return string[]
+ */
+ private function stripFieldPrefix( array $fieldNames ) {
+ $start = strlen( $this->fieldPrefix );
+
+ return array_map( function ( $fieldName ) use ( $start ) {
+ return substr( $fieldName, $start );
+ }, $fieldNames );
}
/**
* Takes a field name with prefix and returns the unprefixed equivalent.
*
* @since 1.20
+ * @deprecated since 1.25, will be removed
*
* @param string $fieldName
*
* @return string
*/
public function unprefixFieldName( $fieldName ) {
- return substr( $fieldName, strlen( $this->getFieldPrefix() ) );
+ wfDeprecated( __METHOD__, '1.25' );
+
+ return substr( $fieldName, strlen( $this->fieldPrefix ) );
}
/**
@@ -832,7 +854,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
$result = (array)$result;
$rawFields = array_combine(
- $this->unprefixFieldNames( array_keys( $result ) ),
+ $this->stripFieldPrefix( array_keys( $result ) ),
array_values( $result )
);