rdbms: cleanup getServer() and connection parameter fields in Database

Make getServer() always return a string, as documented, even with new
Database::NEW_UNCONNECTED handles that have yet to call open(). If the
'host' parameter to __construct() is ''/null, getServer() now returns
'localhost' instead of null. This avoids problems like fatal errors in
calls to TransactionProfiler::recordConnection().

Use Database constants for "connectionParams" field keys for better
static analysis.

Also:
* Add Database::getServerName() method that returns "readable" server
  names in the style of LoadBalancer::getServerName(). Note that the
  "hostName" field is already passed in from LoadBalancer.
* Migrate most getServer() callers to getServerName() for easier
  debugging and more readable logging.
* Also, normalize Database/LoadBalancer SPI logging context to use
  "db_server" and reduce logging code duplication in LoadBalancer.

Bug: T277056
Change-Id: I00ed4049ebb45edab1ea07561c47e226a423ea3b
This commit is contained in:
Aaron Schulz 2021-03-09 17:38:23 -08:00 committed by Krinkle
parent f413a6210b
commit 99d5d2e8cc
18 changed files with 244 additions and 181 deletions

View file

@ -177,7 +177,7 @@ class LegacyLogger extends AbstractLogger {
$context['sql'],
$context['method'],
$context['runtime'],
$context['db_host']
$context['db_server']
);
}

View file

@ -464,6 +464,10 @@ class DBConnRef implements IDatabase {
return $this->__call( __FUNCTION__, func_get_args() );
}
public function getServerName() {
return $this->__call( __FUNCTION__, func_get_args() );
}
public function addQuotes( $s ) {
return $this->__call( __FUNCTION__, func_get_args() );
}

View file

