rdbms: various cleanups to LoadBalancer::reallyOpenConnection()
Move the DBO_TRX init logic out of Database::__construct() and into LoadBalancer since the later already handles setting and clearing this flag based on transaction rounds starting and ending. Add 'lazyMasterHandle', 'topologyRole', and 'topologicalMaster' parameters to Database::factory() and inject them via LoadBalancer all at once in order to avoid worrying about call order. Move some type casting code to Database::__construct(). Add IDatabase::getTopologyRole()/getTopologicalMaster(). Use constants for getLBInfo()/setLBInfo() for better usage tracking and typo resistance. Change-Id: I437ce434326601e6ba36d9aedc55db396dfe4452
This commit is contained in:
parent
cd7972620f
commit
fb621c26a3
15 changed files with 408 additions and 286 deletions
|
|
@ -80,6 +80,14 @@ class DBConnRef implements IDatabase {
|
|||
return $this->__call( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function getTopologyRole() {
|
||||
return $this->__call( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function getTopologyRootMaster() {
|
||||
return $this->__call( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool|null $buffer
|
||||
* @return bool
|
||||
|
|
@ -140,11 +148,6 @@ class DBConnRef implements IDatabase {
|
|||
throw new DBUnexpectedError( $this, "Changing LB info is disallowed to enable reuse." );
|
||||
}
|
||||
|
||||
public function setLazyMasterHandle( IDatabase $conn ) {
|
||||
// Disallow things that might confuse the LoadBalancer tracking
|
||||
throw new DBUnexpectedError( $this, "Database injection is disallowed to enable reuse." );
|
||||
}
|
||||
|
||||
public function implicitOrderby() {
|
||||
return $this->__call( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
protected $cliMode;
|
||||
/** @var string Agent name for query profiling */
|
||||
protected $agent;
|
||||
/** @var string Replication topology role of the server; one of the class ROLE_* constants */
|
||||
protected $topologyRole;
|
||||
/** @var string|null Host (or address) of the root master server for the replication topology */
|
||||
protected $topologyRootMaster;
|
||||
/** @var array Parameters used by initConnection() to establish a connection */
|
||||
protected $connectionParams;
|
||||
/** @var string[]|int[]|float[] SQL variables values to use for all new connections */
|
||||
|
|
@ -180,6 +184,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
/** @var int|null Integer ID of the managing LBFactory instance or null if none */
|
||||
private $ownerId;
|
||||
|
||||
/** @var string Whether the database is a file on disk */
|
||||
const ATTR_DB_IS_FILE = 'db-is-file';
|
||||
/** @var string Lock granularity is on the level of the entire database */
|
||||
const ATTR_DB_LEVEL_LOCKING = 'db-level-locking';
|
||||
/** @var string The SCHEMA keyword refers to a grouping of tables in a database */
|
||||
|
|
@ -243,24 +249,27 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
* @param array $params Parameters passed from Database::factory()
|
||||
*/
|
||||
public function __construct( array $params ) {
|
||||
$this->connectionParams = [];
|
||||
foreach ( [ 'host', 'user', 'password', 'dbname', 'schema', 'tablePrefix' ] as $name ) {
|
||||
$this->connectionParams[$name] = $params[$name];
|
||||
}
|
||||
$this->connectionParams = [
|
||||
'host' => strlen( $params['host'] ) ? $params['host'] : null,
|
||||
'user' => strlen( $params['user'] ) ? $params['user'] : null,
|
||||
'dbname' => strlen( $params['dbname'] ) ? $params['dbname'] : null,
|
||||
'schema' => strlen( $params['schema'] ) ? $params['schema'] : null,
|
||||
'password' => is_string( $params['password'] ) ? $params['password'] : null,
|
||||
'tablePrefix' => (string)$params['tablePrefix']
|
||||
];
|
||||
|
||||
$this->lbInfo = $params['lbInfo'] ?? [];
|
||||
$this->lazyMasterHandle = $params['lazyMasterHandle'] ?? null;
|
||||
$this->connectionVariables = $params['variables'] ?? [];
|
||||
$this->cliMode = $params['cliMode'];
|
||||
$this->agent = $params['agent'];
|
||||
$this->flags = $params['flags'];
|
||||
if ( $this->flags & self::DBO_DEFAULT ) {
|
||||
if ( $this->cliMode ) {
|
||||
$this->flags &= ~self::DBO_TRX;
|
||||
} else {
|
||||
$this->flags |= self::DBO_TRX;
|
||||
}
|
||||
}
|
||||
|
||||
$this->flags = (int)$params['flags'];
|
||||
$this->cliMode = (bool)$params['cliMode'];
|
||||
$this->agent = (string)$params['agent'];
|
||||
$this->topologyRole = (string)$params['topologyRole'];
|
||||
$this->topologyRootMaster = (string)$params['topologicalMaster'];
|
||||
$this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'] ?? 10000;
|
||||
|
||||
$this->srvCache = $params['srvCache'] ?? new HashBagOStuff();
|
||||
$this->srvCache = $params['srvCache'];
|
||||
$this->profiler = is_callable( $params['profiler'] ) ? $params['profiler'] : null;
|
||||
$this->trxProfiler = $params['trxProfiler'];
|
||||
$this->connLogger = $params['connLogger'];
|
||||
|
|
@ -351,6 +360,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
* 'mysqli' driver; the old one 'mysql' has been removed.
|
||||
* - variables: Optional map of session variables to set after connecting. This can be
|
||||
* used to adjust lock timeouts or encoding modes and the like.
|
||||
* - topologyRole: Optional IDatabase::ROLE_* constant for the server.
|
||||
* - topologicalMaster: Optional name of the master server within the replication topology.
|
||||
* - lbInfo: Optional map of field/values for the managing load balancer instance.
|
||||
* The "master" and "replica" fields are used to flag the replication role of this
|
||||
* database server and whether methods like getLag() should actually issue queries.
|
||||
* - lazyMasterHandle: lazy-connecting IDatabase handle to the master DB for the cluster
|
||||
* that this database belongs to. This is used for replication status purposes.
|
||||
* - connLogger: Optional PSR-3 logger interface instance.
|
||||
* - queryLogger: Optional PSR-3 logger interface instance.
|
||||
* - profiler : Optional callback that takes a section name argument and returns
|
||||
|
|
@ -375,6 +391,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
|
||||
if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) {
|
||||
$params += [
|
||||
// Default configuration
|
||||
'host' => null,
|
||||
'user' => null,
|
||||
'password' => null,
|
||||
|
|
@ -383,24 +400,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
'tablePrefix' => '',
|
||||
'flags' => 0,
|
||||
'variables' => [],
|
||||
'lbInfo' => [],
|
||||
'cliMode' => ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ),
|
||||
'agent' => basename( $_SERVER['SCRIPT_NAME'] ) . '@' . gethostname(),
|
||||
'ownerId' => null
|
||||
];
|
||||
|
||||
$normalizedParams = [
|
||||
// Configuration
|
||||
'host' => strlen( $params['host'] ) ? $params['host'] : null,
|
||||
'user' => strlen( $params['user'] ) ? $params['user'] : null,
|
||||
'password' => is_string( $params['password'] ) ? $params['password'] : null,
|
||||
'dbname' => strlen( $params['dbname'] ) ? $params['dbname'] : null,
|
||||
'schema' => strlen( $params['schema'] ) ? $params['schema'] : null,
|
||||
'tablePrefix' => (string)$params['tablePrefix'],
|
||||
'flags' => (int)$params['flags'],
|
||||
'variables' => $params['variables'],
|
||||
'cliMode' => (bool)$params['cliMode'],
|
||||
'agent' => (string)$params['agent'],
|
||||
'ownerId' => null,
|
||||
'topologyRole' => null,
|
||||
'topologicalMaster' => null,
|
||||
// Objects and callbacks
|
||||
'lazyMasterHandle' => $params['lazyMasterHandle'] ?? null,
|
||||
'srvCache' => $params['srvCache'] ?? new HashBagOStuff(),
|
||||
'profiler' => $params['profiler'] ?? null,
|
||||
'trxProfiler' => $params['trxProfiler'] ?? new TransactionProfiler(),
|
||||
|
|
@ -412,10 +419,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
'deprecationLogger' => $params['deprecationLogger'] ?? function ( $msg ) {
|
||||
trigger_error( $msg, E_USER_DEPRECATED );
|
||||
}
|
||||
] + $params;
|
||||
];
|
||||
|
||||
/** @var Database $conn */
|
||||
$conn = new $class( $normalizedParams );
|
||||
$conn = new $class( $params );
|
||||
if ( $connect === self::NEW_CONNECTED ) {
|
||||
$conn->initConnection();
|
||||
}
|
||||
|
|
@ -435,6 +442,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
*/
|
||||
final public static function attributesFromType( $dbType, $driver = null ) {
|
||||
static $defaults = [
|
||||
self::ATTR_DB_IS_FILE => false,
|
||||
self::ATTR_DB_LEVEL_LOCKING => false,
|
||||
self::ATTR_SCHEMAS_AS_TABLE_GROUPS => false
|
||||
];
|
||||
|
|
@ -520,6 +528,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
return $this->getServerVersion();
|
||||
}
|
||||
|
||||
public function getTopologyRole() {
|
||||
return $this->topologyRole;
|
||||
}
|
||||
|
||||
public function getTopologyRootMaster() {
|
||||
return $this->topologyRootMaster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backwards-compatibility no-op method for disabling query buffering
|
||||
*
|
||||
|
|
@ -611,13 +627,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
}
|
||||
}
|
||||
|
||||
public function setLazyMasterHandle( IDatabase $conn ) {
|
||||
$this->lazyMasterHandle = $conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a handle to the master server of the cluster to which this server belongs
|
||||
*
|
||||
* @return IDatabase|null
|
||||
* @see setLazyMasterHandle()
|
||||
* @since 1.27
|
||||
*/
|
||||
protected function getLazyMasterHandle() {
|
||||
|
|
@ -660,7 +673,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
final protected function getTransactionRoundId() {
|
||||
// If transaction round participation is enabled, see if one is active
|
||||
if ( $this->getFlag( self::DBO_TRX ) ) {
|
||||
$id = $this->getLBInfo( 'trxRoundId' );
|
||||
$id = $this->getLBInfo( self::LB_TRX_ROUND_ID );
|
||||
|
||||
return is_string( $id ) ? $id : null;
|
||||
}
|
||||
|
|
@ -963,19 +976,17 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
/**
|
||||
* Make sure that this server is not marked as a replica nor read-only as a sanity check
|
||||
*
|
||||
* @throws DBReadOnlyRoleError
|
||||
* @throws DBReadOnlyError
|
||||
*/
|
||||
protected function assertIsWritableMaster() {
|
||||
if ( $this->getLBInfo( 'replica' ) ) {
|
||||
throw new DBReadOnlyRoleError(
|
||||
$this,
|
||||
'Write operations are not allowed on replica database connections'
|
||||
);
|
||||
}
|
||||
$reason = $this->getReadOnlyReason();
|
||||
if ( $reason !== false ) {
|
||||
throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
|
||||
$info = $this->getReadOnlyReason();
|
||||
if ( $info ) {
|
||||
list( $reason, $source ) = $info;
|
||||
if ( $source === 'role' ) {
|
||||
throw new DBReadOnlyRoleError( $this, "Database is read-only: $reason" );
|
||||
} else {
|
||||
throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1285,7 +1296,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
}
|
||||
}
|
||||
|
||||
$prefix = $this->getLBInfo( 'master' ) ? 'query-m: ' : 'query: ';
|
||||
$prefix = $this->topologyRole ? 'query-m: ' : 'query: ';
|
||||
$generalizedSql = new GeneralizedSql( $sql, $this->trxShortId, $prefix );
|
||||
|
||||
$startTime = microtime( true );
|
||||
|
|
@ -4384,14 +4395,16 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
}
|
||||
|
||||
/**
|
||||
* Get a replica DB lag estimate for this server
|
||||
* Get a replica DB lag estimate for this server at the start of a transaction
|
||||
*
|
||||
* This is a no-op unless the server is known a priori to be a replica DB
|
||||
*
|
||||
* @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate)
|
||||
* @since 1.27
|
||||
*/
|
||||
protected function getApproximateLagStatus() {
|
||||
return [
|
||||
'lag' => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
|
||||
'lag' => ( $this->topologyRole === self::ROLE_STREAMING_REPLICA ) ? $this->getLag() : 0,
|
||||
'since' => microtime( true )
|
||||
];
|
||||
}
|
||||
|
|
@ -4433,9 +4446,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
}
|
||||
|
||||
public function getLag() {
|
||||
if ( $this->getLBInfo( 'master' ) ) {
|
||||
if ( $this->topologyRole === self::ROLE_STREAMING_MASTER ) {
|
||||
return 0; // this is the master
|
||||
} elseif ( $this->getLBInfo( 'is static' ) ) {
|
||||
} elseif ( $this->topologyRole === self::ROLE_STATIC_CLONE ) {
|
||||
return 0; // static dataset
|
||||
}
|
||||
|
||||
|
|
@ -4814,14 +4827,18 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
}
|
||||
|
||||
/**
|
||||
* @return string|bool Reason this DB is read-only or false if it is not
|
||||
* @return array|bool Tuple of (read-only reason, "role" or "lb") or false if it is not
|
||||
*/
|
||||
protected function getReadOnlyReason() {
|
||||
$reason = $this->getLBInfo( 'readOnlyReason' );
|
||||
if ( $this->topologyRole === self::ROLE_STREAMING_REPLICA ) {
|
||||
return [ 'Server is configured as a read-only replica database.', 'role' ];
|
||||
} elseif ( $this->topologyRole === self::ROLE_STATIC_CLONE ) {
|
||||
return [ 'Server is configured as a read-only static clone database.', 'role' ];
|
||||
}
|
||||
|
||||
$reason = $this->getLBInfo( self::LB_READ_ONLY_REASON );
|
||||
if ( is_string( $reason ) ) {
|
||||
return $reason;
|
||||
} elseif ( $this->getLBInfo( 'replica' ) ) {
|
||||
return "Server is configured in the role of a read-only replica database.";
|
||||
return [ $reason, 'lb' ];
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -770,7 +770,7 @@ abstract class DatabaseMysqlBase extends Database {
|
|||
'mysql',
|
||||
'master-info',
|
||||
// Using one key for all cluster replica DBs is preferable
|
||||
$this->getLBInfo( 'clusterMasterHost' ) ?: $this->getServer()
|
||||
$this->topologyRootMaster ?? $this->getServer()
|
||||
);
|
||||
$fname = __METHOD__;
|
||||
|
||||
|
|
@ -849,7 +849,7 @@ abstract class DatabaseMysqlBase extends Database {
|
|||
throw new InvalidArgumentException( "Position not an instance of MySQLMasterPos" );
|
||||
}
|
||||
|
||||
if ( $this->getLBInfo( 'is static' ) === true ) {
|
||||
if ( $this->topologyRole === self::ROLE_STATIC_CLONE ) {
|
||||
$this->queryLogger->debug(
|
||||
"Bypassed replication wait; database has a static dataset",
|
||||
$this->getLogContext( [ 'method' => __METHOD__, 'raw_pos' => $pos ] )
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ class DatabasePostgres extends Database {
|
|||
$this->password = $password;
|
||||
|
||||
$connectVars = [
|
||||
// pg_connect() user $user as the default database. Since a database is required,
|
||||
// then pick a "don't care" database that is more likely to exist than that one.
|
||||
// A database must be specified in order to connect to Postgres. If $dbName is not
|
||||
// specified, then use the standard "postgres" database that should exist by default.
|
||||
'dbname' => strlen( $dbName ) ? $dbName : 'postgres',
|
||||
'user' => $user,
|
||||
'password' => $password
|
||||
|
|
@ -1442,7 +1442,7 @@ SQL;
|
|||
return $row ? ( strtolower( $row->default_transaction_read_only ) === 'on' ) : false;
|
||||
}
|
||||
|
||||
public static function getAttributes() {
|
||||
protected static function getAttributes() {
|
||||
return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,21 +63,21 @@ class DatabaseSqlite extends Database {
|
|||
* - dbDirectory : directory containing the DB and the lock file directory
|
||||
* - dbFilePath : use this to force the path of the DB file
|
||||
* - trxMode : one of (deferred, immediate, exclusive)
|
||||
* @param array $p
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct( array $p ) {
|
||||
if ( isset( $p['dbFilePath'] ) ) {
|
||||
$this->dbPath = $p['dbFilePath'];
|
||||
if ( !strlen( $p['dbname'] ) ) {
|
||||
$p['dbname'] = self::generateDatabaseName( $this->dbPath );
|
||||
public function __construct( array $params ) {
|
||||
if ( isset( $params['dbFilePath'] ) ) {
|
||||
$this->dbPath = $params['dbFilePath'];
|
||||
if ( !strlen( $params['dbname'] ) ) {
|
||||
$params['dbname'] = self::generateDatabaseName( $this->dbPath );
|
||||
}
|
||||
} elseif ( isset( $p['dbDirectory'] ) ) {
|
||||
$this->dbDir = $p['dbDirectory'];
|
||||
} elseif ( isset( $params['dbDirectory'] ) ) {
|
||||
$this->dbDir = $params['dbDirectory'];
|
||||
}
|
||||
|
||||
parent::__construct( $p );
|
||||
parent::__construct( $params );
|
||||
|
||||
$this->trxMode = strtoupper( $p['trxMode'] ?? '' );
|
||||
$this->trxMode = strtoupper( $params['trxMode'] ?? '' );
|
||||
|
||||
$lockDirectory = $this->getLockFileDirectory();
|
||||
if ( $lockDirectory !== null ) {
|
||||
|
|
@ -91,7 +91,10 @@ class DatabaseSqlite extends Database {
|
|||
}
|
||||
|
||||
protected static function getAttributes() {
|
||||
return [ self::ATTR_DB_LEVEL_LOCKING => true ];
|
||||
return [
|
||||
self::ATTR_DB_IS_FILE => true,
|
||||
self::ATTR_DB_LEVEL_LOCKING => true
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -148,6 +151,8 @@ class DatabaseSqlite extends Database {
|
|||
throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
|
||||
}
|
||||
|
||||
$this->server = 'localhost';
|
||||
|
||||
$attributes = [];
|
||||
if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
|
||||
// Persistent connections can avoid some schema index reading overhead.
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ interface IDatabase {
|
|||
const DBO_IGNORE = 4;
|
||||
/** @var int Automatically start a transaction before running a query if none is active */
|
||||
const DBO_TRX = 8;
|
||||
/** @var int Use DBO_TRX in non-CLI mode */
|
||||
/** @var int Join load balancer transaction rounds (which control DBO_TRX) in non-CLI mode */
|
||||
const DBO_DEFAULT = 16;
|
||||
/** @var int Use DB persistent connections if possible */
|
||||
const DBO_PERSISTENT = 32;
|
||||
|
|
@ -129,6 +129,20 @@ interface IDatabase {
|
|||
/** @var bool Parameter to unionQueries() for UNION DISTINCT */
|
||||
const UNION_DISTINCT = false;
|
||||
|
||||
/** @var string Field for getLBInfo()/setLBInfo() */
|
||||
const LB_TRX_ROUND_ID = 'trxRoundId';
|
||||
/** @var string Field for getLBInfo()/setLBInfo() */
|
||||
const LB_READ_ONLY_REASON = 'readOnlyReason';
|
||||
|
||||
/** @var string Master server than can stream OLTP updates to replica servers */
|
||||
const ROLE_STREAMING_MASTER = 'streaming-master';
|
||||
/** @var string Replica server that streams OLTP updates from the master server */
|
||||
const ROLE_STREAMING_REPLICA = 'streaming-replica';
|
||||
/** @var string Replica server of a static dataset that does not get OLTP updates */
|
||||
const ROLE_STATIC_CLONE = 'static-clone';
|
||||
/** @var string Unknown replication topology role */
|
||||
const ROLE_UNKNOWN = 'unknown';
|
||||
|
||||
/**
|
||||
* Get a human-readable string describing the current software version
|
||||
*
|
||||
|
|
@ -138,6 +152,22 @@ interface IDatabase {
|
|||
*/
|
||||
public function getServerInfo();
|
||||
|
||||
/**
|
||||
* Get the replication topology role of this server
|
||||
*
|
||||
* @return string One of the class ROLE_* constants
|
||||
* @since 1.34
|
||||
*/
|
||||
public function getTopologyRole();
|
||||
|
||||
/**
|
||||
* Get the host (or address) of the root master server for the replication topology
|
||||
*
|
||||
* @return string|null Master server name or null if not known
|
||||
* @since 1.34
|
||||
*/
|
||||
public function getTopologyRootMaster();
|
||||
|
||||
/**
|
||||
* Gets the current transaction level.
|
||||
*
|
||||
|
|
@ -202,19 +232,13 @@ interface IDatabase {
|
|||
/**
|
||||
* Set the entire array or a particular key of the managing load balancer info array
|
||||
*
|
||||
* Keys matching the IDatabase::LB_* constants are also used internally by subclasses
|
||||
*
|
||||
* @param array|string $nameOrArray The new array or the name of a key to set
|
||||
* @param array|null $value If $nameOrArray is a string, the new key value (null to unset)
|
||||
*/
|
||||
public function setLBInfo( $nameOrArray, $value = null );
|
||||
|
||||
/**
|
||||
* Set a lazy-connecting DB handle to the master DB (for replication status purposes)
|
||||
*
|
||||
* @param IDatabase $conn
|
||||
* @since 1.27
|
||||
*/
|
||||
public function setLazyMasterHandle( IDatabase $conn );
|
||||
|
||||
/**
|
||||
* Returns true if this database does an implicit order by when the column has an index
|
||||
* For example: SELECT page_title FROM page LIMIT 1
|
||||
|
|
@ -1088,8 +1112,9 @@ interface IDatabase {
|
|||
*
|
||||
* In systems like mysql/mariadb, different databases can easily be referenced on a single
|
||||
* connection merely by name, even in a single query via JOIN. On the other hand, Postgres
|
||||
* treats databases as fully separate, only allowing mechanisms like postgres_fdw to
|
||||
* effectively "mount" foreign DBs. This is true even among DBs on the same server.
|
||||
* treats databases as logically separate, with different database users, requiring special
|
||||
* mechanisms like postgres_fdw to "mount" foreign DBs. This is true even among DBs on the
|
||||
* same server. Changing the selected database via selectDomain() requires a new connection.
|
||||
*
|
||||
* @return bool
|
||||
* @since 1.29
|
||||
|
|
@ -2048,11 +2073,11 @@ interface IDatabase {
|
|||
public function setSessionOptions( array $options );
|
||||
|
||||
/**
|
||||
* Set variables to be used in sourceFile/sourceStream, in preference to the
|
||||
* ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
|
||||
* all. If it's set to false, $GLOBALS will be used.
|
||||
* Set schema variables to be used when streaming commands from SQL files or stdin
|
||||
*
|
||||
* @param bool|array $vars Mapping variable name to value.
|
||||
* Variables appear as SQL comments and are substituted by their corresponding values
|
||||
*
|
||||
* @param array|null $vars Map of (variable => value) or null to use the defaults
|
||||
*/
|
||||
public function setSchemaVars( $vars );
|
||||
|
||||
|
|
|
|||
|
|
@ -58,20 +58,11 @@ class LBFactorySimple extends LBFactory {
|
|||
parent::__construct( $conf );
|
||||
|
||||
$this->mainServers = $conf['servers'] ?? [];
|
||||
foreach ( $this->mainServers as $i => $server ) {
|
||||
if ( $i == 0 ) {
|
||||
$this->mainServers[$i]['master'] = true;
|
||||
} else {
|
||||
$this->mainServers[$i]['replica'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( ( $conf['externalClusters'] ?? [] ) as $cluster => $servers ) {
|
||||
foreach ( $servers as $index => $server ) {
|
||||
$this->externalServersByCluster[$cluster][$index] = $server;
|
||||
}
|
||||
}
|
||||
|
||||
$this->loadMonitorClass = $conf['loadMonitorClass'] ?? LoadMonitor::class;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ class LoadBalancer implements ILoadBalancer {
|
|||
private $servers;
|
||||
/** @var array[] Map of (group => server index => weight) */
|
||||
private $groupLoads;
|
||||
/** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
|
||||
private $allowLagged;
|
||||
/** @var int[] Map of (server index => seconds of lag considered "high") */
|
||||
private $maxLagByIndex;
|
||||
/** @var int Seconds to spend waiting on replica DB lag to resolve */
|
||||
private $waitTimeout;
|
||||
/** @var array The LoadMonitor configuration */
|
||||
|
|
@ -116,6 +116,8 @@ class LoadBalancer implements ILoadBalancer {
|
|||
private $readIndexByGroup = [];
|
||||
/** @var bool|DBMasterPos Replication sync position or false if not set */
|
||||
private $waitForPos;
|
||||
/** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
|
||||
private $allowLagged = false;
|
||||
/** @var bool Whether the generic reader fell back to a lagged replica DB */
|
||||
private $laggedReplicaMode = false;
|
||||
/** @var string The last DB selection or connection error */
|
||||
|
|
@ -134,6 +136,11 @@ class LoadBalancer implements ILoadBalancer {
|
|||
/** @var int|null Integer ID of the managing LBFactory instance or null if none */
|
||||
private $ownerId;
|
||||
|
||||
private static $INFO_SERVER_INDEX = 'serverIndex';
|
||||
private static $INFO_AUTOCOMMIT_ONLY = 'autoCommitOnly';
|
||||
private static $INFO_FORIEGN = 'foreign';
|
||||
private static $INFO_FOREIGN_REF_COUNT = 'foreignPoolRefCount';
|
||||
|
||||
/** @var int Warn when this many connection are held */
|
||||
const CONN_HELD_WARN_THRESHOLD = 10;
|
||||
|
||||
|
|
@ -170,6 +177,13 @@ class LoadBalancer implements ILoadBalancer {
|
|||
throw new InvalidArgumentException( 'Missing or empty "servers" parameter' );
|
||||
}
|
||||
|
||||
$localDomain = isset( $params['localDomain'] )
|
||||
? DatabaseDomain::newFromId( $params['localDomain'] )
|
||||
: DatabaseDomain::newUnspecified();
|
||||
$this->setLocalDomain( $localDomain );
|
||||
|
||||
$this->maxLag = $params['maxLag'] ?? self::MAX_LAG_DEFAULT;
|
||||
|
||||
$listKey = -1;
|
||||
$this->servers = [];
|
||||
$this->groupLoads = [ self::GROUP_GENERIC => [] ];
|
||||
|
|
@ -177,35 +191,22 @@ class LoadBalancer implements ILoadBalancer {
|
|||
if ( ++$listKey !== $i ) {
|
||||
throw new UnexpectedValueException( 'List expected for "servers" parameter' );
|
||||
}
|
||||
if ( $i == 0 ) {
|
||||
$server['master'] = true;
|
||||
} else {
|
||||
$server['replica'] = true;
|
||||
}
|
||||
$this->servers[$i] = $server;
|
||||
foreach ( ( $server['groupLoads'] ?? [] ) as $group => $ratio ) {
|
||||
$this->groupLoads[$group][$i] = $ratio;
|
||||
}
|
||||
$this->groupLoads[self::GROUP_GENERIC][$i] = $server['load'];
|
||||
$this->maxLagByIndex[$i] = $server['max lag'] ?? $this->maxLag;
|
||||
}
|
||||
|
||||
$localDomain = isset( $params['localDomain'] )
|
||||
? DatabaseDomain::newFromId( $params['localDomain'] )
|
||||
: DatabaseDomain::newUnspecified();
|
||||
$this->setLocalDomain( $localDomain );
|
||||
|
||||
$this->waitTimeout = $params['waitTimeout'] ?? self::MAX_WAIT_DEFAULT;
|
||||
|
||||
$this->conns = self::newTrackedConnectionsArray();
|
||||
$this->waitForPos = false;
|
||||
$this->allowLagged = false;
|
||||
|
||||
if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
|
||||
$this->readOnlyReason = $params['readOnlyReason'];
|
||||
}
|
||||
|
||||
$this->maxLag = $params['maxLag'] ?? self::MAX_LAG_DEFAULT;
|
||||
|
||||
$this->loadMonitorConfig = $params['loadMonitor'] ?? [ 'class' => 'LoadMonitorNull' ];
|
||||
$this->loadMonitorConfig += [ 'lagWarnThreshold' => $this->maxLag ];
|
||||
|
||||
|
|
@ -774,7 +775,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
$autocommit &&
|
||||
(
|
||||
// Connection is transaction round aware
|
||||
!$candidateConn->getLBInfo( 'autoCommitOnly' ) ||
|
||||
!$candidateConn->getLBInfo( self::$INFO_AUTOCOMMIT_ONLY ) ||
|
||||
// Some sort of error left a transaction open?
|
||||
$candidateConn->trxLevel()
|
||||
)
|
||||
|
|
@ -900,12 +901,12 @@ class LoadBalancer implements ILoadBalancer {
|
|||
if (
|
||||
$serverIndex === $this->getWriterIndex() &&
|
||||
$this->getLaggedReplicaMode( $domain ) &&
|
||||
!is_string( $conn->getLBInfo( 'readOnlyReason' ) )
|
||||
!is_string( $conn->getLBInfo( $conn::LB_READ_ONLY_REASON ) )
|
||||
) {
|
||||
$reason = ( $this->getExistingReaderIndex( self::GROUP_GENERIC ) >= 0 )
|
||||
? 'The database is read-only until replication lag decreases.'
|
||||
: 'The database is read-only until replica database servers becomes reachable.';
|
||||
$conn->setLBInfo( 'readOnlyReason', $reason );
|
||||
$conn->setLBInfo( $conn::LB_READ_ONLY_REASON, $reason );
|
||||
}
|
||||
|
||||
return $conn;
|
||||
|
|
@ -965,15 +966,15 @@ class LoadBalancer implements ILoadBalancer {
|
|||
} else {
|
||||
$readOnlyReason = false;
|
||||
}
|
||||
$conn->setLBInfo( 'readOnlyReason', $readOnlyReason );
|
||||
$conn->setLBInfo( $conn::LB_READ_ONLY_REASON, $readOnlyReason );
|
||||
}
|
||||
|
||||
return $conn;
|
||||
}
|
||||
|
||||
public function reuseConnection( IDatabase $conn ) {
|
||||
$serverIndex = $conn->getLBInfo( 'serverIndex' );
|
||||
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
|
||||
$serverIndex = $conn->getLBInfo( self::$INFO_SERVER_INDEX );
|
||||
$refCount = $conn->getLBInfo( self::$INFO_FOREIGN_REF_COUNT );
|
||||
if ( $serverIndex === null || $refCount === null ) {
|
||||
return; // non-foreign connection; no domain-use tracking to update
|
||||
} elseif ( $conn instanceof DBConnRef ) {
|
||||
|
|
@ -990,7 +991,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
return; // DBConnRef handle probably survived longer than the LoadBalancer
|
||||
}
|
||||
|
||||
if ( $conn->getLBInfo( 'autoCommitOnly' ) ) {
|
||||
if ( $conn->getLBInfo( self::$INFO_AUTOCOMMIT_ONLY ) ) {
|
||||
$connFreeKey = self::KEY_FOREIGN_FREE_NOROUND;
|
||||
$connInUseKey = self::KEY_FOREIGN_INUSE_NOROUND;
|
||||
} else {
|
||||
|
|
@ -1007,7 +1008,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
"Connection $serverIndex/$domain mismatched; it may have already been freed" );
|
||||
}
|
||||
|
||||
$conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
|
||||
$conn->setLBInfo( self::$INFO_FOREIGN_REF_COUNT, --$refCount );
|
||||
if ( $refCount <= 0 ) {
|
||||
$this->conns[$connFreeKey][$serverIndex][$domain] = $conn;
|
||||
unset( $this->conns[$connInUseKey][$serverIndex][$domain] );
|
||||
|
|
@ -1072,47 +1073,45 @@ class LoadBalancer implements ILoadBalancer {
|
|||
*
|
||||
* @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
|
||||
*
|
||||
* @param int $i Server index
|
||||
* @param int $i Specific server index
|
||||
* @param int $flags Class CONN_* constant bitfield
|
||||
* @return Database
|
||||
* @throws InvalidArgumentException When the server index is invalid
|
||||
* @throws UnexpectedValueException When the DB domain of the connection is corrupted
|
||||
*/
|
||||
private function getLocalConnection( $i, $flags = 0 ) {
|
||||
$autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
|
||||
// Connection handles required to be in auto-commit mode use a separate connection
|
||||
// pool since the main pool is effected by implicit and explicit transaction rounds
|
||||
$autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
|
||||
|
||||
$connKey = $autoCommit ? self::KEY_LOCAL_NOROUND : self::KEY_LOCAL;
|
||||
|
||||
if ( isset( $this->conns[$connKey][$i][0] ) ) {
|
||||
$conn = $this->conns[$connKey][$i][0];
|
||||
} else {
|
||||
// Open a new connection
|
||||
$server = $this->getServerInfoStrict( $i );
|
||||
$server['serverIndex'] = $i;
|
||||
$server['autoCommitOnly'] = $autoCommit;
|
||||
$conn = $this->reallyOpenConnection( $server, $this->localDomain );
|
||||
$host = $this->getServerName( $i );
|
||||
$conn = $this->reallyOpenConnection(
|
||||
$i,
|
||||
$this->localDomain,
|
||||
[ self::$INFO_AUTOCOMMIT_ONLY => $autoCommit ]
|
||||
);
|
||||
if ( $conn->isOpen() ) {
|
||||
$this->connLogger->debug(
|
||||
__METHOD__ . ": connected to database $i at '$host'." );
|
||||
$this->connLogger->debug( __METHOD__ . ": opened new connection for $i" );
|
||||
$this->conns[$connKey][$i][0] = $conn;
|
||||
} else {
|
||||
$this->connLogger->warning(
|
||||
__METHOD__ . ": failed to connect to database $i at '$host'." );
|
||||
$this->connLogger->warning( __METHOD__ . ": connection error for $i" );
|
||||
$this->errorConnection = $conn;
|
||||
$conn = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Final sanity check to make sure the right domain is selected
|
||||
// Sanity check to make sure that the right domain is selected
|
||||
if (
|
||||
$conn instanceof IDatabase &&
|
||||
!$this->localDomain->isCompatible( $conn->getDomainID() )
|
||||
) {
|
||||
throw new UnexpectedValueException(
|
||||
"Got connection to '{$conn->getDomainID()}', " .
|
||||
"but expected local domain ('{$this->localDomain}')" );
|
||||
"but expected local domain ('{$this->localDomain}')"
|
||||
);
|
||||
}
|
||||
|
||||
return $conn;
|
||||
|
|
@ -1134,7 +1133,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
*
|
||||
* @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
|
||||
*
|
||||
* @param int $i Server index
|
||||
* @param int $i Specific server index
|
||||
* @param string $domain Domain ID to open
|
||||
* @param int $flags Class CONN_* constant bitfield
|
||||
* @return Database|bool Returns false on connection error
|
||||
|
|
@ -1144,10 +1143,9 @@ class LoadBalancer implements ILoadBalancer {
|
|||
*/
|
||||
private function getForeignConnection( $i, $domain, $flags = 0 ) {
|
||||
$domainInstance = DatabaseDomain::newFromId( $domain );
|
||||
$autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
|
||||
// Connection handles required to be in auto-commit mode use a separate connection
|
||||
// pool since the main pool is effected by implicit and explicit transaction rounds
|
||||
$autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
|
||||
|
||||
if ( $autoCommit ) {
|
||||
$connFreeKey = self::KEY_FOREIGN_FREE_NOROUND;
|
||||
$connInUseKey = self::KEY_FOREIGN_INUSE_NOROUND;
|
||||
|
|
@ -1200,33 +1198,35 @@ class LoadBalancer implements ILoadBalancer {
|
|||
}
|
||||
|
||||
if ( !$conn ) {
|
||||
// Open a new connection
|
||||
$server = $this->getServerInfoStrict( $i );
|
||||
$server['serverIndex'] = $i;
|
||||
$server['foreignPoolRefCount'] = 0;
|
||||
$server['foreign'] = true;
|
||||
$server['autoCommitOnly'] = $autoCommit;
|
||||
$conn = $this->reallyOpenConnection( $server, $domainInstance );
|
||||
if ( !$conn->isOpen() ) {
|
||||
$this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" );
|
||||
$this->errorConnection = $conn;
|
||||
$conn = false;
|
||||
} else {
|
||||
$conn = $this->reallyOpenConnection(
|
||||
$i,
|
||||
$domainInstance,
|
||||
[
|
||||
self::$INFO_AUTOCOMMIT_ONLY => $autoCommit,
|
||||
self::$INFO_FORIEGN => true,
|
||||
self::$INFO_FOREIGN_REF_COUNT => 0
|
||||
]
|
||||
);
|
||||
if ( $conn->isOpen() ) {
|
||||
// Note that if $domain is an empty string, getDomainID() might not match it
|
||||
$this->conns[$connInUseKey][$i][$conn->getDomainID()] = $conn;
|
||||
$this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
|
||||
} else {
|
||||
$this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" );
|
||||
$this->errorConnection = $conn;
|
||||
$conn = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $conn instanceof IDatabase ) {
|
||||
// Final sanity check to make sure the right domain is selected
|
||||
// Sanity check to make sure that the right domain is selected
|
||||
if ( !$domainInstance->isCompatible( $conn->getDomainID() ) ) {
|
||||
throw new UnexpectedValueException(
|
||||
"Got connection to '{$conn->getDomainID()}', but expected '$domain'" );
|
||||
}
|
||||
// Increment reference count
|
||||
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
|
||||
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
|
||||
$refCount = $conn->getLBInfo( self::$INFO_FOREIGN_REF_COUNT );
|
||||
$conn->setLBInfo( self::$INFO_FOREIGN_REF_COUNT, $refCount + 1 );
|
||||
}
|
||||
|
||||
return $conn;
|
||||
|
|
@ -1254,75 +1254,65 @@ class LoadBalancer implements ILoadBalancer {
|
|||
*
|
||||
* Returns a Database object whether or not the connection was successful.
|
||||
*
|
||||
* @param array $server
|
||||
* @param int $i Specific server index
|
||||
* @param DatabaseDomain $domain Domain the connection is for, possibly unspecified
|
||||
* @param array $lbInfo Additional information for setLBInfo()
|
||||
* @return Database
|
||||
* @throws DBAccessError
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
|
||||
protected function reallyOpenConnection( $i, DatabaseDomain $domain, array $lbInfo ) {
|
||||
if ( $this->disabled ) {
|
||||
throw new DBAccessError();
|
||||
}
|
||||
|
||||
if ( $domain->getDatabase() === null ) {
|
||||
// The database domain does not specify a DB name and some database systems require a
|
||||
// valid DB specified on connection. The $server configuration array contains a default
|
||||
// DB name to use for connections in such cases.
|
||||
if ( $server['type'] === 'mysql' ) {
|
||||
// For MySQL, DATABASE and SCHEMA are synonyms, connections need not specify a DB,
|
||||
// and the DB name in $server might not exist due to legacy reasons (the default
|
||||
// domain used to ignore the local LB domain, even when mismatched).
|
||||
$server['dbname'] = null;
|
||||
}
|
||||
} else {
|
||||
$server['dbname'] = $domain->getDatabase();
|
||||
}
|
||||
$server = $this->getServerInfoStrict( $i );
|
||||
|
||||
if ( $domain->getSchema() !== null ) {
|
||||
$server['schema'] = $domain->getSchema();
|
||||
}
|
||||
|
||||
// It is always possible to connect with any prefix, even the empty string
|
||||
$server['tablePrefix'] = $domain->getTablePrefix();
|
||||
|
||||
// Let the handle know what the cluster master is (e.g. "db1052")
|
||||
$masterName = $this->getServerName( $this->getWriterIndex() );
|
||||
$server['clusterMasterHost'] = $masterName;
|
||||
|
||||
$server['srvCache'] = $this->srvCache;
|
||||
// Set loggers and profilers
|
||||
$server['connLogger'] = $this->connLogger;
|
||||
$server['queryLogger'] = $this->queryLogger;
|
||||
$server['errorLogger'] = $this->errorLogger;
|
||||
$server['deprecationLogger'] = $this->deprecationLogger;
|
||||
$server['profiler'] = $this->profiler;
|
||||
$server['trxProfiler'] = $this->trxProfiler;
|
||||
// Use the same agent and PHP mode for all DB handles
|
||||
$server['cliMode'] = $this->cliMode;
|
||||
$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'] = $server['flags'] ?? IDatabase::DBO_DEFAULT;
|
||||
$server['ownerId'] = $this->id;
|
||||
|
||||
// Create a live connection object
|
||||
$conn = Database::factory( $server['type'], $server, Database::NEW_UNCONNECTED );
|
||||
$conn->setLBInfo( $server );
|
||||
$conn->setLazyMasterHandle(
|
||||
$this->getLazyConnectionRef( self::DB_MASTER, [], $conn->getDomainID() )
|
||||
$conn = Database::factory(
|
||||
$server['type'],
|
||||
array_merge( $server, [
|
||||
// Basic replication role information
|
||||
'topologyRole' => $this->getTopologyRole( $i, $server ),
|
||||
'topologicalMaster' => $this->getMasterServerName(),
|
||||
// Use the database specified in $domain (null means "none or entrypoint DB");
|
||||
// fallback to the $server default if the RDBMs is an embedded library using a
|
||||
// file on disk since there would be nothing to access to without a DB/file name.
|
||||
'dbname' => $this->getServerAttributes( $i )[Database::ATTR_DB_IS_FILE]
|
||||
? ( $domain->getDatabase() ?? $server['dbname'] ?? null )
|
||||
: $domain->getDatabase(),
|
||||
// Override the $server default schema with that of $domain if specified
|
||||
'schema' => $domain->getSchema() ?? $server['schema'] ?? null,
|
||||
// Use the table prefix specified in $domain
|
||||
'tablePrefix' => $domain->getTablePrefix(),
|
||||
// Participate in transaction rounds if $server does not specify otherwise
|
||||
'flags' => $this->initConnFlags( $server['flags'] ?? IDatabase::DBO_DEFAULT ),
|
||||
// Inject the PHP execution mode and the agent string
|
||||
'cliMode' => $this->cliMode,
|
||||
'agent' => $this->agent,
|
||||
'ownerId' => $this->id,
|
||||
// Inject object and callback dependencies
|
||||
'lazyMasterHandle' => $this->getLazyConnectionRef(
|
||||
self::DB_MASTER,
|
||||
[],
|
||||
$domain->getId()
|
||||
),
|
||||
'srvCache' => $this->srvCache,
|
||||
'connLogger' => $this->connLogger,
|
||||
'queryLogger' => $this->queryLogger,
|
||||
'errorLogger' => $this->errorLogger,
|
||||
'deprecationLogger' => $this->deprecationLogger,
|
||||
'profiler' => $this->profiler,
|
||||
'trxProfiler' => $this->trxProfiler
|
||||
] ),
|
||||
Database::NEW_UNCONNECTED
|
||||
);
|
||||
// Attach load balancer information to the handle
|
||||
$conn->setLBInfo( [ self::$INFO_SERVER_INDEX => $i ] + $lbInfo );
|
||||
// Set alternative table/index names before any queries can be issued
|
||||
$conn->setTableAliases( $this->tableAliases );
|
||||
$conn->setIndexAliases( $this->indexAliases );
|
||||
|
||||
try {
|
||||
$conn->initConnection();
|
||||
++$this->connectionCounter;
|
||||
} catch ( DBConnectionError $e ) {
|
||||
// ignore; let the DB handle the logging
|
||||
}
|
||||
|
||||
if ( $server['serverIndex'] === $this->getWriterIndex() ) {
|
||||
// Account for any active transaction round and listeners
|
||||
if ( $i === $this->getWriterIndex() ) {
|
||||
if ( $this->trxRoundId !== false ) {
|
||||
$this->applyTransactionRoundFlags( $conn );
|
||||
}
|
||||
|
|
@ -1331,9 +1321,22 @@ class LoadBalancer implements ILoadBalancer {
|
|||
}
|
||||
}
|
||||
|
||||
$this->lazyLoadReplicationPositions(); // session consistency
|
||||
// Make the connection handle live
|
||||
try {
|
||||
$conn->initConnection();
|
||||
++$this->connectionCounter;
|
||||
} catch ( DBConnectionError $e ) {
|
||||
// ignore; let the DB handle the logging
|
||||
}
|
||||
|
||||
// Log when many connection are made on requests
|
||||
// Try to maintain session consistency for clients that trigger write transactions
|
||||
// in a request or script and then return soon after in another request or script.
|
||||
// This requires cooperation with ChronologyProtector and the application wiring.
|
||||
if ( $conn->isOpen() ) {
|
||||
$this->lazyLoadReplicationPositions();
|
||||
}
|
||||
|
||||
// Log when many connection are made during a single request/script
|
||||
$count = $this->getCurrentConnectionCount();
|
||||
if ( $count >= self::CONN_HELD_WARN_THRESHOLD ) {
|
||||
$this->perfLogger->warning(
|
||||
|
|
@ -1341,7 +1344,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
[
|
||||
'connections' => $count,
|
||||
'dbserver' => $conn->getServer(),
|
||||
'masterdb' => $conn->getLBInfo( 'clusterMasterHost' )
|
||||
'masterdb' => $this->getMasterServerName()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -1349,6 +1352,38 @@ class LoadBalancer implements ILoadBalancer {
|
|||
return $conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $i Specific server index
|
||||
* @param array $server Server config map
|
||||
* @return string IDatabase::ROLE_* constant
|
||||
*/
|
||||
private function getTopologyRole( $i, array $server ) {
|
||||
if ( !empty( $server['is static'] ) ) {
|
||||
return IDatabase::ROLE_STATIC_CLONE;
|
||||
}
|
||||
|
||||
return ( $i === $this->getWriterIndex() )
|
||||
? IDatabase::ROLE_STREAMING_MASTER
|
||||
: IDatabase::ROLE_STREAMING_REPLICA;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see IDatabase::DBO_DEFAULT
|
||||
* @param int $flags Bit field of IDatabase::DBO_* constants from configuration
|
||||
* @return int Bit field of IDatabase::DBO_* constants to use with Database::factory()
|
||||
*/
|
||||
private function initConnFlags( $flags ) {
|
||||
if ( ( $flags & IDatabase::DBO_DEFAULT ) === IDatabase::DBO_DEFAULT ) {
|
||||
if ( $this->cliMode ) {
|
||||
$flags &= ~IDatabase::DBO_TRX;
|
||||
} else {
|
||||
$flags |= IDatabase::DBO_TRX;
|
||||
}
|
||||
}
|
||||
|
||||
return $flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that any "waitForPos" positions are loaded and available to doWait()
|
||||
*/
|
||||
|
|
@ -1529,7 +1564,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
throw new RuntimeException( 'Cannot close DBConnRef instance; it must be shareable' );
|
||||
}
|
||||
|
||||
$serverIndex = $conn->getLBInfo( 'serverIndex' );
|
||||
$serverIndex = $conn->getLBInfo( self::$INFO_SERVER_INDEX );
|
||||
foreach ( $this->conns as $type => $connsByServer ) {
|
||||
if ( !isset( $connsByServer[$serverIndex] ) ) {
|
||||
continue;
|
||||
|
|
@ -1871,7 +1906,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
* @param Database $conn
|
||||
*/
|
||||
private function applyTransactionRoundFlags( Database $conn ) {
|
||||
if ( $conn->getLBInfo( 'autoCommitOnly' ) ) {
|
||||
if ( $conn->getLBInfo( self::$INFO_AUTOCOMMIT_ONLY ) ) {
|
||||
return; // transaction rounds do not apply to these connections
|
||||
}
|
||||
|
||||
|
|
@ -1882,7 +1917,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
}
|
||||
|
||||
if ( $conn->getFlag( $conn::DBO_TRX ) ) {
|
||||
$conn->setLBInfo( 'trxRoundId', $this->trxRoundId );
|
||||
$conn->setLBInfo( $conn::LB_TRX_ROUND_ID, $this->trxRoundId );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1890,12 +1925,12 @@ class LoadBalancer implements ILoadBalancer {
|
|||
* @param Database $conn
|
||||
*/
|
||||
private function undoTransactionRoundFlags( Database $conn ) {
|
||||
if ( $conn->getLBInfo( 'autoCommitOnly' ) ) {
|
||||
if ( $conn->getLBInfo( self::$INFO_AUTOCOMMIT_ONLY ) ) {
|
||||
return; // transaction rounds do not apply to these connections
|
||||
}
|
||||
|
||||
if ( $conn->getFlag( $conn::DBO_TRX ) ) {
|
||||
$conn->setLBInfo( 'trxRoundId', null ); // remove the round ID
|
||||
$conn->setLBInfo( $conn::LB_TRX_ROUND_ID, null ); // remove the round ID
|
||||
}
|
||||
|
||||
if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
|
||||
|
|
@ -2192,20 +2227,14 @@ class LoadBalancer implements ILoadBalancer {
|
|||
* @deprecated Since 1.34 Use IDatabase::getLag() instead
|
||||
*/
|
||||
public function safeGetLag( IDatabase $conn ) {
|
||||
if ( $conn->getLBInfo( 'is static' ) ) {
|
||||
return 0; // static dataset
|
||||
} elseif ( $conn->getLBInfo( 'serverIndex' ) == $this->getWriterIndex() ) {
|
||||
return 0; // this is the master
|
||||
}
|
||||
|
||||
return $conn->getLag();
|
||||
}
|
||||
|
||||
public function waitForMasterPos( IDatabase $conn, $pos = false, $timeout = null ) {
|
||||
$timeout = max( 1, $timeout ?: $this->waitTimeout );
|
||||
|
||||
if ( $this->getServerCount() <= 1 || !$conn->getLBInfo( 'replica' ) ) {
|
||||
return true; // server is not a replica DB
|
||||
if ( $conn->getLBInfo( self::$INFO_SERVER_INDEX ) === $this->getWriterIndex() ) {
|
||||
return true; // not a replica DB server
|
||||
}
|
||||
|
||||
if ( !$pos ) {
|
||||
|
|
@ -2302,7 +2331,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
$this->forEachOpenConnection( function ( IDatabase $conn ) use ( &$domainsInUse ) {
|
||||
// Once reuseConnection() is called on a handle, its reference count goes from 1 to 0.
|
||||
// Until then, it is still in use by the caller (explicitly or via DBConnRef scope).
|
||||
if ( $conn->getLBInfo( 'foreignPoolRefCount' ) > 0 ) {
|
||||
if ( $conn->getLBInfo( self::$INFO_FOREIGN_REF_COUNT ) > 0 ) {
|
||||
$domainsInUse[] = $conn->getDomainID();
|
||||
}
|
||||
} );
|
||||
|
|
@ -2322,7 +2351,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
|
||||
// Update the prefix for all local connections...
|
||||
$this->forEachOpenConnection( function ( IDatabase $conn ) use ( $prefix ) {
|
||||
if ( !$conn->getLBInfo( 'foreign' ) ) {
|
||||
if ( !$conn->getLBInfo( self::$INFO_FORIEGN ) ) {
|
||||
$conn->tablePrefix( $prefix );
|
||||
}
|
||||
} );
|
||||
|
|
@ -2383,7 +2412,7 @@ class LoadBalancer implements ILoadBalancer {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return string Name of the master server of the relevant DB cluster (e.g. "db1052")
|
||||
*/
|
||||
private function getMasterServerName() {
|
||||
return $this->getServerName( $this->getWriterIndex() );
|
||||
|
|
|
|||
|
|
@ -37,21 +37,21 @@ class LoadBalancerSingle extends LoadBalancer {
|
|||
* - connection: An IDatabase connection object
|
||||
*/
|
||||
public function __construct( array $params ) {
|
||||
if ( !isset( $params['connection'] ) ) {
|
||||
/** @var IDatabase $conn */
|
||||
$conn = $params['connection'] ?? null;
|
||||
if ( !$conn ) {
|
||||
throw new InvalidArgumentException( "Missing 'connection' argument." );
|
||||
}
|
||||
|
||||
$this->db = $params['connection'];
|
||||
$this->db = $conn;
|
||||
|
||||
parent::__construct( [
|
||||
'servers' => [
|
||||
[
|
||||
'type' => $this->db->getType(),
|
||||
'host' => $this->db->getServer(),
|
||||
'dbname' => $this->db->getDBname(),
|
||||
'load' => 1,
|
||||
]
|
||||
],
|
||||
'servers' => [ [
|
||||
'type' => $conn->getType(),
|
||||
'host' => $conn->getServer(),
|
||||
'dbname' => $conn->getDBname(),
|
||||
'load' => 1,
|
||||
] ],
|
||||
'trxProfiler' => $params['trxProfiler'] ?? null,
|
||||
'srvCache' => $params['srvCache'] ?? null,
|
||||
'wanCache' => $params['wanCache'] ?? null,
|
||||
|
|
@ -60,7 +60,7 @@ class LoadBalancerSingle extends LoadBalancer {
|
|||
] );
|
||||
|
||||
if ( isset( $params['readOnlyReason'] ) ) {
|
||||
$this->db->setLBInfo( 'readOnlyReason', $params['readOnlyReason'] );
|
||||
$conn->setLBInfo( $conn::LB_READ_ONLY_REASON, $params['readOnlyReason'] );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ class LoadBalancerSingle extends LoadBalancer {
|
|||
) );
|
||||
}
|
||||
|
||||
protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
|
||||
protected function reallyOpenConnection( $i, DatabaseDomain $domain, array $lbInfo = [] ) {
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ use MediaWiki\Storage\SqlBlobStore;
|
|||
use MediaWiki\User\UserIdentityValue;
|
||||
use MediaWikiTestCase;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\NullLogger;
|
||||
use Revision;
|
||||
use TestUserRegistry;
|
||||
use Title;
|
||||
|
|
@ -148,7 +149,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
|
|||
->getMock();
|
||||
|
||||
$lb->method( 'reallyOpenConnection' )->willReturnCallback(
|
||||
function ( array $server, $dbNameOverride ) {
|
||||
function () use ( $server ) {
|
||||
return $this->getDatabaseMock( $server );
|
||||
}
|
||||
);
|
||||
|
|
@ -207,12 +208,15 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
|
|||
'variables' => [],
|
||||
'schema' => '',
|
||||
'cliMode' => true,
|
||||
'topologyRole' => Database::ROLE_STREAMING_MASTER,
|
||||
'topologicalMaster' => null,
|
||||
'agent' => '',
|
||||
'load' => 100,
|
||||
'srvCache' => new HashBagOStuff(),
|
||||
'profiler' => null,
|
||||
'trxProfiler' => new TransactionProfiler(),
|
||||
'connLogger' => new \Psr\Log\NullLogger(),
|
||||
'queryLogger' => new \Psr\Log\NullLogger(),
|
||||
'connLogger' => new NullLogger(),
|
||||
'queryLogger' => new NullLogger(),
|
||||
'errorLogger' => function () {
|
||||
},
|
||||
'deprecationLogger' => function () {
|
||||
|
|
|
|||
|
|
@ -184,6 +184,8 @@ class DatabasePostgresTest extends MediaWikiTestCase {
|
|||
* @covers \Wikimedia\Rdbms\DatabasePostgres::getAttributes
|
||||
*/
|
||||
public function testAttributes() {
|
||||
$this->assertTrue( DatabasePostgres::getAttributes()[Database::ATTR_SCHEMAS_AS_TABLE_GROUPS] );
|
||||
$this->assertTrue(
|
||||
Database::attributesFromType( 'postgres' )[Database::ATTR_SCHEMAS_AS_TABLE_GROUPS]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,8 +52,10 @@ class DatabaseTestHelper extends Database {
|
|||
'schema' => null,
|
||||
'tablePrefix' => '',
|
||||
'flags' => 0,
|
||||
'cliMode' => $opts['cliMode'] ?? true,
|
||||
'cliMode' => true,
|
||||
'agent' => '',
|
||||
'topologyRole' => null,
|
||||
'topologicalMaster' => null,
|
||||
'srvCache' => new HashBagOStuff(),
|
||||
'profiler' => null,
|
||||
'trxProfiler' => new TransactionProfiler(),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
* @copyright © 2013 Wikimedia Foundation Inc.
|
||||
*/
|
||||
|
||||
use Wikimedia\AtEase\AtEase;
|
||||
use Wikimedia\Rdbms\IDatabase;
|
||||
use Wikimedia\Rdbms\IMaintainableDatabase;
|
||||
use Wikimedia\Rdbms\LBFactory;
|
||||
|
|
@ -102,10 +103,12 @@ class LBFactoryTest extends MediaWikiTestCase {
|
|||
$lb = $factory->getMainLB();
|
||||
|
||||
$dbw = $lb->getConnection( DB_MASTER );
|
||||
$this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
|
||||
$this->assertEquals(
|
||||
$dbw::ROLE_STREAMING_MASTER, $dbw->getTopologyRole(), 'master shows as master' );
|
||||
|
||||
$dbr = $lb->getConnection( DB_REPLICA );
|
||||
$this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
|
||||
$this->assertEquals(
|
||||
$dbr::ROLE_STREAMING_MASTER, $dbw->getTopologyRole(), 'replica shows as replica' );
|
||||
|
||||
$this->assertSame( 'my_test_wiki', $factory->resolveDomainID( 'my_test_wiki' ) );
|
||||
$this->assertSame( $factory->getLocalDomainID(), $factory->resolveDomainID( false ) );
|
||||
|
|
@ -146,18 +149,22 @@ class LBFactoryTest extends MediaWikiTestCase {
|
|||
$lb = $factory->getMainLB();
|
||||
|
||||
$dbw = $lb->getConnection( DB_MASTER );
|
||||
$this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
|
||||
$this->assertEquals(
|
||||
$dbw::ROLE_STREAMING_MASTER, $dbw->getTopologyRole(), 'master shows as master' );
|
||||
$this->assertEquals(
|
||||
( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
|
||||
$dbw->getLBInfo( 'clusterMasterHost' ),
|
||||
$dbw->getTopologyRootMaster(),
|
||||
'cluster master set' );
|
||||
|
||||
$dbr = $lb->getConnection( DB_REPLICA );
|
||||
$this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
|
||||
$this->assertEquals(
|
||||
$dbr::ROLE_STREAMING_REPLICA, $dbr->getTopologyRole(), 'replica shows as replica' );
|
||||
|
||||
$this->assertEquals(
|
||||
( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
|
||||
$dbr->getLBInfo( 'clusterMasterHost' ),
|
||||
'cluster master set' );
|
||||
$dbr->getTopologyRootMaster(),
|
||||
'cluster master set'
|
||||
);
|
||||
|
||||
$factory->shutdown();
|
||||
}
|
||||
|
|
@ -166,10 +173,12 @@ class LBFactoryTest extends MediaWikiTestCase {
|
|||
$factory = $this->newLBFactoryMultiLBs();
|
||||
|
||||
$dbw = $factory->getMainLB()->getConnection( DB_MASTER );
|
||||
$this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
|
||||
$this->assertEquals(
|
||||
$dbw::ROLE_STREAMING_MASTER, $dbw->getTopologyRole(), 'master shows as master' );
|
||||
|
||||
$dbr = $factory->getMainLB()->getConnection( DB_REPLICA );
|
||||
$this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
|
||||
$this->assertEquals(
|
||||
$dbr::ROLE_STREAMING_REPLICA, $dbr->getTopologyRole(), 'replica shows as replica' );
|
||||
|
||||
// Destructor should trigger without round stage errors
|
||||
unset( $factory );
|
||||
|
|
@ -473,7 +482,7 @@ class LBFactoryTest extends MediaWikiTestCase {
|
|||
unset( $db );
|
||||
|
||||
/** @var IMaintainableDatabase $db */
|
||||
$db = $lb->getConnection( DB_MASTER, [], '' );
|
||||
$db = $lb->getConnection( DB_MASTER, [], $lb::DOMAIN_ANY );
|
||||
|
||||
$this->assertSame(
|
||||
'',
|
||||
|
|
@ -552,7 +561,7 @@ class LBFactoryTest extends MediaWikiTestCase {
|
|||
);
|
||||
$lb = $factory->getMainLB();
|
||||
/** @var IMaintainableDatabase $db */
|
||||
$db = $lb->getConnection( DB_MASTER, [], '' );
|
||||
$db = $lb->getConnection( DB_MASTER, [], $lb::DOMAIN_ANY );
|
||||
|
||||
$this->assertSame( '', $db->getDomainID(), "Null domain used" );
|
||||
|
||||
|
|
@ -620,16 +629,16 @@ class LBFactoryTest extends MediaWikiTestCase {
|
|||
);
|
||||
$lb = $factory->getMainLB();
|
||||
/** @var IDatabase $db */
|
||||
$db = $lb->getConnection( DB_MASTER, [], '' );
|
||||
$db = $lb->getConnection( DB_MASTER, [], $lb::DOMAIN_ANY );
|
||||
|
||||
\Wikimedia\suppressWarnings();
|
||||
AtEase::suppressWarnings();
|
||||
try {
|
||||
$this->assertFalse( $db->selectDB( 'garbage-db' ) );
|
||||
$this->assertFalse( $db->selectDomain( 'garbagedb' ) );
|
||||
$this->fail( "No error thrown." );
|
||||
} catch ( \Wikimedia\Rdbms\DBQueryError $e ) {
|
||||
$this->assertRegExp( '/[\'"]garbage-db[\'"]/', $e->getMessage() );
|
||||
$this->assertRegExp( '/[\'"]garbagedb[\'"]/', $e->getMessage() );
|
||||
}
|
||||
\Wikimedia\restoreWarnings();
|
||||
AtEase::restoreWarnings();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -648,12 +657,12 @@ class LBFactoryTest extends MediaWikiTestCase {
|
|||
);
|
||||
$lb = $factory->getMainLB();
|
||||
|
||||
if ( !$lb->getConnection( DB_MASTER )->databasesAreIndependent() ) {
|
||||
$this->markTestSkipped( "Not applicable per databasesAreIndependent()" );
|
||||
if ( !$factory->getMainLB()->getServerAttributes( 0 )[Database::ATTR_DB_IS_FILE] ) {
|
||||
$this->markTestSkipped( "Not applicable per ATTR_DB_IS_FILE" );
|
||||
}
|
||||
|
||||
/** @var IDatabase $db */
|
||||
$lb->getConnection( DB_MASTER, [], '' );
|
||||
$this->assertNotNull( $lb->getConnection( DB_MASTER, [], $lb::DOMAIN_ANY ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -677,7 +686,7 @@ class LBFactoryTest extends MediaWikiTestCase {
|
|||
}
|
||||
|
||||
$db = $lb->getConnection( DB_MASTER );
|
||||
$db->selectDB( 'garbage-db' );
|
||||
$db->selectDomain( 'garbage-db' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
use PHPUnit\Framework\Constraint\StringContains;
|
||||
use Wikimedia\Rdbms\DBError;
|
||||
use Wikimedia\Rdbms\DatabaseDomain;
|
||||
|
|
@ -91,12 +90,15 @@ class LoadBalancerTest extends MediaWikiTestCase {
|
|||
|
||||
$dbw = $lb->getConnection( DB_MASTER );
|
||||
$this->assertTrue( $called );
|
||||
$this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
|
||||
$this->assertEquals(
|
||||
$dbw::ROLE_STREAMING_MASTER, $dbw->getTopologyRole(), 'master shows as master'
|
||||
);
|
||||
$this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
|
||||
$this->assertWriteAllowed( $dbw );
|
||||
|
||||
$dbr = $lb->getConnection( DB_REPLICA );
|
||||
$this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
|
||||
$this->assertEquals(
|
||||
$dbr::ROLE_STREAMING_MASTER, $dbr->getTopologyRole(), 'DB_REPLICA also gets the master' );
|
||||
$this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
|
||||
|
||||
if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
|
||||
|
|
@ -155,23 +157,26 @@ class LoadBalancerTest extends MediaWikiTestCase {
|
|||
}
|
||||
|
||||
$dbw = $lb->getConnection( DB_MASTER );
|
||||
$this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
|
||||
$this->assertEquals(
|
||||
$dbw::ROLE_STREAMING_MASTER, $dbw->getTopologyRole(), 'master shows as master' );
|
||||
$this->assertEquals(
|
||||
( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
|
||||
$dbw->getLBInfo( 'clusterMasterHost' ),
|
||||
'cluster master set' );
|
||||
$dbw->getTopologyRootMaster(),
|
||||
'cluster master set'
|
||||
);
|
||||
$this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
|
||||
$this->assertWriteAllowed( $dbw );
|
||||
|
||||
$dbr = $lb->getConnection( DB_REPLICA );
|
||||
$this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
|
||||
$this->assertEquals(
|
||||
$dbr::ROLE_STREAMING_REPLICA, $dbr->getTopologyRole(), 'replica shows as replica' );
|
||||
$this->assertTrue( $dbr->isReadOnly(), 'replica shows as replica' );
|
||||
$this->assertEquals(
|
||||
( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
|
||||
$dbr->getLBInfo( 'clusterMasterHost' ),
|
||||
'cluster master set' );
|
||||
$dbr->getTopologyRootMaster(),
|
||||
'cluster master set'
|
||||
);
|
||||
$this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
|
||||
$this->assertWriteForbidden( $dbr );
|
||||
$this->assertEquals( $dbr->getLBInfo( 'serverIndex' ), $lb->getReaderIndex() );
|
||||
|
||||
if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
|
||||
|
|
@ -575,7 +580,23 @@ class LoadBalancerTest extends MediaWikiTestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef()
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
|
||||
* @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
|
||||
*/
|
||||
public function testForbiddenWritesNoRef() {
|
||||
// Simulate web request with DBO_TRX
|
||||
$lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_TRX ] );
|
||||
|
||||
$dbr = $lb->getConnection( DB_REPLICA );
|
||||
$this->assertTrue( $dbr->isReadOnly(), 'replica shows as replica' );
|
||||
$dbr->delete( 'some_table', [ 'id' => 57634126 ], __METHOD__ );
|
||||
|
||||
$lb->closeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef()
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
|
||||
*/
|
||||
public function testDBConnRefReadsMasterAndReplicaRoles() {
|
||||
|
|
@ -602,7 +623,7 @@ class LoadBalancerTest extends MediaWikiTestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef()
|
||||
* @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
|
||||
*/
|
||||
public function testDBConnRefWritesReplicaRole() {
|
||||
|
|
@ -614,7 +635,7 @@ class LoadBalancerTest extends MediaWikiTestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef()
|
||||
* @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
|
||||
*/
|
||||
public function testDBConnRefWritesReplicaRoleIndex() {
|
||||
|
|
@ -626,7 +647,19 @@ class LoadBalancerTest extends MediaWikiTestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef()
|
||||
* @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
|
||||
*/
|
||||
public function testLazyDBConnRefWritesReplicaRoleIndex() {
|
||||
$lb = $this->newMultiServerLocalLoadBalancer();
|
||||
|
||||
$rConn = $lb->getLazyConnectionRef( 1 );
|
||||
|
||||
$rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef()
|
||||
* @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
|
||||
*/
|
||||
public function testDBConnRefWritesReplicaRoleInsert() {
|
||||
|
|
|
|||
|
|
@ -370,6 +370,8 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
|
|||
$db->method( 'getMasterServerInfo' )
|
||||
->willReturn( [ 'serverId' => 172, 'asOf' => time() ] );
|
||||
|
||||
$db->setLBInfo( 'replica', true );
|
||||
|
||||
// Fake the current time.
|
||||
list( $nowSecFrac, $nowSec ) = explode( ' ', microtime() );
|
||||
$now = (float)$nowSec + (float)$nowSecFrac;
|
||||
|
|
|
|||
Loading…
Reference in a new issue