'IEUrlExtension' => __DIR__ . '/includes/libs/IEUrlExtension.php',
'IExpiringStore' => __DIR__ . '/includes/libs/objectcache/IExpiringStore.php',
'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
+ 'ILBFactory' => __DIR__ . '/includes/libs/rdbms/lbfactory/ILBFactory.php',
'ILoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/ILoadBalancer.php',
'ILoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/ILoadMonitor.php',
+ 'IMaintainableDatabase' => __DIR__ . '/includes/libs/rdbms/database/IMaintainableDatabase.php',
'IP' => __DIR__ . '/includes/libs/IP.php',
'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php',
'IPTC' => __DIR__ . '/includes/media/IPTC.php',
use Psr\Log\LoggerInterface;
/**
- * Database abstraction object
+ * Relational database abstraction object
+ *
* @ingroup Database
+ * @since 1.28
*/
-abstract class Database implements IDatabase, LoggerAwareInterface {
+abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAwareInterface {
/** Number of times to re-try an operation in case of deadlock */
const DEADLOCK_TRIES = 4;
/** Minimum time to wait before retry, in microseconds */
$this->agent = str_replace( '/', '-', $params['agent'] );
$this->mFlags = $params['flags'];
- if ( $this->mFlags & DBO_DEFAULT ) {
+ if ( $this->mFlags & self::DBO_DEFAULT ) {
if ( $this->cliMode ) {
- $this->mFlags &= ~DBO_TRX;
+ $this->mFlags &= ~self::DBO_TRX;
} else {
- $this->mFlags |= DBO_TRX;
+ $this->mFlags |= self::DBO_TRX;
}
}
}
public function bufferResults( $buffer = null ) {
- $res = !$this->getFlag( DBO_NOBUFFER );
+ $res = !$this->getFlag( self::DBO_NOBUFFER );
if ( $buffer !== null ) {
- $buffer ? $this->clearFlag( DBO_NOBUFFER ) : $this->setFlag( DBO_NOBUFFER );
+ $buffer
+ ? $this->clearFlag( self::DBO_NOBUFFER )
+ : $this->setFlag( self::DBO_NOBUFFER );
}
return $res;
* @return bool The previous value of the flag.
*/
protected function ignoreErrors( $ignoreErrors = null ) {
- $res = $this->getFlag( DBO_IGNORE );
+ $res = $this->getFlag( self::DBO_IGNORE );
if ( $ignoreErrors !== null ) {
- $ignoreErrors ? $this->setFlag( DBO_IGNORE ) : $this->clearFlag( DBO_IGNORE );
+ $ignoreErrors
+ ? $this->setFlag( self::DBO_IGNORE )
+ : $this->clearFlag( self::DBO_IGNORE );
}
return $res;
$commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
# Start implicit transactions that wrap the request if DBO_TRX is enabled
- if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX )
+ if ( !$this->mTrxLevel && $this->getFlag( self::DBO_TRX )
&& $this->isTransactableQuery( $sql )
) {
$this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
- if ( $this->getFlag( DBO_DEBUG ) ) {
+ if ( $this->getFlag( self::DBO_DEBUG ) ) {
$this->queryLogger->debug( "{$this->mDBname} {$commentedSql}" );
}
return $this->mServer;
}
- /**
- * Format a table name ready for use in constructing an SQL query
- *
- * This does two important things: it quotes the table names to clean them up,
- * and it adds a table prefix if only given a table name with no quotes.
- *
- * All functions of this object which require a table name call this function
- * themselves. Pass the canonical name to such functions. This is only needed
- * when calling query() directly.
- *
- * @note This function does not sanitize user input. It is not safe to use
- * this function to escape user input.
- * @param string $name Database table name
- * @param string $format One of:
- * quoted - Automatically pass the table name through addIdentifierQuotes()
- * so that it can be used in a query.
- * raw - Do not add identifier quotes to the table name
- * @return string Full database name
- */
public function tableName( $name, $format = 'quoted' ) {
# Skip the entire process when we have a string quoted on both ends.
# Note that we check the end so that we will still quote any use of
return $tableName;
}
- /**
- * Fetch a number of table names into an array
- * This is handy when you need to construct SQL for joins
- *
- * Example:
- * extract( $dbr->tableNames( 'user', 'watchlist' ) );
- * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
- * WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
- *
- * @return array
- */
public function tableNames() {
$inArray = func_get_args();
$retVal = [];
return $retVal;
}
- /**
- * Fetch a number of table names into an zero-indexed numerical array
- * This is handy when you need to construct SQL for joins
- *
- * Example:
- * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' );
- * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
- * WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
- *
- * @return array
- */
public function tableNamesN() {
$inArray = func_get_args();
$retVal = [];
$this->query( $sql, $fname );
}
- /**
- * Returns the size of a text field, or -1 for "unlimited"
- *
- * @param string $table
- * @param string $field
- * @return int
- */
public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
return false;
}
- /**
- * Perform a deadlock-prone transaction.
- *
- * This function invokes a callback function to perform a set of write
- * queries. If a deadlock occurs during the processing, the transaction
- * will be rolled back and the callback function will be called again.
- *
- * Avoid using this method outside of Job or Maintenance classes.
- *
- * Usage:
- * $dbw->deadlockLoop( callback, ... );
- *
- * Extra arguments are passed through to the specified callback function.
- * This method requires that no transactions are already active to avoid
- * causing premature commits or exceptions.
- *
- * Returns whatever the callback function returned on its successful,
- * iteration, or false on error, for example if the retry limit was
- * reached.
- *
- * @return mixed
- * @throws DBUnexpectedError
- * @throws Exception
- */
public function deadlockLoop() {
$args = func_get_args();
$function = array_shift( $args );
return;
}
- $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
+ $autoTrx = $this->getFlag( self::DBO_TRX ); // automatic begin() enabled?
/** @var Exception $e */
$e = null; // first exception
do { // callbacks may add callbacks :)
foreach ( $callbacks as $callback ) {
try {
list( $phpCallback ) = $callback;
- $this->clearFlag( DBO_TRX ); // make each query its own transaction
+ $this->clearFlag( self::DBO_TRX ); // make each query its own transaction
call_user_func_array( $phpCallback, [ $trigger ] );
if ( $autoTrx ) {
- $this->setFlag( DBO_TRX ); // restore automatic begin()
+ $this->setFlag( self::DBO_TRX ); // restore automatic begin()
} else {
- $this->clearFlag( DBO_TRX ); // restore auto-commit
+ $this->clearFlag( self::DBO_TRX ); // restore auto-commit
}
} catch ( Exception $ex ) {
call_user_func( $this->errorLogger, $ex );
$this->begin( $fname, self::TRANSACTION_INTERNAL );
// If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
// in all changes being in one transaction to keep requests transactional.
- if ( !$this->getFlag( DBO_TRX ) ) {
+ if ( !$this->getFlag( self::DBO_TRX ) ) {
$this->mTrxAutomaticAtomic = true;
}
}
$this->queryLogger->error( $msg );
return; // join the main transaction set
}
- } elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
+ } elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
// @TODO: make this an exception at some point
$msg = "$fname: Implicit transaction expected (DBO_TRX set).";
$this->queryLogger->error( $msg );
$this->queryLogger->error(
"$fname: No transaction to rollback, something got out of sync." );
return; // nothing to do
- } elseif ( $this->getFlag( DBO_TRX ) ) {
+ } elseif ( $this->getFlag( self::DBO_TRX ) ) {
throw new DBUnexpectedError(
$this,
"$fname: Expected mass rollback of all peer databases (DBO_TRX set)."
$this->allViews = null;
}
- /**
- * Lists all the VIEWs in the database
- *
- * For caching purposes the list of all views should be stored in
- * $this->allViews. The process cache can be cleared with clearViewsCache()
- *
- * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
- * @param string $fname Name of calling function
- * @throws RuntimeException
- * @return array
- * @since 1.22
- */
public function listViews( $prefix = null, $fname = __METHOD__ ) {
throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
}
// This will reconnect if possible or return false if not
- $this->clearFlag( DBO_TRX, self::REMEMBER_PRIOR );
+ $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
$ok = ( $this->query( self::PING_QUERY, __METHOD__, true ) !== false );
$this->restoreFlags( self::RESTORE_PRIOR );
public function setSessionOptions( array $options ) {
}
- /**
- * Read and execute SQL commands from a file.
- *
- * Returns true on success, error string or exception on failure (depending
- * on object's error ignore settings).
- *
- * @param string $filename File name to open
- * @param bool|callable $lineCallback Optional function called before reading each line
- * @param bool|callable $resultCallback Optional function called for each MySQL result
- * @param bool|string $fname Calling function name or false if name should be
- * generated dynamically using $filename
- * @param bool|callable $inputCallback Optional function called for each
- * complete line sent
- * @return bool|string
- * @throws Exception
- */
public function sourceFile(
$filename,
$lineCallback = false,
$this->mSchemaVars = $vars;
}
- /**
- * Read and execute commands from an open file handle.
- *
- * Returns true on success, error string or exception on failure (depending
- * on object's error ignore settings).
- *
- * @param resource $fp File handle
- * @param bool|callable $lineCallback Optional function called before reading each query
- * @param bool|callable $resultCallback Optional function called for each MySQL result
- * @param string $fname Calling function name
- * @param bool|callable $inputCallback Optional function called for each complete query sent
- * @return bool|string
- */
public function sourceStream(
$fp,
$lineCallback = false,
}
$connFlags = 0;
- if ( $this->mFlags & DBO_SSL ) {
+ if ( $this->mFlags & self::DBO_SSL ) {
$connFlags |= MYSQL_CLIENT_SSL;
}
- if ( $this->mFlags & DBO_COMPRESS ) {
+ if ( $this->mFlags & self::DBO_COMPRESS ) {
$connFlags |= MYSQL_CLIENT_COMPRESS;
}
if ( $i > 1 ) {
usleep( 1000 );
}
- if ( $this->mFlags & DBO_PERSISTENT ) {
+ if ( $this->mFlags & self::DBO_PERSISTENT ) {
$conn = mysql_pconnect( $realServer, $this->mUser, $this->mPassword, $connFlags );
} else {
# Create a new connection...
$mysqli = mysqli_init();
$connFlags = 0;
- if ( $this->mFlags & DBO_SSL ) {
+ if ( $this->mFlags & self::DBO_SSL ) {
$connFlags |= MYSQLI_CLIENT_SSL;
$mysqli->ssl_set(
$this->sslKeyPath,
$this->sslCiphers
);
}
- if ( $this->mFlags & DBO_COMPRESS ) {
+ if ( $this->mFlags & self::DBO_COMPRESS ) {
$connFlags |= MYSQLI_CLIENT_COMPRESS;
}
- if ( $this->mFlags & DBO_PERSISTENT ) {
+ if ( $this->mFlags & self::DBO_PERSISTENT ) {
$realServer = 'p:' . $realServer;
}
if ( (int)$this->port > 0 ) {
$connectVars['port'] = (int)$this->port;
}
- if ( $this->mFlags & DBO_SSL ) {
+ if ( $this->mFlags & self::DBO_SSL ) {
$connectVars['sslmode'] = 1;
}
$this->dbPath = $fileName;
try {
- if ( $this->mFlags & DBO_PERSISTENT ) {
+ if ( $this->mFlags & self::DBO_PERSISTENT ) {
$this->mConn = new PDO( "sqlite:$fileName", '', '',
[ PDO::ATTR_PERSISTENT => true ] );
} else {
<?php
-
/**
* @defgroup Database Database
*
*/
/**
- * Basic database interface for live and lazy-loaded DB handles
+ * Basic database interface for live and lazy-loaded relation database handles
*
* @note: IDatabase and DBConnRef should be updated to reflect any changes
* @ingroup Database
/** @var int Combine list with OR clauses */
const LIST_OR = 4;
+ /** @var int Enable debug logging */
+ const DBO_DEBUG = 1;
+ /** @var int Disable query buffering (only one result set can be iterated at a time) */
+ const DBO_NOBUFFER = 2;
+ /** @var int Ignore query errors (internal use only!) */
+ const DBO_IGNORE = 4;
+ /** @var int Autoatically start transaction on first query (work with ILoadBalancer rounds) */
+ const DBO_TRX = 8;
+ /** @var int Use DBO_TRX in non-CLI mode */
+ const DBO_DEFAULT = 16;
+ /** @var int Use DB persistent connections if possible */
+ const DBO_PERSISTENT = 32;
+ /** @var int DBA session mode; mostly for Oracle */
+ const DBO_SYSDBA = 64;
+ /** @var int Schema file mode; mostly for Oracle */
+ const DBO_DDLMODE = 128;
+ /** @var int Enable SSL/TLS in connection protocol */
+ const DBO_SSL = 256;
+ /** @var int Enable compression in connection protocol */
+ const DBO_COMPRESS = 512;
+
/**
* A string describing the current software version, and possibly
* other details in a user-friendly way. Will be listed on Special:Version, etc.
--- /dev/null
+<?php
+
+/**
+ * This file deals with database interface functions
+ * and query specifics/optimisations.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+
+/**
+ * Advanced database interface for IDatabase handles that include maintenance methods
+ *
+ * This is useful for type-hints used by installer, upgrader, and background scripts
+ * that will make use of lower-level and longer-running queries, including schema changes.
+ *
+ * @ingroup Database
+ * @since 1.28
+ */
+interface IMaintainableDatabase extends IDatabase {
+ /**
+ * Format a table name ready for use in constructing an SQL query
+ *
+ * This does two important things: it quotes the table names to clean them up,
+ * and it adds a table prefix if only given a table name with no quotes.
+ *
+ * All functions of this object which require a table name call this function
+ * themselves. Pass the canonical name to such functions. This is only needed
+ * when calling query() directly.
+ *
+ * @note This function does not sanitize user input. It is not safe to use
+ * this function to escape user input.
+ * @param string $name Database table name
+ * @param string $format One of:
+ * quoted - Automatically pass the table name through addIdentifierQuotes()
+ * so that it can be used in a query.
+ * raw - Do not add identifier quotes to the table name
+ * @return string Full database name
+ */
+ public function tableName( $name, $format = 'quoted' );
+
+ /**
+ * Fetch a number of table names into an array
+ * This is handy when you need to construct SQL for joins
+ *
+ * Example:
+ * extract( $dbr->tableNames( 'user', 'watchlist' ) );
+ * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
+ * WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
+ *
+ * @return array
+ */
+ public function tableNames();
+
+ /**
+ * Fetch a number of table names into an zero-indexed numerical array
+ * This is handy when you need to construct SQL for joins
+ *
+ * Example:
+ * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' );
+ * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
+ * WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
+ *
+ * @return array
+ */
+ public function tableNamesN();
+
+ /**
+ * Returns the size of a text field, or -1 for "unlimited"
+ *
+ * @param string $table
+ * @param string $field
+ * @return int
+ */
+ public function textFieldSize( $table, $field );
+
+ /**
+ * Read and execute SQL commands from a file.
+ *
+ * Returns true on success, error string or exception on failure (depending
+ * on object's error ignore settings).
+ *
+ * @param string $filename File name to open
+ * @param bool|callable $lineCallback Optional function called before reading each line
+ * @param bool|callable $resultCallback Optional function called for each MySQL result
+ * @param bool|string $fname Calling function name or false if name should be
+ * generated dynamically using $filename
+ * @param bool|callable $inputCallback Optional function called for each
+ * complete line sent
+ * @return bool|string
+ * @throws Exception
+ */
+ public function sourceFile(
+ $filename,
+ $lineCallback = false,
+ $resultCallback = false,
+ $fname = false,
+ $inputCallback = false
+ );
+
+ /**
+ * Read and execute commands from an open file handle.
+ *
+ * Returns true on success, error string or exception on failure (depending
+ * on object's error ignore settings).
+ *
+ * @param resource $fp File handle
+ * @param bool|callable $lineCallback Optional function called before reading each query
+ * @param bool|callable $resultCallback Optional function called for each MySQL result
+ * @param string $fname Calling function name
+ * @param bool|callable $inputCallback Optional function called for each complete query sent
+ * @return bool|string
+ */
+ public function sourceStream(
+ $fp,
+ $lineCallback = false,
+ $resultCallback = false,
+ $fname = __METHOD__,
+ $inputCallback = false
+ );
+
+ /**
+ * Called by sourceStream() to check if we've reached a statement end
+ *
+ * @param string &$sql SQL assembled so far
+ * @param string &$newLine New line about to be added to $sql
+ * @return bool Whether $newLine contains end of the statement
+ */
+ public function streamStatementEnd( &$sql, &$newLine );
+
+ /**
+ * Delete a table
+ * @param string $tableName
+ * @param string $fName
+ * @return bool|ResultWrapper
+ */
+ public function dropTable( $tableName, $fName = __METHOD__ );
+
+ /**
+ * Perform a deadlock-prone transaction.
+ *
+ * This function invokes a callback function to perform a set of write
+ * queries. If a deadlock occurs during the processing, the transaction
+ * will be rolled back and the callback function will be called again.
+ *
+ * Avoid using this method outside of Job or Maintenance classes.
+ *
+ * Usage:
+ * $dbw->deadlockLoop( callback, ... );
+ *
+ * Extra arguments are passed through to the specified callback function.
+ * This method requires that no transactions are already active to avoid
+ * causing premature commits or exceptions.
+ *
+ * Returns whatever the callback function returned on its successful,
+ * iteration, or false on error, for example if the retry limit was
+ * reached.
+ *
+ * @return mixed
+ * @throws DBUnexpectedError
+ * @throws Exception
+ */
+ public function deadlockLoop();
+
+ /**
+ * Lists all the VIEWs in the database
+ *
+ * For caching purposes the list of all views should be stored in
+ * $this->allViews. The process cache can be cleared with clearViewsCache()
+ *
+ * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
+ * @param string $fname Name of calling function
+ * @throws RuntimeException
+ * @return array
+ */
+ public function listViews( $prefix = null, $fname = __METHOD__ );
+}
/**@{
* Database related constants
*/
-define( 'DBO_DEBUG', 1 );
-define( 'DBO_NOBUFFER', 2 );
-define( 'DBO_IGNORE', 4 );
-define( 'DBO_TRX', 8 ); // automatically start transaction on first query
-define( 'DBO_DEFAULT', 16 );
-define( 'DBO_PERSISTENT', 32 );
-define( 'DBO_SYSDBA', 64 ); // for oracle maintenance
-define( 'DBO_DDLMODE', 128 ); // when using schema files: mostly for Oracle
-define( 'DBO_SSL', 256 );
-define( 'DBO_COMPRESS', 512 );
+define( 'DBO_DEBUG', IDatabase::DBO_DEBUG );
+define( 'DBO_NOBUFFER', IDatabase::DBO_NOBUFFER );
+define( 'DBO_IGNORE', IDatabase::DBO_IGNORE );
+define( 'DBO_TRX', IDatabase::DBO_TRX );
+define( 'DBO_DEFAULT', IDatabase::DBO_DEFAULT );
+define( 'DBO_PERSISTENT', IDatabase::DBO_PERSISTENT );
+define( 'DBO_SYSDBA', IDatabase::DBO_SYSDBA );
+define( 'DBO_DDLMODE', IDatabase::DBO_DDLMODE );
+define( 'DBO_SSL', IDatabase::DBO_SSL );
+define( 'DBO_COMPRESS', IDatabase::DBO_COMPRESS );
/**@}*/
/**@{
* Valid database indexes
* Operation-based indexes
*/
-define( 'DB_REPLICA', -1 ); # Read from a replica (or only server)
-define( 'DB_MASTER', -2 ); # Write to master (or only server)
+define( 'DB_REPLICA', ILoadBalancer::DB_REPLICA );
+define( 'DB_MASTER', ILoadBalancer::DB_MASTER );
/**@}*/
--- /dev/null
+<?php
+/**
+ * Generator and manager of database load balancing objects
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+
+/**
+ * An interface for generating database load balancers
+ * @ingroup Database
+ * @since 1.28
+ */
+interface ILBFactory {
+ const SHUTDOWN_NO_CHRONPROT = 0; // don't save DB positions at all
+ const SHUTDOWN_CHRONPROT_ASYNC = 1; // save DB positions, but don't wait on remote DCs
+ const SHUTDOWN_CHRONPROT_SYNC = 2; // save DB positions, waiting on all DCs
+
+ /**
+ * Construct a manager of ILoadBalancer objects
+ *
+ * Sub-classes will extend the required keys in $conf with additional parameters
+ *
+ * @param $conf $params Array with keys:
+ * - localDomain: A DatabaseDomain or domain ID string.
+ * - readOnlyReason : Reason the master DB is read-only if so [optional]
+ * - srvCache : BagOStuff object for server cache [optional]
+ * - memCache : BagOStuff object for cluster memory cache [optional]
+ * - wanCache : WANObjectCache object [optional]
+ * - hostname : The name of the current server [optional]
+ * - cliMode: Whether the execution context is a CLI script. [optional]
+ * - profiler : Class name or instance with profileIn()/profileOut() methods. [optional]
+ * - trxProfiler: TransactionProfiler instance. [optional]
+ * - replLogger: PSR-3 logger instance. [optional]
+ * - connLogger: PSR-3 logger instance. [optional]
+ * - queryLogger: PSR-3 logger instance. [optional]
+ * - perfLogger: PSR-3 logger instance. [optional]
+ * - errorLogger : Callback that takes an Exception and logs it. [optional]
+ * @throws InvalidArgumentException
+ */
+ public function __construct( array $conf );
+
+ /**
+ * Disables all load balancers. All connections are closed, and any attempt to
+ * open a new connection will result in a DBAccessError.
+ * @see ILoadBalancer::disable()
+ */
+ public function destroy();
+
+ /**
+ * Create a new load balancer object. The resulting object will be untracked,
+ * not chronology-protected, and the caller is responsible for cleaning it up.
+ *
+ * This method is for only advanced usage and callers should almost always use
+ * getMainLB() instead. This method can be useful when a table is used as a key/value
+ * store. In that cases, one might want to query it in autocommit mode (DBO_TRX off)
+ * but still use DBO_TRX transaction rounds on other tables.
+ *
+ * @param bool|string $domain Domain ID, or false for the current domain
+ * @return ILoadBalancer
+ */
+ public function newMainLB( $domain = false );
+
+ /**
+ * Get a cached (tracked) load balancer object.
+ *
+ * @param bool|string $domain Domain ID, or false for the current domain
+ * @return ILoadBalancer
+ */
+ public function getMainLB( $domain = false );
+
+ /**
+ * Create a new load balancer for external storage. The resulting object will be
+ * untracked, not chronology-protected, and the caller is responsible for
+ * cleaning it up.
+ *
+ * This method is for only advanced usage and callers should almost always use
+ * getExternalLB() instead. This method can be useful when a table is used as a
+ * key/value store. In that cases, one might want to query it in autocommit mode
+ * (DBO_TRX off) but still use DBO_TRX transaction rounds on other tables.
+ *
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $domain Domain ID, or false for the current domain
+ * @return ILoadBalancer
+ */
+ public function newExternalLB( $cluster, $domain = false );
+
+ /**
+ * Get a cached (tracked) load balancer for external storage
+ *
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $domain Domain ID, or false for the current domain
+ * @return ILoadBalancer
+ */
+ public function getExternalLB( $cluster, $domain = false );
+
+ /**
+ * Execute a function for each tracked load balancer
+ * The callback is called with the load balancer as the first parameter,
+ * and $params passed as the subsequent parameters.
+ *
+ * @param callable $callback
+ * @param array $params
+ */
+ public function forEachLB( $callback, array $params = [] );
+
+ /**
+ * Prepare all tracked load balancers for shutdown
+ * @param integer $mode One of the class SHUTDOWN_* constants
+ * @param callable|null $workCallback Work to mask ChronologyProtector writes
+ */
+ public function shutdown(
+ $mode = self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback = null
+ );
+
+ /**
+ * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
+ *
+ * @param string $fname Caller name
+ */
+ public function flushReplicaSnapshots( $fname = __METHOD__ );
+
+ /**
+ * Commit on all connections. Done for two reasons:
+ * 1. To commit changes to the masters.
+ * 2. To release the snapshot on all connections, master and replica DB.
+ * @param string $fname Caller name
+ * @param array $options Options map:
+ * - maxWriteDuration: abort if more than this much time was spent in write queries
+ */
+ public function commitAll( $fname = __METHOD__, array $options = [] );
+
+ /**
+ * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
+ *
+ * The DBO_TRX setting will be reverted to the default in each of these methods:
+ * - commitMasterChanges()
+ * - rollbackMasterChanges()
+ * - commitAll()
+ *
+ * This allows for custom transaction rounds from any outer transaction scope.
+ *
+ * @param string $fname
+ * @throws DBTransactionError
+ */
+ public function beginMasterChanges( $fname = __METHOD__ );
+
+ /**
+ * Commit changes on all master connections
+ * @param string $fname Caller name
+ * @param array $options Options map:
+ * - maxWriteDuration: abort if more than this much time was spent in write queries
+ * @throws Exception
+ */
+ public function commitMasterChanges( $fname = __METHOD__, array $options = [] );
+
+ /**
+ * Rollback changes on all master connections
+ * @param string $fname Caller name
+ */
+ public function rollbackMasterChanges( $fname = __METHOD__ );
+
+ /**
+ * Determine if any master connection has pending changes
+ * @return bool
+ */
+ public function hasMasterChanges();
+
+ /**
+ * Detemine if any lagged replica DB connection was used
+ * @return bool
+ */
+ public function laggedReplicaUsed();
+
+ /**
+ * Determine if any master connection has pending/written changes from this request
+ * @param float $age How many seconds ago is "recent" [defaults to LB lag wait timeout]
+ * @return bool
+ */
+ public function hasOrMadeRecentMasterChanges( $age = null );
+
+ /**
+ * Waits for the replica DBs to catch up to the current master position
+ *
+ * Use this when updating very large numbers of rows, as in maintenance scripts,
+ * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs.
+ *
+ * By default this waits on all DB clusters actually used in this request.
+ * This makes sense when lag being waiting on is caused by the code that does this check.
+ * In that case, setting "ifWritesSince" can avoid the overhead of waiting for clusters
+ * that were not changed since the last wait check. To forcefully wait on a specific cluster
+ * for a given domain, use the 'domain' parameter. To forcefully wait on an "external" cluster,
+ * use the "cluster" parameter.
+ *
+ * Never call this function after a large DB write that is *still* in a transaction.
+ * It only makes sense to call this after the possible lag inducing changes were committed.
+ *
+ * @param array $opts Optional fields that include:
+ * - domain : wait on the load balancer DBs that handles the given domain ID
+ * - cluster : wait on the given external load balancer DBs
+ * - timeout : Max wait time. Default: ~60 seconds
+ * - ifWritesSince: Only wait if writes were done since this UNIX timestamp
+ * @throws DBReplicationWaitError If a timeout or error occured waiting on a DB cluster
+ */
+ public function waitForReplication( array $opts = [] );
+
+ /**
+ * Add a callback to be run in every call to waitForReplication() before waiting
+ *
+ * Callbacks must clear any transactions that they start
+ *
+ * @param string $name Callback name
+ * @param callable|null $callback Use null to unset a callback
+ */
+ public function setWaitForReplicationListener( $name, callable $callback = null );
+
+ /**
+ * Get a token asserting that no transaction writes are active
+ *
+ * @param string $fname Caller name (e.g. __METHOD__)
+ * @return mixed A value to pass to commitAndWaitForReplication()
+ */
+ public function getEmptyTransactionTicket( $fname );
+
+ /**
+ * Convenience method for safely running commitMasterChanges()/waitForReplication()
+ *
+ * This will commit and wait unless $ticket indicates it is unsafe to do so
+ *
+ * @param string $fname Caller name (e.g. __METHOD__)
+ * @param mixed $ticket Result of getEmptyTransactionTicket()
+ * @param array $opts Options to waitForReplication()
+ * @throws DBReplicationWaitError
+ */
+ public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] );
+
+ /**
+ * @param string $dbName DB master name (e.g. "db1052")
+ * @return float|bool UNIX timestamp when client last touched the DB or false if not recent
+ */
+ public function getChronologyProtectorTouched( $dbName );
+
+ /**
+ * Disable the ChronologyProtector for all load balancers
+ *
+ * This can be called at the start of special API entry points
+ */
+ public function disableChronologyProtection();
+
+ /**
+ * Set a new table prefix for the existing local domain ID for testing
+ *
+ * @param string $prefix
+ */
+ public function setDomainPrefix( $prefix );
+
+ /**
+ * Close all open database connections on all open load balancers.
+ */
+ public function closeAll();
+
+ /**
+ * @param string $agent Agent name for query profiling
+ */
+ public function setAgentName( $agent );
+
+ /**
+ * Append ?cpPosTime parameter to a URL for ChronologyProtector purposes if needed
+ *
+ * Note that unlike cookies, this works accross domains
+ *
+ * @param string $url
+ * @param float $time UNIX timestamp just before shutdown() was called
+ * @return string
+ */
+ public function appendPreShutdownTimeAsQuery( $url, $time );
+
+ /**
+ * @param array $info Map of fields, including:
+ * - IPAddress : IP address
+ * - UserAgent : User-Agent HTTP header
+ * - ChronologyProtection : cookie/header value specifying ChronologyProtector usage
+ */
+ public function setRequestInfo( array $info );
+}
* An interface for generating database load balancers
* @ingroup Database
*/
-abstract class LBFactory {
+abstract class LBFactory implements ILBFactory {
/** @var ChronologyProtector */
protected $chronProt;
/** @var object|string Class name or object With profileIn/profileOut methods */
/** @var string Agent name for query profiling */
protected $agent;
- const SHUTDOWN_NO_CHRONPROT = 0; // don't save DB positions at all
- const SHUTDOWN_CHRONPROT_ASYNC = 1; // save DB positions, but don't wait on remote DCs
- const SHUTDOWN_CHRONPROT_SYNC = 2; // save DB positions, waiting on all DCs
-
private static $loggerFields =
[ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ];
- /**
- * Construct a manager of ILoadBalancer objects
- *
- * Sub-classes will extend the required keys in $conf with additional parameters
- *
- * @param $conf $params Array with keys:
- * - localDomain: A DatabaseDomain or domain ID string.
- * - readOnlyReason : Reason the master DB is read-only if so [optional]
- * - srvCache : BagOStuff object for server cache [optional]
- * - memCache : BagOStuff object for cluster memory cache [optional]
- * - wanCache : WANObjectCache object [optional]
- * - hostname : The name of the current server [optional]
- * - cliMode: Whether the execution context is a CLI script. [optional]
- * - profiler : Class name or instance with profileIn()/profileOut() methods. [optional]
- * - trxProfiler: TransactionProfiler instance. [optional]
- * - replLogger: PSR-3 logger instance. [optional]
- * - connLogger: PSR-3 logger instance. [optional]
- * - queryLogger: PSR-3 logger instance. [optional]
- * - perfLogger: PSR-3 logger instance. [optional]
- * - errorLogger : Callback that takes an Exception and logs it. [optional]
- * @throws InvalidArgumentException
- */
public function __construct( array $conf ) {
$this->localDomain = isset( $conf['localDomain'] )
? DatabaseDomain::newFromId( $conf['localDomain'] )
$this->ticket = mt_rand();
}
- /**
- * Disables all load balancers. All connections are closed, and any attempt to
- * open a new connection will result in a DBAccessError.
- * @see ILoadBalancer::disable()
- */
public function destroy() {
$this->shutdown( self::SHUTDOWN_NO_CHRONPROT );
$this->forEachLBCallMethod( 'disable' );
}
- /**
- * Create a new load balancer object. The resulting object will be untracked,
- * not chronology-protected, and the caller is responsible for cleaning it up.
- *
- * This method is for only advanced usage and callers should almost always use
- * getMainLB() instead. This method can be useful when a table is used as a key/value
- * store. In that cases, one might want to query it in autocommit mode (DBO_TRX off)
- * but still use DBO_TRX transaction rounds on other tables.
- *
- * @param bool|string $domain Domain ID, or false for the current domain
- * @return ILoadBalancer
- */
- abstract public function newMainLB( $domain = false );
-
- /**
- * Get a cached (tracked) load balancer object.
- *
- * @param bool|string $domain Domain ID, or false for the current domain
- * @return ILoadBalancer
- */
- abstract public function getMainLB( $domain = false );
-
- /**
- * Create a new load balancer for external storage. The resulting object will be
- * untracked, not chronology-protected, and the caller is responsible for
- * cleaning it up.
- *
- * This method is for only advanced usage and callers should almost always use
- * getExternalLB() instead. This method can be useful when a table is used as a
- * key/value store. In that cases, one might want to query it in autocommit mode
- * (DBO_TRX off) but still use DBO_TRX transaction rounds on other tables.
- *
- * @param string $cluster External storage cluster, or false for core
- * @param bool|string $domain Domain ID, or false for the current domain
- * @return ILoadBalancer
- */
- abstract public function newExternalLB( $cluster, $domain = false );
-
- /**
- * Get a cached (tracked) load balancer for external storage
- *
- * @param string $cluster External storage cluster, or false for core
- * @param bool|string $domain Domain ID, or false for the current domain
- * @return ILoadBalancer
- */
- abstract public function getExternalLB( $cluster, $domain = false );
-
- /**
- * Execute a function for each tracked load balancer
- * The callback is called with the load balancer as the first parameter,
- * and $params passed as the subsequent parameters.
- *
- * @param callable $callback
- * @param array $params
- */
- abstract public function forEachLB( $callback, array $params = [] );
-
- /**
- * Prepare all tracked load balancers for shutdown
- * @param integer $mode One of the class SHUTDOWN_* constants
- * @param callable|null $workCallback Work to mask ChronologyProtector writes
- */
public function shutdown(
$mode = self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback = null
) {
);
}
- /**
- * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
- *
- * @param string $fname Caller name
- * @since 1.28
- */
public function flushReplicaSnapshots( $fname = __METHOD__ ) {
$this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
}
- /**
- * Commit on all connections. Done for two reasons:
- * 1. To commit changes to the masters.
- * 2. To release the snapshot on all connections, master and replica DB.
- * @param string $fname Caller name
- * @param array $options Options map:
- * - maxWriteDuration: abort if more than this much time was spent in write queries
- */
public function commitAll( $fname = __METHOD__, array $options = [] ) {
$this->commitMasterChanges( $fname, $options );
$this->forEachLBCallMethod( 'commitAll', [ $fname ] );
}
- /**
- * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
- *
- * The DBO_TRX setting will be reverted to the default in each of these methods:
- * - commitMasterChanges()
- * - rollbackMasterChanges()
- * - commitAll()
- *
- * This allows for custom transaction rounds from any outer transaction scope.
- *
- * @param string $fname
- * @throws DBTransactionError
- * @since 1.28
- */
public function beginMasterChanges( $fname = __METHOD__ ) {
if ( $this->trxRoundId !== false ) {
throw new DBTransactionError(
$this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] );
}
- /**
- * Commit changes on all master connections
- * @param string $fname Caller name
- * @param array $options Options map:
- * - maxWriteDuration: abort if more than this much time was spent in write queries
- * @throws Exception
- */
public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
throw new DBTransactionError(
}
}
- /**
- * Rollback changes on all master connections
- * @param string $fname Caller name
- * @since 1.23
- */
public function rollbackMasterChanges( $fname = __METHOD__ ) {
$this->trxRoundId = false;
$this->forEachLBCallMethod( 'suppressTransactionEndCallbacks' );
}
}
- /**
- * Determine if any master connection has pending changes
- * @return bool
- * @since 1.23
- */
public function hasMasterChanges() {
$ret = false;
$this->forEachLB( function ( ILoadBalancer $lb ) use ( &$ret ) {
return $ret;
}
- /**
- * Detemine if any lagged replica DB connection was used
- * @return bool
- * @since 1.28
- */
public function laggedReplicaUsed() {
$ret = false;
$this->forEachLB( function ( ILoadBalancer $lb ) use ( &$ret ) {
return $ret;
}
- /**
- * Determine if any master connection has pending/written changes from this request
- * @param float $age How many seconds ago is "recent" [defaults to LB lag wait timeout]
- * @return bool
- * @since 1.27
- */
public function hasOrMadeRecentMasterChanges( $age = null ) {
$ret = false;
$this->forEachLB( function ( ILoadBalancer $lb ) use ( $age, &$ret ) {
return $ret;
}
- /**
- * Waits for the replica DBs to catch up to the current master position
- *
- * Use this when updating very large numbers of rows, as in maintenance scripts,
- * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs.
- *
- * By default this waits on all DB clusters actually used in this request.
- * This makes sense when lag being waiting on is caused by the code that does this check.
- * In that case, setting "ifWritesSince" can avoid the overhead of waiting for clusters
- * that were not changed since the last wait check. To forcefully wait on a specific cluster
- * for a given domain, use the 'domain' parameter. To forcefully wait on an "external" cluster,
- * use the "cluster" parameter.
- *
- * Never call this function after a large DB write that is *still* in a transaction.
- * It only makes sense to call this after the possible lag inducing changes were committed.
- *
- * @param array $opts Optional fields that include:
- * - domain : wait on the load balancer DBs that handles the given domain ID
- * - cluster : wait on the given external load balancer DBs
- * - timeout : Max wait time. Default: ~60 seconds
- * - ifWritesSince: Only wait if writes were done since this UNIX timestamp
- * @throws DBReplicationWaitError If a timeout or error occured waiting on a DB cluster
- * @since 1.27
- */
public function waitForReplication( array $opts = [] ) {
$opts += [
'domain' => false,
}
}
- /**
- * Add a callback to be run in every call to waitForReplication() before waiting
- *
- * Callbacks must clear any transactions that they start
- *
- * @param string $name Callback name
- * @param callable|null $callback Use null to unset a callback
- * @since 1.28
- */
public function setWaitForReplicationListener( $name, callable $callback = null ) {
if ( $callback ) {
$this->replicationWaitCallbacks[$name] = $callback;
}
}
- /**
- * Get a token asserting that no transaction writes are active
- *
- * @param string $fname Caller name (e.g. __METHOD__)
- * @return mixed A value to pass to commitAndWaitForReplication()
- * @since 1.28
- */
public function getEmptyTransactionTicket( $fname ) {
if ( $this->hasMasterChanges() ) {
$this->queryLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" .
return $this->ticket;
}
- /**
- * Convenience method for safely running commitMasterChanges()/waitForReplication()
- *
- * This will commit and wait unless $ticket indicates it is unsafe to do so
- *
- * @param string $fname Caller name (e.g. __METHOD__)
- * @param mixed $ticket Result of getEmptyTransactionTicket()
- * @param array $opts Options to waitForReplication()
- * @throws DBReplicationWaitError
- * @since 1.28
- */
public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
if ( $ticket !== $this->ticket ) {
$this->perfLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" .
}
}
- /**
- * @param string $dbName DB master name (e.g. "db1052")
- * @return float|bool UNIX timestamp when client last touched the DB or false if not recent
- * @since 1.28
- */
public function getChronologyProtectorTouched( $dbName ) {
return $this->getChronologyProtector()->getTouched( $dbName );
}
- /**
- * Disable the ChronologyProtector for all load balancers
- *
- * This can be called at the start of special API entry points
- *
- * @since 1.27
- */
public function disableChronologyProtection() {
$this->getChronologyProtector()->setEnabled( false );
}
}
}
- /**
- * Set a new table prefix for the existing local domain ID for testing
- *
- * @param string $prefix
- * @since 1.28
- */
public function setDomainPrefix( $prefix ) {
$this->localDomain = new DatabaseDomain(
$this->localDomain->getDatabase(),
} );
}
- /**
- * Close all open database connections on all open load balancers.
- * @since 1.28
- */
public function closeAll() {
$this->forEachLBCallMethod( 'closeAll', [] );
}
- /**
- * @param string $agent Agent name for query profiling
- * @since 1.28
- */
public function setAgentName( $agent ) {
$this->agent = $agent;
}
- /**
- * Append ?cpPosTime parameter to a URL for ChronologyProtector purposes if needed
- *
- * Note that unlike cookies, this works accross domains
- *
- * @param string $url
- * @param float $time UNIX timestamp just before shutdown() was called
- * @return string
- * @since 1.28
- */
public function appendPreShutdownTimeAsQuery( $url, $time ) {
$usedCluster = 0;
$this->forEachLB( function ( ILoadBalancer $lb ) use ( &$usedCluster ) {
return strpos( $url, '?' ) === false ? "$url?cpPosTime=$time" : "$url&cpPosTime=$time";
}
- /**
- * @param array $info Map of fields, including:
- * - IPAddress : IP address
- * - UserAgent : User-Agent HTTP header
- * - ChronologyProtection : cookie/header value specifying ChronologyProtector usage
- * @since 1.28
- */
public function setRequestInfo( array $info ) {
$this->requestInfo = $info + $this->requestInfo;
}
}
$serverInfo['hostName'] = $serverName;
$serverInfo['load'] = $load;
- $serverInfo += [ 'flags' => DBO_DEFAULT ];
+ $serverInfo += [ 'flags' => IDatabase::DBO_DEFAULT ];
$servers[] = $serverInfo;
}
* @ingroup Database
* @author Aaron Schulz
*/
-require_once __DIR__ . '/../defines.php';
/**
* Database cluster connection, tracking, load balancing, and transaction manager interface
* - Read-only archive clones: set 'is static' in the server configuration maps. This will
* treat all such DBs as having 0 lag.
* - SQL load balancing proxy: any proxy should handle lag checks on its own, so the 'max lag'
- * parameter should probably be set to INF in the server configuration maps. This will make
- * the load balancer ignore whatever it detects as the lag of the logical replica is (which
- * would probably just randomly bounce around).
+ * parameter should probably be set to INF in the server configuration maps. This will make
+ * the load balancer ignore whatever it detects as the lag of the logical replica is (which
+ * would probably just randomly bounce around).
*
* If using a SQL proxy service, it would probably be best to have two proxy hosts for the
* load balancer to talk to. One would be the 'host' of the master server entry and another for
* @ingroup Database
*/
interface ILoadBalancer {
- /** @var integer Request a master DB connection */
- const DB_MASTER = DB_MASTER;
/** @var integer Request a replica DB connection */
- const DB_REPLICA = DB_REPLICA;
+ const DB_REPLICA = -1;
+ /** @var integer Request a master DB connection */
+ const DB_MASTER = -2;
/**
* Construct a manager of IDatabase connection objects
? [ false ] // check one "group": the generic pool
: (array)$groups;
- $masterOnly = ( $i == DB_MASTER || $i == $this->getWriterIndex() );
+ $masterOnly = ( $i == self::DB_MASTER || $i == $this->getWriterIndex() );
$oldConnsOpened = $this->connsOpened; // connections open now
- if ( $i == DB_MASTER ) {
+ if ( $i == self::DB_MASTER ) {
$i = $this->getWriterIndex();
} else {
# Try to find an available server in any the query groups (in order)
}
# Operation-based index
- if ( $i == DB_REPLICA ) {
+ if ( $i == self::DB_REPLICA ) {
$this->mLastError = 'Unknown error'; // reset error string
# Try the general server pool if $groups are unavailable.
$i = in_array( false, $groups, true )
/**
* This can happen in code like:
* foreach ( $dbs as $db ) {
- * $conn = $lb->getConnection( DB_REPLICA, [], $db );
+ * $conn = $lb->getConnection( $lb::DB_REPLICA, [], $db );
* ...
* $lb->reuseConnection( $conn );
* }
$server['agent'] = $this->agent;
// Use DBO_DEFAULT flags by default for LoadBalancer managed databases. Assume that the
// application calls LoadBalancer::commitMasterChanges() before the PHP script completes.
- $server['flags'] = isset( $server['flags'] ) ? $server['flags'] : DBO_DEFAULT;
+ $server['flags'] = isset( $server['flags'] ) ? $server['flags'] : IDatabase::DBO_DEFAULT;
// Create a live connection object
try {
$db->setLBInfo( $server );
$db->setLazyMasterHandle(
- $this->getLazyConnectionRef( DB_MASTER, [], $db->getDomainID() )
+ $this->getLazyConnectionRef( self::DB_MASTER, [], $db->getDomainID() )
);
$db->setTableAliases( $this->tableAliases );
* @param IDatabase $conn
*/
private function applyTransactionRoundFlags( IDatabase $conn ) {
- if ( $conn->getFlag( DBO_DEFAULT ) ) {
+ if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
// DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT.
// Force DBO_TRX even in CLI mode since a commit round is expected soon.
- $conn->setFlag( DBO_TRX, $conn::REMEMBER_PRIOR );
+ $conn->setFlag( $conn::DBO_TRX, $conn::REMEMBER_PRIOR );
// If config has explicitly requested DBO_TRX be either on or off by not
// setting DBO_DEFAULT, then respect that. Forcing no transactions is useful
// for things like blob stores (ExternalStore) which want auto-commit mode.
* @param IDatabase $conn
*/
private function undoTransactionRoundFlags( IDatabase $conn ) {
- if ( $conn->getFlag( DBO_DEFAULT ) ) {
+ if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
$conn->restoreFlags( $conn::RESTORE_PRIOR );
}
}
if ( !$this->laggedReplicaMode && $this->getServerCount() > 1 ) {
try {
// See if laggedReplicaMode gets set
- $conn = $this->getConnection( DB_REPLICA, false, $domain );
+ $conn = $this->getConnection( self::DB_REPLICA, false, $domain );
$this->reuseConnection( $conn );
} catch ( DBConnectionError $e ) {
// Avoid expensive re-connect attempts and failures
function () use ( $domain, $conn ) {
$this->trxProfiler->setSilenced( true );
try {
- $dbw = $conn ?: $this->getConnection( DB_MASTER, [], $domain );
+ $dbw = $conn ?: $this->getConnection( self::DB_MASTER, [], $domain );
$readOnly = (int)$dbw->serverIsReadOnly();
if ( !$conn ) {
$this->reuseConnection( $dbw );
if ( !$pos ) {
// Get the current master position
- $dbw = $this->getConnection( DB_MASTER );
+ $dbw = $this->getConnection( self::DB_MASTER );
$pos = $dbw->getMasterPos();
$this->reuseConnection( $dbw );
}
( function ( $ ) {
'use strict';
- var mw,
+ var mw, StringSet, log,
hasOwn = Object.prototype.hasOwnProperty,
slice = Array.prototype.slice,
trackCallbacks = $.Callbacks( 'memory' ),
return hash;
}
+ StringSet = window.Set || ( function () {
+ /**
+ * @private
+ * @class
+ */
+ function StringSet() {
+ this.set = {};
+ }
+ StringSet.prototype.add = function ( value ) {
+ this.set[ value ] = true;
+ };
+ StringSet.prototype.has = function ( value ) {
+ return this.set.hasOwnProperty( value );
+ };
+ return StringSet;
+ }() );
+
/**
* Create an object that can be read from or written to from methods that allow
* interaction both with single and multiple properties at once.
}
};
+ log = ( function () {
+ // Also update the restoration of methods in mediawiki.log.js
+ // when adding or removing methods here.
+ var log = function () {},
+ console = window.console;
+
+ /**
+ * @class mw.log
+ * @singleton
+ */
+
+ /**
+ * Write a message to the console's warning channel.
+ * Actions not supported by the browser console are silently ignored.
+ *
+ * @param {...string} msg Messages to output to console
+ */
+ log.warn = console && console.warn && Function.prototype.bind ?
+ Function.prototype.bind.call( console.warn, console ) :
+ $.noop;
+
+ /**
+ * Write a message to the console's error channel.
+ *
+ * Most browsers provide a stacktrace by default if the argument
+ * is a caught Error object.
+ *
+ * @since 1.26
+ * @param {Error|...string} msg Messages to output to console
+ */
+ log.error = console && console.error && Function.prototype.bind ?
+ Function.prototype.bind.call( console.error, console ) :
+ $.noop;
+
+ /**
+ * Create a property in a host object that, when accessed, will produce
+ * a deprecation warning in the console.
+ *
+ * @param {Object} obj Host object of deprecated property
+ * @param {string} key Name of property to create in `obj`
+ * @param {Mixed} val The value this property should return when accessed
+ * @param {string} [msg] Optional text to include in the deprecation message
+ */
+ log.deprecate = !Object.defineProperty ? function ( obj, key, val ) {
+ obj[ key ] = val;
+ } : function ( obj, key, val, msg ) {
+ msg = 'Use of "' + key + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' );
+ var logged = new StringSet();
+ function uniqueTrace() {
+ var trace = new Error().stack;
+ if ( logged.has( trace ) ) {
+ return false;
+ }
+ logged.add( trace );
+ return true;
+ }
+ Object.defineProperty( obj, key, {
+ configurable: true,
+ enumerable: true,
+ get: function () {
+ if ( uniqueTrace() ) {
+ mw.track( 'mw.deprecate', key );
+ mw.log.warn( msg );
+ }
+ return val;
+ },
+ set: function ( newVal ) {
+ if ( uniqueTrace() ) {
+ mw.track( 'mw.deprecate', key );
+ mw.log.warn( msg );
+ }
+ val = newVal;
+ }
+ } );
+
+ };
+
+ return log;
+ }() );
+
/**
* @class mw
*/
},
/**
- * Dummy placeholder for {@link mw.log}
+ * No-op dummy placeholder for {@link mw.log} in debug mode.
*
* @method
*/
- log: ( function () {
- // Also update the restoration of methods in mediawiki.log.js
- // when adding or removing methods here.
- var log = function () {},
- console = window.console;
-
- /**
- * @class mw.log
- * @singleton
- */
-
- /**
- * Write a message to the console's warning channel.
- * Actions not supported by the browser console are silently ignored.
- *
- * @param {...string} msg Messages to output to console
- */
- log.warn = console && console.warn && Function.prototype.bind ?
- Function.prototype.bind.call( console.warn, console ) :
- $.noop;
-
- /**
- * Write a message to the console's error channel.
- *
- * Most browsers provide a stacktrace by default if the argument
- * is a caught Error object.
- *
- * @since 1.26
- * @param {Error|...string} msg Messages to output to console
- */
- log.error = console && console.error && Function.prototype.bind ?
- Function.prototype.bind.call( console.error, console ) :
- $.noop;
-
- /**
- * Create a property in a host object that, when accessed, will produce
- * a deprecation warning in the console with backtrace.
- *
- * @param {Object} obj Host object of deprecated property
- * @param {string} key Name of property to create in `obj`
- * @param {Mixed} val The value this property should return when accessed
- * @param {string} [msg] Optional text to include in the deprecation message
- */
- log.deprecate = !Object.defineProperty ? function ( obj, key, val ) {
- obj[ key ] = val;
- } : function ( obj, key, val, msg ) {
- /*globals Set */
- msg = 'Use of "' + key + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' );
- var logged, loggedIsSet, uniqueTrace;
- if ( window.Set ) {
- logged = new Set();
- loggedIsSet = true;
- } else {
- logged = {};
- loggedIsSet = false;
- }
- uniqueTrace = function () {
- var trace = new Error().stack;
- if ( loggedIsSet ) {
- if ( logged.has( trace ) ) {
- return false;
- }
- logged.add( trace );
- return true;
- } else {
- if ( logged.hasOwnProperty( trace ) ) {
- return false;
- }
- logged[ trace ] = 1;
- return true;
- }
- };
- Object.defineProperty( obj, key, {
- configurable: true,
- enumerable: true,
- get: function () {
- if ( uniqueTrace() ) {
- mw.track( 'mw.deprecate', key );
- mw.log.warn( msg );
- }
- return val;
- },
- set: function ( newVal ) {
- if ( uniqueTrace() ) {
- mw.track( 'mw.deprecate', key );
- mw.log.warn( msg );
- }
- val = newVal;
- }
- } );
-
- };
-
- return log;
- }() ),
+ log: log,
/**
* Client for ResourceLoader server end point.
* dependencies, such that later modules depend on earlier modules. The array
* contains the module names. If the array contains already some module names,
* this function appends its result to the pre-existing array.
- * @param {Object} [unresolved] Hash used to track the current dependency
- * chain; used to report loops in the dependency graph.
+ * @param {StringSet} [unresolved] Used to track the current dependency
+ * chain, and to report loops in the dependency graph.
* @throws {Error} If any unregistered module or a dependency loop is encountered
*/
function sortDependencies( module, resolved, unresolved ) {
}
// Create unresolved if not passed in
if ( !unresolved ) {
- unresolved = {};
+ unresolved = new StringSet();
}
// Tracks down dependencies
deps = registry[ module ].dependencies;
for ( i = 0; i < deps.length; i++ ) {
if ( $.inArray( deps[ i ], resolved ) === -1 ) {
- if ( unresolved[ deps[ i ] ] ) {
+ if ( unresolved.has( deps[ i ] ) ) {
throw new Error( mw.format(
'Circular reference detected: $1 -> $2',
module,
) );
}
- // Add to unresolved
- unresolved[ module ] = true;
+ unresolved.add( module );
sortDependencies( deps[ i ], resolved, unresolved );
}
}
markModuleReady();
}
} catch ( e ) {
- // This needs to NOT use mw.log because these errors are common in production mode
- // and not in debug mode, such as when a symbol that should be global isn't exported
+ // Use mw.track instead of mw.log because these errors are common in production mode
+ // (e.g. undefined variable), and mw.log is only enabled in debug mode.
registry[ module ].state = 'error';
mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'module-execute' } );
handlePending( module );
}
}
- /**
- * Evaluate a batch of load.php responses retrieved from mw.loader.store.
- *
- * @private
- * @param {string[]} implementations Array containing pieces of JavaScript code in the
- * form of calls to mw.loader#implement().
- * @param {Function} cb Callback in case of failure
- * @param {Error} cb.err
- */
- function batchEval( implementations, cb ) {
- if ( !implementations.length ) {
- return;
- }
- mw.requestIdleCallback( function iterate( deadline ) {
- while ( implementations[ 0 ] && deadline.timeRemaining() > 5 ) {
- try {
- $.globalEval( implementations.shift() );
- } catch ( err ) {
- cb( err );
- return;
- }
- }
- if ( implementations[ 0 ] ) {
- mw.requestIdleCallback( iterate );
- }
- } );
- }
-
/* Public Members */
return {
/**
* @protected
*/
work: function () {
- var q, batch, implementations, sourceModules;
+ var q, batch, concatSource, origBatch;
batch = [];
mw.loader.store.init();
if ( mw.loader.store.enabled ) {
- implementations = [];
- sourceModules = [];
+ concatSource = [];
+ origBatch = batch;
batch = $.grep( batch, function ( module ) {
- var implementation = mw.loader.store.get( module );
- if ( implementation ) {
- implementations.push( implementation );
- sourceModules.push( module );
+ var source = mw.loader.store.get( module );
+ if ( source ) {
+ concatSource.push( source );
return false;
}
return true;
} );
- batchEval( implementations, function ( err ) {
+ try {
+ $.globalEval( concatSource.join( ';' ) );
+ } catch ( err ) {
// Not good, the cached mw.loader.implement calls failed! This should
// never happen, barring ResourceLoader bugs, browser bugs and PEBKACs.
// Depending on how corrupt the string is, it is likely that some
// modules' implement() succeeded while the ones after the error will
// never run and leave their modules in the 'loading' state forever.
+
// Since this is an error not caused by an individual module but by
// something that infected the implement call itself, don't take any
// risks and clear everything in this cache.
mw.loader.store.clear();
+ // Re-add the ones still pending back to the batch and let the server
+ // repopulate these modules to the cache.
+ // This means that at most one module will be useless (the one that had
+ // the error) instead of all of them.
mw.track( 'resourceloader.exception', { exception: err, source: 'store-eval' } );
-
- // Re-add the failed ones that are still pending back to the batch
- var failed = $.grep( sourceModules, function ( module ) {
+ origBatch = $.grep( origBatch, function ( module ) {
return registry[ module ].state === 'loading';
} );
- batchRequest( failed );
- } );
+ batch = batch.concat( origBatch );
+ }
}
batchRequest( batch );
* reference, so that debugging tools loaded later are supported (e.g. Firebug Lite in IE).
*
* @private
- * @method log_
* @param {string} topic Stream name passed by mw.track
* @param {Object} data Data passed by mw.track
* @param {Error} [data.exception]
* @param {string} data.source Error source
* @param {string} [data.module] Name of module which caused the error
*/
- function log( topic, data ) {
+ function logError( topic, data ) {
var msg,
e = data.exception,
source = data.source,
}
// Subscribe to error streams
- mw.trackSubscribe( 'resourceloader.exception', log );
- mw.trackSubscribe( 'resourceloader.assert', log );
+ mw.trackSubscribe( 'resourceloader.exception', logError );
+ mw.trackSubscribe( 'resourceloader.assert', logError );
/**
* Fired when all modules associated with the page have finished loading.
( function ( mw, $ ) {
- // Reference to dummy
- // We don't need the dummy, but it has other methods on it
- // that we need to restore afterwards.
+ // Keep reference to the dummy placeholder from mediawiki.js
+ // The root is replaced below, but it has other methods that we need to restore.
var original = mw.log,
slice = Array.prototype.slice;