@ -84,6 +84,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
protected $user;
/** @var string|null Password used to establish the current connection */
protected $password;
/** @var string|null Readible name or host/IP of the database server */
protected $serverName;
/** @var bool Whether this PHP instance is for a CLI script */
protected $cliMode;
/** @var string Agent name for query profiling */
@ -92,7 +94,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
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 */
/** @var array<string,mixed> Connection parameters used by initConnection() and open() */
protected $connectionParams;
/** @var string[]|int[]|float[] SQL variables values to use for all new connections */
protected $connectionVariables;
@ -260,6 +262,19 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
self::DBO_DEBUG | self::DBO_NOBUFFER | self::DBO_TRX | self::DBO_DDLMODE
);
/** Hostname or IP address to use on all connections */
protected const CONN_HOST = 'host';
/** Database server username to use on all connections */
protected const CONN_USER = 'user';
/** Database server password to use on all connections */
protected const CONN_PASSWORD = 'password';
/** Database name to use on initial connection */
protected const CONN_INITIAL_DB = 'dbname';
/** Schema name to use on initial connection */
protected const CONN_INITIAL_SCHEMA = 'schema';
/** Table prefix to use on initial connection */
protected const CONN_INITIAL_TABLE_PREFIX = 'tablePrefix';
/**
* @note exceptions for missing libraries/drivers should be thrown in initConnection()
* @stable to call
@ -267,20 +282,20 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
*/
public function __construct( array $params ) {
$this->connectionParams = [
'host' => ( isset( $params['host'] ) && $params['host'] !== '' )
self::CONN_HOST => ( isset( $params['host'] ) && $params['host'] !== '' )
? $params['host']
: null,
'user' => ( isset( $params['user'] ) && $params['user'] !== '' )
self::CONN_USER => ( isset( $params['user'] ) && $params['user'] !== '' )
? $params['user']
: null,
'dbname' => ( isset( $params['dbname'] ) && $params['dbname'] !== '' )
self::CONN_INITIAL_DB => ( isset( $params['dbname'] ) && $params['dbname'] !== '' )
? $params['dbname']
: null,
'schema' => ( isset( $params['schema'] ) && $params['schema'] !== '' )
self::CONN_INITIAL_SCHEMA => ( isset( $params['schema'] ) && $params['schema'] !== '' )
? $params['schema']
: null,
'password' => is_string( $params['password'] ) ? $params['password'] : null,
'tablePrefix' => (string)$params['tablePrefix']
self::CONN_PASSWORD => is_string( $params['password'] ) ? $params['password'] : null,
self::CONN_INITIAL_TABLE_PREFIX => (string)$params['tablePrefix']
];
$this->lbInfo = $params['lbInfo'] ?? [];
@ -290,8 +305,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
$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->serverName = $params['serverName'];
$this->topologyRole = $params['topologyRole'];
$this->topologyRootMaster = $params['topologicalMaster'];
$this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'] ?? 10000;
$this->srvCache = $params['srvCache'];
@ -339,27 +355,27 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
*/
protected function doInitConnection() {
$this->open(
$this->connectionParams['host'],
$this->connectionParams['user'],
$this->connectionParams['password'],
$this->connectionParams['dbname'],
$this->connectionParams['schema'],
$this->connectionParams['tablePrefix']
$this->connectionParams[self::CONN_HOST],
$this->connectionParams[self::CONN_USER],
$this->connectionParams[self::CONN_PASSWORD],
$this->connectionParams[self::CONN_INITIAL_DB],
$this->connectionParams[self::CONN_INITIAL_SCHEMA],
$this->connectionParams[self::CONN_INITIAL_TABLE_PREFIX]
);
}
/**
* Open a new connection to the database (closing any existing one)
*
* @param string|null $server Database server host
* @param string|null $user Database user name
* @param string|null $password Database user password
* @param string|null $dbName Database name
* @param string|null $server Server host/address and optional port {@see connectionParams}
* @param string|null $user User name {@see connectionParams}
* @param string|null $password User password {@see connectionParams}
* @param string|null $db Database name
* @param string|null $schema Database schema name
* @param string $tablePrefix Table prefix
* @throws DBConnectionError
*/
abstract protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix );
abstract protected function open( $server, $user, $password, $db, $schema, $tablePrefix );
/**
* Construct a Database subclass instance given a database type and parameters
@ -368,7 +384,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
*
* @param string $type A possible DB type (sqlite, mysql, postgres,...)
* @param array $params Parameter map with keys:
* - host : The hostname of the DB server
* - host : The hostname or IP address of the database server
* - user : The name of the database user the client operates under
* - password : The password for the database user
* - dbname : The name of the database to use where queries do not specify one.
@ -388,7 +404,8 @@ 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.
* - serverName : Optional readable name for the database server.
* - topologyRole: Optional IDatabase::ROLE_* constant for the database 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
@ -433,6 +450,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
'cliMode' => ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ),
'agent' => '',
'ownerId' => null,
'serverName' => null,
'topologyRole' => null,
'topologicalMaster' => null,
// Objects and callbacks
@ -919,9 +937,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
protected function getLogContext( array $extras = [] ) {
return array_merge(
[
'db_server' => $this->server,
'db_server' => $this->getServerName(),
'db_name' => $this->getDBname(),
'db_user' => $this->user,
'db_user' => $this->connectionParams[self::CONN_USER],
],
$extras
);
@ -1394,7 +1412,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
if ( $this->trxLevel() && !$this->trxDoneWrites ) {
$this->trxDoneWrites = true;
$this->trxProfiler->transactionWritingIn(
$this->getServer(),
$this->getServerName(),
$this->getDomainID(),
$this->trxShortId
);
@ -1452,14 +1470,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
// Avoid the overhead of logging calls unless debug mode is enabled
if ( $this->getFlag( self::DBO_DEBUG ) ) {
$this->queryLogger->debug(
"{method} [{runtime}s] {db_host}: {sql}",
[
"{method} [{runtime}s] {db_server}: {sql}",
$this->getLogContext( [
'method' => $fname,
'db_host' => $this->getServer(),
'sql' => $sql,
'domain' => $this->getDomainID(),
'runtime' => round( $queryRuntime, 3 )
]
] )
);
}
@ -1624,7 +1641,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
// @note: leave trxRecurringCallbacks in place
if ( $this->trxDoneWrites ) {
$this->trxProfiler->transactionWritingOut(
$this->getServer(),
$this->getServerName(),
$this->getDomainID(),
$oldTrxShortId,
$this->pendingWriteQueryDuration( self::ESTIMATE_TOTAL ),
@ -2847,7 +2864,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
}
public function getServer() {
return $this->server;
return $this->connectionParams[self::CONN_HOST] ?? null;
}
public function getServerName() {
return $this->serverName ?? $this->getServer();
}
/**
@ -4769,7 +4790,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
if ( $this->trxDoneWrites ) {
$this->lastWriteTime = microtime( true );
$this->trxProfiler->transactionWritingOut(
$this->getServer(),
$this->getServerName(),
$this->getDomainID(),
$oldTrxShortId,
$writeTime,
@ -4833,7 +4854,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
$writeTime = $this->pingAndCalculateLastTrxApplyTime();
if ( $this->trxDoneWrites ) {
$this->trxProfiler->transactionWritingOut(
$this->getServer(),
$this->getServerName(),
$this->getDomainID(),
$oldTrxShortId,
$writeTime,
@ -5015,9 +5036,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
try {
$this->open(
$this->server,
$this->user,
$this->password,
$this->connectionParams[self::CONN_HOST],
$this->connectionParams[self::CONN_USER],
$this->connectionParams[self::CONN_PASSWORD],
$this->currentDomain->getDatabase(),
$this->currentDomain->getSchema(),
$this->tablePrefix()
@ -5026,18 +5047,15 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
$ok = true;
$this->connLogger->warning(
$fname . ': lost connection to {dbserver}; reconnected',
[
'dbserver' => $this->getServer(),
'exception' => new RuntimeException()
]
$fname . ': lost connection to {db_server}; reconnected',
$this->getLogContext( [ 'exception' => new RuntimeException() ] )
);
} catch ( DBConnectionError $e ) {
$ok = false;
$this->connLogger->error(
$fname . ': lost connection to {dbserver} permanently',
[ 'dbserver' => $this->getServer() ]
$fname . ': lost connection to {db_server} permanently',
$this->getLogContext( [ 'exception' => new RuntimeException() ] )
);
}
@ -5150,6 +5168,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
*
* Callers should avoid using this method while a transaction is active
*
* @see getLag()
*
* @stable to override
* @return float|int|false Database replication lag in seconds or false on error
* @throws DBError
@ -5835,9 +5855,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
$this->trxSectionCancelCallbacks = []; // don't copy
$this->handleSessionLossPreconnect(); // no trx or locks anymore
$this->open(
$this->server,
$this->user,
$this->password,
$this->connectionParams[self::CONN_HOST],
$this->connectionParams[self::CONN_USER],
$this->connectionParams[self::CONN_PASSWORD],
$this->currentDomain->getDatabase(),
$this->currentDomain->getSchema(),
$this->tablePrefix()

View file

@ -52,7 +52,11 @@ abstract class DatabaseMysqlBase extends Database {
protected $sslCAFile;
/** @var string|null */
protected $sslCAPath;
/** @var string[]|null */
/**
* Open SSL cipher list string
* @see https://www.openssl.org/docs/man1.0.2/man1/ciphers.html
* @var string|null
*/
protected $sslCiphers;
/** @var string sql_mode value to send on connection */
protected $sqlMode;
@ -116,20 +120,16 @@ abstract class DatabaseMysqlBase extends Database {
return 'mysql';
}
protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
$this->close( __METHOD__ );
if ( $schema !== null ) {
throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
}
$this->server = $server;
$this->user = $user;
$this->password = $password;
$this->installErrorHandler();
try {
$this->conn = $this->mysqlConnect( $this->server, $dbName );
$this->conn = $this->mysqlConnect( $server, $user, $password, $db );
} catch ( RuntimeException $e ) {
$this->restoreErrorHandler();
throw $this->newExceptionAfterConnectError( $e->getMessage() );
@ -142,7 +142,7 @@ abstract class DatabaseMysqlBase extends Database {
try {
$this->currentDomain = new DatabaseDomain(
strlen( $dbName ) ? $dbName : null,
strlen( $db ) ? $db : null,
null,
$tablePrefix
);
@ -216,12 +216,14 @@ abstract class DatabaseMysqlBase extends Database {
/**
* Open a connection to a MySQL server
*
* @param string $realServer
* @param string|null $dbName
* @param string|null $server
* @param string|null $user
* @param string|null $password
* @param string|null $db
* @return mixed|null Driver connection handle
* @throws DBConnectionError
*/
abstract protected function mysqlConnect( $realServer, $dbName );
abstract protected function mysqlConnect( $server, $user, $password, $db );
/**
* @param IResultWrapper|resource $res
@ -427,7 +429,7 @@ abstract class DatabaseMysqlBase extends Database {
$error = $this->mysqlError();
}
if ( $error ) {
$error .= ' (' . $this->server . ')';
$error .= ' (' . $this->getServerName() . ')';
}
return $error;
@ -766,7 +768,7 @@ abstract class DatabaseMysqlBase extends Database {
'mysql',
'master-info',
// Using one key for all cluster replica DBs is preferable
$this->topologyRootMaster ?? $this->getServer()
$this->topologyRootMaster ?? $this->getServerName()
);
$fname = __METHOD__;
@ -828,7 +830,7 @@ abstract class DatabaseMysqlBase extends Database {
return parent::getApproximateLagStatus();
}
$key = $this->srvCache->makeGlobalKey( 'mysql-lag', $this->getServer() );
$key = $this->srvCache->makeGlobalKey( 'mysql-lag', $this->getServerName() );
$approxLag = $this->srvCache->get( $key );
if ( !$approxLag ) {
$approxLag = parent::getApproximateLagStatus();
@ -1027,7 +1029,7 @@ abstract class DatabaseMysqlBase extends Database {
protected function getServerId() {
$fname = __METHOD__;
return $this->srvCache->getWithSetCallback(
$this->srvCache->makeGlobalKey( 'mysql-server-id', $this->getServer() ),
$this->srvCache->makeGlobalKey( 'mysql-server-id', $this->getServerName() ),
self::SERVER_ID_CACHE_TTL,
function () use ( $fname ) {
$flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
@ -1044,7 +1046,7 @@ abstract class DatabaseMysqlBase extends Database {
protected function getServerUUID() {
$fname = __METHOD__;
return $this->srvCache->getWithSetCallback(
$this->srvCache->makeGlobalKey( 'mysql-server-uuid', $this->getServer() ),
$this->srvCache->makeGlobalKey( 'mysql-server-uuid', $this->getServerName() ),
self::SERVER_ID_CACHE_TTL,
function () use ( $fname ) {
$flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
@ -1142,7 +1144,7 @@ abstract class DatabaseMysqlBase extends Database {
$fname = __METHOD__;
return $cache->getWithSetCallback(
$cache->makeGlobalKey( 'mysql-server-version', $this->getServer() ),
$cache->makeGlobalKey( 'mysql-server-version', $this->getServerName() ),
$cache::TTL_HOUR,
function () use ( $fname ) {
// Not using mysql_get_server_info() or similar for consistency: in the handshake,

View file

@ -50,12 +50,14 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
/**
* @param string $realServer
* @param string|null $dbName
* @param string|null $server
* @param string|null $user
* @param string|null $password
* @param string|null $db
* @return mysqli|null
* @throws DBConnectionError
*/
protected function mysqlConnect( $realServer, $dbName ) {
protected function mysqlConnect( $server, $user, $password, $db ) {
if ( !function_exists( 'mysqli_init' ) ) {
throw $this->newExceptionAfterConnectError(
"MySQLi functions missing, have you compiled PHP with the --with-mysqli option?"
@ -72,25 +74,27 @@ class DatabaseMysqli extends DatabaseMysqlBase {
// We need to parse the port or socket path out of $realServer
$port = null;
$socket = null;
$hostAndPort = IPUtils::splitHostAndPort( $realServer );
$hostAndPort = IPUtils::splitHostAndPort( $server );
if ( $hostAndPort ) {
$realServer = $hostAndPort[0];
if ( $hostAndPort[1] ) {
$port = $hostAndPort[1];
}
} elseif ( substr_count( $realServer, ':/' ) == 1 ) {
} elseif ( substr_count( $server, ':/' ) == 1 ) {
// If we have a colon slash instead of a colon and a port number
// after the ip or hostname, assume it's the Unix domain socket path
list( $realServer, $socket ) = explode( ':', $realServer, 2 );
list( $realServer, $socket ) = explode( ':', $server, 2 );
} else {
$realServer = $server;
}
$mysqli = mysqli_init();
// Make affectedRows() for UPDATE reflect the number of matching rows, regardless
// of whether any column values changed. This is what callers want to know and is
// consistent with what Postgres, SQLite, and SQL Server return.
$connFlags = MYSQLI_CLIENT_FOUND_ROWS;
$flags = MYSQLI_CLIENT_FOUND_ROWS;
if ( $this->getFlag( self::DBO_SSL ) ) {
$connFlags |= MYSQLI_CLIENT_SSL;
$flags |= MYSQLI_CLIENT_SSL;
$mysqli->ssl_set(
$this->sslKeyPath,
$this->sslCertPath,
@ -100,7 +104,7 @@ class DatabaseMysqli extends DatabaseMysqlBase {
);
}
if ( $this->getFlag( self::DBO_COMPRESS ) ) {
$connFlags |= MYSQLI_CLIENT_COMPRESS;
$flags |= MYSQLI_CLIENT_COMPRESS;
}
if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
$realServer = 'p:' . $realServer;
@ -115,19 +119,9 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
$mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
if ( $mysqli->real_connect(
$realServer,
$this->user,
$this->password,
$dbName,
$port,
$socket,
$connFlags
) ) {
return $mysqli;
}
$ok = $mysqli->real_connect( $realServer, $user, $password, $db, $port, $socket, $flags );
return null;
return $ok ? $mysqli : null;
}
/**

View file

@ -37,8 +37,8 @@ class DatabasePostgres extends Database {
private $coreSchema;
/** @var string */
private $tempSchema;
/** @var string[] Map of (reserved table name => alternate table name) */
private $keywordTableMap = [];
/** @var null|string[] Map of (reserved table name => alternate table name) */
private $keywordTableMap;
/** @var float|string */
private $numericVersion;
@ -48,6 +48,7 @@ class DatabasePostgres extends Database {
/**
* @see Database::__construct()
* @param array $params Additional parameters include:
* - port: A port to append to the hostname
* - keywordTableMap : Map of reserved table names to alternative table names to use
* This is is deprecated since 1.37. Reserved identifiers should be quoted where necessary,
*/
@ -87,7 +88,7 @@ class DatabasePostgres extends Database {
return false;
}
protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
if ( !function_exists( 'pg_connect' ) ) {
throw $this->newExceptionAfterConnectError(
"Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
@ -98,14 +99,10 @@ class DatabasePostgres extends Database {
$this->close( __METHOD__ );
$this->server = $server;
$this->user = $user;
$this->password = $password;
$connectVars = [
// 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',
'dbname' => strlen( $db ) ? $db : 'postgres',
'user' => $user,
'password' => $password
];
@ -153,7 +150,7 @@ class DatabasePostgres extends Database {
);
}
$this->determineCoreSchema( $schema );
$this->currentDomain = new DatabaseDomain( $dbName, $schema, $tablePrefix );
$this->currentDomain = new DatabaseDomain( $db, $schema, $tablePrefix );
} catch ( RuntimeException $e ) {
throw $this->newExceptionAfterConnectError( $e->getMessage() );
}
@ -177,9 +174,9 @@ class DatabasePostgres extends Database {
// Postgres doesn't support selectDB in the same way MySQL does.
// So if the DB name doesn't match the open connection, open a new one
$this->open(
$this->server,
$this->user,
$this->password,
$this->connectionParams[self::CONN_HOST],
$this->connectionParams[self::CONN_USER],
$this->connectionParams[self::CONN_PASSWORD],
$domain->getDatabase(),
$domain->getSchema(),
$domain->getTablePrefix()

View file

@ -133,7 +133,7 @@ class DatabaseSqlite extends Database {
return 'sqlite';
}
protected function open( $server, $user, $pass, $dbName, $schema, $tablePrefix ) {
protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
$this->close( __METHOD__ );
// Note that for SQLite, $server, $user, and $pass are ignored
@ -145,7 +145,7 @@ class DatabaseSqlite extends Database {
if ( $this->dbPath !== null ) {
$path = $this->dbPath;
} elseif ( $this->dbDir !== null ) {
$path = self::generateFileName( $this->dbDir, $dbName );
$path = self::generateFileName( $this->dbDir, $db );
} else {
throw $this->newExceptionAfterConnectError( "DB path or directory required" );
}
@ -161,8 +161,6 @@ 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.
@ -184,7 +182,7 @@ class DatabaseSqlite extends Database {
throw $this->newExceptionAfterConnectError( $e->getMessage() );
}
$this->currentDomain = new DatabaseDomain( $dbName, null, $tablePrefix );
$this->currentDomain = new DatabaseDomain( $db, null, $tablePrefix );
try {
$flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_NO_RETRY;

View file

@ -369,7 +369,7 @@ interface IDatabase {
public function getDomainID();
/**
* Get the type of the DBMS (e.g. "mysql", "sqlite")
* Get the RDBMS type of the server (e.g. "mysql", "sqlite")
*
* @return string
*/
@ -1217,17 +1217,27 @@ interface IDatabase {
public function selectDomain( $domain );
/**
* Get the current DB name
* Get the current database name; null if there isn't one
*
* @return string|null
*/
public function getDBname();
/**
* Get the server hostname or IP address
* @return string
* Get the hostname or IP address of the server
*
* @return string|null
*/
public function getServer();
/**
* Get the readable name for the server
*
* @return string Readable server name, falling back to the hostname or IP address
* @since 1.36
*/
public function getServerName();
/**
* Escape and quote a raw value string for use in a SQL query
*

View file

@ -38,7 +38,7 @@ class LBFactoryMulti extends LBFactory {
/** @var LoadBalancer[] Map of (external cluster => tracked LoadBalancer) */
private $externalLBs = [];
/** @var string[] Map of (hostname => IP address) */
/** @var string[] Map of (server name => IP address) */
private $hostsByServerName;
/** @var string[] Map of (database name => main section) */
private $sectionsByDB;
@ -48,7 +48,7 @@ class LBFactoryMulti extends LBFactory {
private $groupLoadsByDB;
/** @var int[][] Map of (external cluster => server name => load ratio) */
private $externalLoadsByCluster;
/** @var array Server config map ("host", "hostName", "load", and "groupLoads" are ignored) */
/** @var array Server config map ("host", "serverName", "load", and "groupLoads" ignored) */
private $serverTemplate;
/** @var array Server config map overriding "serverTemplate" for all external servers */
private $externalTemplateOverrides;
@ -82,7 +82,7 @@ class LBFactoryMulti extends LBFactory {
*
* @see LBFactory::__construct()
* @param array $conf Additional parameters include:
* - hostsByName: map of (hostname => IP address). [optional]
* - hostsByName: map of (server name => IP address). [optional]
* - sectionsByDB: map of (database => main section). The database name "DEFAULT" is
* interpeted as a catch-all for all databases not otherwise mentioned. [optional]
* - sectionLoads: map of (main section => server name => load ratio); the first host
@ -92,7 +92,7 @@ class LBFactoryMulti extends LBFactory {
* - groupLoadsByDB: map of (database => group => server name => load ratio) map. [optional]
* - externalLoads: map of (cluster => server name => load ratio) map. [optional]
* - serverTemplate: server config map for Database::factory().
* Note that "host", "hostName" and "load" entries will be overridden by
* Note that "host", "serverName" and "load" entries will be overridden by
* "groupLoadsBySection" and "hostsByName". [optional]
* - externalTemplateOverrides: server config map overrides for external stores;
* respects the override precedence described above. [optional]
@ -296,7 +296,7 @@ class LBFactoryMulti extends LBFactory {
$this->templateOverridesByServer[$serverName] ?? [],
[
'host' => $this->hostsByServerName[$serverName] ?? $serverName,
'hostName' => $serverName,
'serverName' => $serverName,
'load' => $load,
'groupLoads' => $groupLoadsByServerName[$serverName] ?? []
]

View file

@ -477,31 +477,34 @@ interface ILoadBalancer {
public function hasStreamingReplicaServers();
/**
* Get the name of the server with the specified index
* Get the readable name of the server with the specified index
*
* @param int $i
* @return string Readable name if available or IP/host otherwise
* @param int $i Specific server index
* @return string Readable server name, falling back to the hostname or IP address
*/
public function getServerName( $i );
/**
* Return the server info structure for a given index or false if the index is invalid.
* @param int $i
* @return array|bool
* Return the server configuration map for the server with the specified index
*
* @param int $i Specific server index
* @return array|false Server configuration map; false if the index is invalid
* @since 1.31
*/
public function getServerInfo( $i );
/**
* Get DB type of the server with the specified index
* Get the RDBMS type of the server with the specified index (e.g. "mysql", "sqlite")
*
* @param int $i
* @param int $i Specific server index
* @return string One of (mysql,postgres,sqlite,...) or "unknown" for bad indexes
* @since 1.30
*/
public function getServerType( $i );
/**
* Get basic attributes of the server with the specified index without connecting
*
* @param int $i Specific server index
* @return array (Database::ATTRIBUTE_* constant => value) for all such constants
* @since 1.31

View file

@ -234,9 +234,10 @@ class LoadBalancer implements ILoadBalancer {
$this->deprecationLogger = $params['deprecationLogger'] ?? static function ( $msg ) {
trigger_error( $msg, E_USER_DEPRECATED );
};
foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
$this->$key = $params[$key] ?? new NullLogger();
}
$this->replLogger = $params['replLogger'] ?? new NullLogger();
$this->connLogger = $params['connLogger'] ?? new NullLogger();
$this->queryLogger = $params['queryLogger'] ?? new NullLogger();
$this->perfLogger = $params['perfLogger'] ?? new NullLogger();
$this->clusterName = $params['clusterName'] ?? null;
$this->profiler = $params['profiler'] ?? null;
@ -454,17 +455,18 @@ class LoadBalancer implements ILoadBalancer {
# Constrain that futher by $maxLag argument
$maxServerLag = min( $maxServerLag, $maxLag );
$host = $this->getServerName( $i );
$srvName = $this->getServerName( $i );
if ( $lag === false && !is_infinite( $maxServerLag ) ) {
$this->replLogger->debug(
__METHOD__ .
": server {dbserver} is not replicating?", [ 'dbserver' => $host ] );
__METHOD__ . ": server {db_server} is not replicating?",
[ 'db_server' => $srvName ]
);
unset( $loads[$i] );
} elseif ( $lag > $maxServerLag ) {
$this->replLogger->debug(
__METHOD__ .
": server {dbserver} has {lag} seconds of lag (>= {maxlag})",
[ 'dbserver' => $host, 'lag' => $lag, 'maxlag' => $maxServerLag ]
": server {db_server} has {lag} seconds of lag (>= {maxlag})",
[ 'db_server' => $srvName, 'lag' => $lag, 'maxlag' => $maxServerLag ]
);
unset( $loads[$i] );
}
@ -845,8 +847,8 @@ class LoadBalancer implements ILoadBalancer {
if ( !$conn->isOpen() ) {
$this->connLogger->warning(
__METHOD__ .
": pooled DB handle for {dbserver} (#$i) has no open connection.",
[ 'dbserver' => $conn->getServer() ]
": pooled DB handle for {db_server} (#$i) has no open connection.",
$this->getConnLogContext( $conn )
);
continue; // some sort of error occurred?
@ -863,8 +865,8 @@ class LoadBalancer implements ILoadBalancer {
// Some sort of bug left a transaction open
$this->connLogger->warning(
__METHOD__ .
": pooled DB handle for {dbserver} (#$i) has a pending transaction.",
[ 'dbserver' => $conn->getServer() ]
": pooled DB handle for {db_server} (#$i) has a pending transaction.",
$this->getConnLogContext( $conn )
);
continue;
@ -889,8 +891,8 @@ class LoadBalancer implements ILoadBalancer {
$timeout = max( 1, intval( $timeout ?: $this->waitTimeout ) );
// Check if we already know that the DB has reached this point
$server = $this->getServerName( $index );
$key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server, 'v1' );
$srvName = $this->getServerName( $index );
$key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $srvName, 'v1' );
/** @var DBMasterPos $knownReachedPos */
$knownReachedPos = $this->srvCache->get( $key );
if (
@ -899,8 +901,8 @@ class LoadBalancer implements ILoadBalancer {
) {
$this->replLogger->debug(
__METHOD__ .
": replica DB {dbserver} known to be caught up (pos >= $knownReachedPos).",
[ 'dbserver' => $server ]
": replica DB {db_server} known to be caught up (pos >= $knownReachedPos).",
[ 'db_server' => $srvName ]
);
return true;
@ -917,8 +919,8 @@ class LoadBalancer implements ILoadBalancer {
$conn = $this->getServerConnection( $index, self::DOMAIN_ANY, $flags );
if ( !$conn ) {
$this->replLogger->warning(
__METHOD__ . ': failed to connect to {dbserver}',
[ 'dbserver' => $server ]
__METHOD__ . ': failed to connect to {db_server}',
[ 'db_server' => $srvName ]
);
return false;
@ -930,8 +932,8 @@ class LoadBalancer implements ILoadBalancer {
$this->replLogger->info(
__METHOD__ .
': waiting for replica DB {dbserver} to catch up...',
[ 'dbserver' => $server ]
': waiting for replica DB {db_server} to catch up...',
$this->getConnLogContext( $conn )
);
$result = $conn->masterPosWait( $this->waitForPos, $timeout );
@ -1003,7 +1005,7 @@ class LoadBalancer implements ILoadBalancer {
// Profile any new connections caused by this method
if ( $this->connectionCounter > $priorConnectionsMade ) {
$this->trxProfiler->recordConnection(
$conn->getServer(),
$conn->getServerName(),
$conn->getDBname(),
self::fieldHasBit( $flags, self::CONN_INTENT_WRITABLE )
);
@ -1435,12 +1437,14 @@ class LoadBalancer implements ILoadBalancer {
if ( $count >= self::CONN_HELD_WARN_THRESHOLD ) {
$this->perfLogger->warning(
__METHOD__ . ": {connections}+ connections made (master={masterdb})",
[
'connections' => $count,
'dbserver' => $conn->getServer(),
'masterdb' => $this->getMasterServerName(),
'db_domain' => $domain->getId()
]
$this->getConnLogContext(
$conn,
[
'connections' => $count,
'masterdb' => $this->getMasterServerName(),
'db_domain' => $domain->getId()
]
)
);
}
@ -1501,13 +1505,13 @@ class LoadBalancer implements ILoadBalancer {
];
if ( $conn instanceof IDatabase ) {
$context['db_server'] = $conn->getServer();
$srvName = $conn->getServerName();
$this->connLogger->warning(
__METHOD__ . ": connection error: {last_error} ({db_server})",
$context
$this->getConnLogContext( $conn, $context )
);
$error = $conn->lastError() ?: $this->lastError;
throw new DBConnectionError( $conn, "$error ({$context['db_server']})" );
throw new DBConnectionError( $conn, "{$error} ($srvName)" );
} else {
// No last connection, probably due to all servers being too busy
$this->connLogger->error(
@ -1574,7 +1578,7 @@ class LoadBalancer implements ILoadBalancer {
}
public function getServerName( $i ) {
$name = $this->servers[$i]['hostName'] ?? ( $this->servers[$i]['host'] ?? '' );
$name = $this->servers[$i]['serverName'] ?? ( $this->servers[$i]['host'] ?? '' );
return ( $name != '' ) ? $name : 'localhost';
}
@ -1646,8 +1650,8 @@ class LoadBalancer implements ILoadBalancer {
$scope = ScopedCallback::newScopedIgnoreUserAbort();
}
$this->forEachOpenConnection( function ( IDatabase $conn ) use ( $fname ) {
$host = $conn->getServer();
$this->connLogger->debug( "$fname: closing connection to database '$host'." );
$srvName = $conn->getServerName();
$this->connLogger->debug( "$fname: closing connection to database '$srvName'." );
$conn->close( $fname, $this->id );
} );
@ -1665,7 +1669,7 @@ class LoadBalancer implements ILoadBalancer {
throw new RuntimeException( 'Database handle is missing server index' );
}
$host = $this->getServerName( $serverIndex );
$srvName = $this->getServerName( $serverIndex );
$domain = $conn->getDomainID();
$found = false;
@ -1680,13 +1684,13 @@ class LoadBalancer implements ILoadBalancer {
if ( !$found ) {
$this->connLogger->warning(
__METHOD__ .
": got orphaned connection to database $serverIndex/$domain at '$host'."
": got orphaned connection to database $serverIndex/$domain at '$srvName'."
);
}
$this->connLogger->debug(
__METHOD__ .
": closing connection to database $serverIndex/$domain at '$host'."
": closing connection to database $serverIndex/$domain at '$srvName'."
);
$conn->close( __METHOD__ );
@ -1815,7 +1819,7 @@ class LoadBalancer implements ILoadBalancer {
$conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
} catch ( DBError $e ) {
( $this->errorLogger )( $e );
$failures[] = "{$conn->getServer()}: {$e->getMessage()}";
$failures[] = "{$conn->getServerName()}: {$e->getMessage()}";
}
}
);
@ -1882,11 +1886,10 @@ class LoadBalancer implements ILoadBalancer {
$fnames = implode( ', ', $conn->pendingWriteAndCallbackCallers() );
$this->queryLogger->warning(
"$fname: found writes pending ($fnames).",
[
'db_server' => $conn->getServer(),
'db_domain' => $conn->getDomainID(),
'exception' => new RuntimeException()
]
$this->getConnLogContext(
$conn,
[ 'exception' => new RuntimeException() ]
)
);
} elseif ( $conn->trxLevel() ) {
// A callback from another handle read from this one and DBO_TRX is set,
@ -2149,7 +2152,7 @@ class LoadBalancer implements ILoadBalancer {
// Note that table prefixes are not related to server-side read-only mode
$key = $this->srvCache->makeGlobalKey(
'rdbms-server-readonly',
$conn->getServer(),
$conn->getServerName(),
$conn->getDBname(),
$conn->dbSchema()
);
@ -2383,12 +2386,8 @@ class LoadBalancer implements ILoadBalancer {
} else {
$ok = false; // something is misconfigured
$this->replLogger->error(
__METHOD__ . ': could not get master pos for {dbserver}',
[
'dbserver' => $conn->getServer(),
'db_domain' => $conn->getDomainID(),
'exception' => new RuntimeException(),
]
__METHOD__ . ': could not get master pos for {db_server}',
$this->getConnLogContext( $conn, [ 'exception' => new RuntimeException() ] )
);
}
@ -2514,6 +2513,23 @@ class LoadBalancer implements ILoadBalancer {
return ( ( $flags & $bit ) === $bit );
}
/**
* Create a log context to pass to PSR-3 logger functions.
*
* @param IDatabase $conn
* @param array $extras Additional data to add to context
* @return array
*/
protected function getConnLogContext( IDatabase $conn, array $extras = [] ) {
return array_merge(
[
'db_server' => $conn->getServerName(),
'db_domain' => $conn->getDomainID()
],
$extras
);
}
public function __destruct() {
// Avoid connection leaks for sanity
$this->disable( __METHOD__, $this->ownerId );

View file

@ -272,7 +272,7 @@ class LoadMonitor implements ILoadMonitor {
$this->replLogger->warning(
"Server {dbserver} has {lag} seconds of lag (>= {maxlag})",
[
'dbserver' => $host,
'db_server' => $host,
'lag' => $lagTimes[$i],
'maxlag' => $this->lagWarnThreshold
]

View file

@ -85,7 +85,7 @@ class MwSql extends Maintenance {
$db = $lb->getMaintenanceConnectionRef( $index, [], $wiki );
if ( $replicaDB != '' && $db->getLBInfo( 'master' ) !== null ) {
$this->fatalError( "The server selected ({$db->getServer()}) is not a replica DB." );
$this->fatalError( "Server {$db->getServerName()} is not a replica DB." );
}
if ( $index === DB_PRIMARY ) {

View file

@ -202,6 +202,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiIntegrationTestCase {
'topologyRole' => Database::ROLE_STREAMING_MASTER,
'topologicalMaster' => null,
'agent' => '',
'serverName' => '*dummy*',
'load' => 100,
'srvCache' => new HashBagOStuff(),
'profiler' => null,

View file

@ -58,6 +58,7 @@ class DatabaseTestHelper extends Database {
'flags' => 0,
'cliMode' => true,
'agent' => '',
'serverName' => null,
'topologyRole' => null,
'topologicalMaster' => null,
'srvCache' => new HashBagOStuff(),
@ -176,8 +177,7 @@ class DatabaseTestHelper extends Database {
return 'test';
}
public function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
$this->server = 'localhost';
public function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
$this->conn = (object)[ 'test' ];
return true;

View file

@ -40,7 +40,7 @@ class LoadBalancerTest extends MediaWikiIntegrationTestCase {
return [
'host' => $wgDBserver,
'hostName' => 'testhost',
'serverName' => 'testhost',
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'user' => $wgDBuser,

View file

@ -46,6 +46,7 @@ class DatabaseSqliteTest extends \MediaWikiIntegrationTestCase {
'tablePrefix' => '',
'cliMode' => true,
'agent' => 'unit-tests',
'serverName' => null,
'flags' => DBO_DEFAULT,
'variables' => [],
'profiler' => null,

View file

@ -31,7 +31,13 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
*/
public function testFactory() {
$m = Database::NEW_UNCONNECTED; // no-connect mode
$p = [ 'host' => 'localhost', 'user' => 'me', 'password' => 'myself', 'dbname' => 'i' ];
$p = [
'host' => 'localhost',
'serverName' => 'localdb',
'user' => 'me',
'password' => 'myself',
'dbname' => 'i'
];
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'mysqli', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySqli', $p, $m ) );
@ -43,6 +49,10 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
$this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
$x = $p + [ 'dbDirectory' => 'some/file' ];
$this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
$conn = Database::factory( 'sqlite', $p, $m );
$this->assertEquals( 'localhost', $conn->getServer() );
$this->assertEquals( 'localdb', $conn->getServerName() );
}
public static function provideAddQuotes() {
@ -436,17 +446,23 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
'closeConnection',
'dataSeek',
'doQuery',
'fetchObject', 'fetchRow',
'fieldInfo', 'fieldName',
'getSoftwareLink', 'getServerVersion',
'fetchObject',
'fetchRow',
'fieldInfo',
'fieldName',
'getSoftwareLink',
'getServerVersion',
'getType',
'indexInfo',
'insertId',
'lastError', 'lastErrno',
'numFields', 'numRows',
'lastError',
'lastErrno',
'numFields',
'numRows',
'open',
'strencode',
'tableExists'
'tableExists',
'getServer'
];
$db = $this->getMockBuilder( Database::class )
->disableOriginalConstructor()
@ -465,7 +481,8 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
$wdb->deprecationLogger = static function ( $msg ) {
};
$wdb->currentDomain = DatabaseDomain::newUnspecified();
$wdb->server = 'localhost';
$db->method( 'getServer' )->willReturn( '*dummy*' );
return $db;
}