return;
}
- // XXX: we really want the default database ID...
- $storeWiki = $storeWiki ?: wfWikiID();
+ $storeWiki = $storeWiki ?: $this->loadBalancer->getLocalDomainID();
+ // @FIXME: when would getDomainID() be false here?
$dbWiki = $dbWiki ?: wfWikiID();
if ( $dbWiki === $storeWiki ) {
if ( !isset( $infos[$tableName] ) ) {
throw new \InvalidArgumentException( "Invalid table name \$tableName" );
}
- if ( $wiki === wfWikiID() ) {
+ if ( $wiki === $this->lbFactory->getLocalDomainID() ) {
$wiki = false;
}
+
if ( isset( $this->stores[$tableName][$wiki] ) ) {
return $this->stores[$tableName][$wiki];
}
use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabaseDomain;
use Wikimedia\Rdbms\Blob;
use Wikimedia\Rdbms\ResultWrapper;
use Wikimedia\Rdbms\DBConnectionError;
use Wikimedia\Rdbms\DBUnexpectedError;
+use Wikimedia\Rdbms\DBExpectedError;
/**
* @ingroup Database
return false;
}
- function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
global $wgDBOracleDRCP;
+
if ( !function_exists( 'oci_connect' ) ) {
throw new DBConnectionError(
$this,
$this->close();
$this->user = $user;
$this->password = $password;
- // changed internal variables functions
- // mServer now holds the TNS endpoint
- // mDBname is schema name if different from username
if ( !$server ) {
- // backward compatibillity (server used to be null and TNS was supplied in dbname)
+ // Backward compatibility (server used to be null and TNS was supplied in dbname)
$this->server = $dbName;
- $this->dbName = $user;
+ $realDatabase = $user;
} else {
+ // $server now holds the TNS endpoint
$this->server = $server;
- if ( !$dbName ) {
- $this->dbName = $user;
- } else {
- $this->dbName = $dbName;
- }
+ // $dbName is schema name if different from username
+ $realDatabase = $dbName ?: $user;
}
if ( !strlen( $user ) ) { # e.g. the class is being loaded
}
Wikimedia\restoreWarnings();
- if ( $this->user != $this->dbName ) {
+ if ( $this->user != $realDatabase ) {
// change current schema in session
- $this->selectDB( $this->dbName );
+ $this->selectDB( $realDatabase );
+ } else {
+ $this->currentDomain = new DatabaseDomain(
+ $realDatabase,
+ null,
+ $tablePrefix
+ );
}
if ( !$this->conn ) {
atc.table_name
) || '_' ||
atc.column_name || '_SEQ' = '{$this->tablePrefix}' || asq.sequence_name
- AND asq.sequence_owner = upper('{$this->dbName}')
- AND atc.owner = upper('{$this->dbName}')" );
+ AND asq.sequence_owner = upper('{$this->getDBname()}')
+ AND atc.owner = upper('{$this->getDBname()}')" );
while ( ( $row = $result->fetchRow() ) !== false ) {
$this->sequenceData[$row[1]] = [
$listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
}
- $owner = strtoupper( $this->dbName );
+ $owner = strtoupper( $this->getDBname() );
$result = $this->doQuery( "SELECT table_name FROM all_tables " .
"WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
$table = $this->tableName( $table );
$table = strtoupper( $this->removeIdentifierQuotes( $table ) );
$index = strtoupper( $index );
- $owner = strtoupper( $this->dbName );
+ $owner = strtoupper( $this->getDBname() );
$sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
$res = $this->doQuery( $sql );
if ( $res ) {
function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
- $owner = $this->addQuotes( strtoupper( $this->dbName ) );
+ $owner = $this->addQuotes( strtoupper( $this->getDBname() ) );
$sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
$res = $this->doQuery( $sql );
if ( $res && $res->numRows() > 0 ) {
return true;
}
- function selectDB( $db ) {
- $this->dbName = $db;
- if ( $db == null || $db == $this->user ) {
+ protected function doSelectDomain( DatabaseDomain $domain ) {
+ if ( $domain->getSchema() !== null ) {
+ // We use the *database* aspect of $domain for schema, not the domain schema
+ throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+ }
+
+ $database = $domain->getDatabase();
+ if ( $database === null || $database === $this->user ) {
+ // Backward compatibility
+ $this->currentDomain = $domain;
+
return true;
}
- $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
+
+ // https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj32268.html
+ $encDatabase = $this->addIdentifierQuotes( strtoupper( $database ) );
+ $sql = "ALTER SESSION SET CURRENT_SCHEMA=$encDatabase";
$stmt = oci_parse( $this->conn, $sql );
Wikimedia\suppressWarnings();
$success = oci_execute( $stmt );
Wikimedia\restoreWarnings();
- if ( !$success ) {
+ if ( $success ) {
+ // Update that domain fields on success (no exception thrown)
+ $this->currentDomain = $domain;
+ } else {
$e = oci_error( $stmt );
- if ( $e['code'] != '1435' ) {
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
- }
-
- return false;
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
}
return true;
return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
}
- function getDBname() {
- return $this->dbName;
- }
-
function getServer() {
return $this->server;
}
throw new DBUnexpectedError( $this, "Database selection is disallowed to enable reuse." );
}
+ public function selectDomain( $domain ) {
+ // Disallow things that might confuse the LoadBalancer tracking
+ throw new DBUnexpectedError( $this, "Database selection is disallowed to enable reuse." );
+ }
+
public function getDBname() {
return $this->__call( __FUNCTION__, func_get_args() );
}
protected $user;
/** @var string Password used to establish the current connection */
protected $password;
- /** @var string Database that this instance is currently connected to */
- protected $dbName;
/** @var array[] Map of (table => (dbname, schema, prefix) map) */
protected $tableAliases = [];
/** @var string[] Map of (index alias => index) */
/** @var bool Whether to suppress triggering of transaction end callbacks */
protected $trxEndCallbacksSuppressed = false;
- /** @var string */
- protected $tablePrefix = '';
- /** @var string */
- protected $schema = '';
/** @var int */
protected $flags;
/** @var array */
* @param array $params Parameters passed from Database::factory()
*/
protected function __construct( array $params ) {
- foreach ( [ 'host', 'user', 'password', 'dbname' ] as $name ) {
+ foreach ( [ 'host', 'user', 'password', 'dbname', 'schema', 'tablePrefix' ] as $name ) {
$this->connectionParams[$name] = $params[$name];
}
- $this->schema = $params['schema'];
- $this->tablePrefix = $params['tablePrefix'];
-
$this->cliMode = $params['cliMode'];
// Agent name is added to SQL queries in a comment, so make sure it can't break out
$this->agent = str_replace( '/', '-', $params['agent'] );
}
// Set initial dummy domain until open() sets the final DB/prefix
- $this->currentDomain = DatabaseDomain::newUnspecified();
+ $this->currentDomain = new DatabaseDomain(
+ $params['dbname'] != '' ? $params['dbname'] : null,
+ $params['schema'] != '' ? $params['schema'] : null,
+ $params['tablePrefix']
+ );
}
/**
}
// Establish the connection
$this->doInitConnection();
- // Set the domain object after open() sets the relevant fields
- if ( $this->dbName != '' ) {
- // Domains with server scope but a table prefix are not used by IDatabase classes
- $this->currentDomain = new DatabaseDomain( $this->dbName, null, $this->tablePrefix );
- }
}
/**
$this->connectionParams['host'],
$this->connectionParams['user'],
$this->connectionParams['password'],
- $this->connectionParams['dbname']
+ $this->connectionParams['dbname'],
+ $this->connectionParams['schema'],
+ $this->connectionParams['tablePrefix']
);
} else {
throw new InvalidArgumentException( "No database user provided." );
* @param string $user Database user name
* @param string $password Database user password
* @param string $dbName Database name
+ * @param string|null $schema Database schema name
+ * @param string $tablePrefix Table prefix
* @return bool
* @throws DBConnectionError
*/
- abstract protected function open( $server, $user, $password, $dbName );
+ abstract protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix );
/**
* Construct a Database subclass instance given a database type and parameters
$p['flags'] = $p['flags'] ?? 0;
$p['variables'] = $p['variables'] ?? [];
$p['tablePrefix'] = $p['tablePrefix'] ?? '';
- $p['schema'] = $p['schema'] ?? '';
+ $p['schema'] = $p['schema'] ?? null;
$p['cliMode'] = $p['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
$p['agent'] = $p['agent'] ?? '';
if ( !isset( $p['connLogger'] ) ) {
}
public function tablePrefix( $prefix = null ) {
- $old = $this->tablePrefix;
+ $old = $this->currentDomain->getTablePrefix();
if ( $prefix !== null ) {
- $this->tablePrefix = $prefix;
- $this->currentDomain = ( $this->dbName != '' )
- ? new DatabaseDomain( $this->dbName, null, $this->tablePrefix )
- : DatabaseDomain::newUnspecified();
+ $this->currentDomain = new DatabaseDomain(
+ $this->currentDomain->getDatabase(),
+ $this->currentDomain->getSchema(),
+ $prefix
+ );
}
return $old;
}
public function dbSchema( $schema = null ) {
- $old = $this->schema;
+ $old = $this->currentDomain->getSchema();
if ( $schema !== null ) {
- $this->schema = $schema;
+ $this->currentDomain = new DatabaseDomain(
+ $this->currentDomain->getDatabase(),
+ // DatabaseDomain uses null for unspecified schemas
+ strlen( $schema ) ? $schema : null,
+ $this->currentDomain->getTablePrefix()
+ );
}
- return $old;
+ return (string)$old;
+ }
+
+ /**
+ * @return string Schema to use to qualify relations in queries
+ */
+ protected function relationSchemaQualifier() {
+ return $this->dbSchema();
}
public function getLBInfo( $name = null ) {
return array_merge(
[
'db_server' => $this->server,
- 'db_name' => $this->dbName,
+ 'db_name' => $this->getDBname(),
'db_user' => $this->user,
],
$extras
return false;
}
- public function selectDB( $db ) {
- # Stub. Shouldn't cause serious problems if it's not overridden, but
- # if your database engine supports a concept similar to MySQL's
- # databases you may as well.
- $this->dbName = $db;
+ final public function selectDB( $db ) {
+ $this->selectDomain( new DatabaseDomain(
+ $db,
+ $this->currentDomain->getSchema(),
+ $this->currentDomain->getTablePrefix()
+ ) );
return true;
}
+ final public function selectDomain( $domain ) {
+ $this->doSelectDomain( DatabaseDomain::newFromId( $domain ) );
+ }
+
+ protected function doSelectDomain( DatabaseDomain $domain ) {
+ $this->currentDomain = $domain;
+ }
+
public function getDBname() {
- return $this->dbName;
+ return $this->currentDomain->getDatabase();
}
public function getServer() {
$database = $this->tableAliases[$table]['dbname'];
$schema = is_string( $this->tableAliases[$table]['schema'] )
? $this->tableAliases[$table]['schema']
- : $this->schema;
+ : $this->relationSchemaQualifier();
$prefix = is_string( $this->tableAliases[$table]['prefix'] )
? $this->tableAliases[$table]['prefix']
- : $this->tablePrefix;
+ : $this->tablePrefix();
} else {
$database = '';
- $schema = $this->schema; # Default schema
- $prefix = $this->tablePrefix; # Default prefix
+ $schema = $this->relationSchemaQualifier(); # Default schema
+ $prefix = $this->tablePrefix(); # Default prefix
}
}
$this->opened = false;
$this->conn = false;
try {
- $this->open( $this->server, $this->user, $this->password, $this->dbName );
+ $this->open(
+ $this->server,
+ $this->user,
+ $this->password,
+ $this->getDBname(),
+ $this->dbSchema(),
+ $this->tablePrefix()
+ );
$this->lastPing = microtime( true );
$ok = true;
$this->conn = false;
$this->trxEndCallbacks = []; // don't copy
$this->handleSessionLoss(); // no trx or locks anymore
- $this->open( $this->server, $this->user, $this->password, $this->dbName );
+ $this->open(
+ $this->server,
+ $this->user,
+ $this->password,
+ $this->getDBname(),
+ $this->dbSchema(),
+ $this->tablePrefix()
+ );
$this->lastPing = microtime( true );
}
}
parent::__construct( $params );
}
- protected function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
# Test for driver support, to avoid suppressed fatal error
if ( !function_exists( 'sqlsrv_connect' ) ) {
throw new DBConnectionError(
$this->server = $server;
$this->user = $user;
$this->password = $password;
- $this->dbName = $dbName;
$connectionInfo = [];
- if ( $dbName ) {
+ if ( $dbName != '' ) {
$connectionInfo['Database'] = $dbName;
}
}
$this->opened = true;
+ $this->currentDomain = new DatabaseDomain(
+ ( $dbName != '' ) ? $dbName : null,
+ null,
+ $tablePrefix
+ );
return (bool)$this->conn;
}
}
if ( $schema === false ) {
- $schema = $this->schema;
+ $schema = $this->dbSchema();
}
$res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
$s );
}
- /**
- * @param string $db
- * @return bool
- */
- public function selectDB( $db ) {
- try {
- $this->dbName = $db;
- $this->query( "USE $db" );
- return true;
- } catch ( Exception $e ) {
- return false;
- }
+ protected function doSelectDomain( DatabaseDomain $domain ) {
+ $encDatabase = $this->addIdentifierQuotes( $domain->getDatabase() );
+ $this->query( "USE $encDatabase" );
+ // Update that domain fields on success (no exception thrown)
+ $this->currentDomain = $domain;
+
+ return true;
}
/**
private function populateColumnCaches() {
$res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
[
- 'TABLE_CATALOG' => $this->dbName,
- 'TABLE_SCHEMA' => $this->schema,
+ 'TABLE_CATALOG' => $this->getDBname(),
+ 'TABLE_SCHEMA' => $this->dbSchema(),
'DATA_TYPE' => [ 'varbinary', 'binary', 'image', 'bit' ]
] );
return 'mysql';
}
- protected function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
# Close/unset connection handle
$this->close();
$this->server = $server;
$this->user = $user;
$this->password = $password;
- $this->dbName = $dbName;
$this->installErrorHandler();
try {
- $this->conn = $this->mysqlConnect( $this->server );
+ $this->conn = $this->mysqlConnect( $this->server, $dbName );
} catch ( Exception $ex ) {
$this->restoreErrorHandler();
throw $ex;
}
if ( strlen( $dbName ) ) {
- Wikimedia\suppressWarnings();
- $success = $this->selectDB( $dbName );
- Wikimedia\restoreWarnings();
- if ( !$success ) {
- $error = $this->lastError();
- $this->queryLogger->error(
- "Error selecting database {db_name} on server {db_server}: {error}",
- $this->getLogContext( [
- 'method' => __METHOD__,
- 'error' => $error,
- ] )
- );
- throw new DBConnectionError( $this, "Error selecting database $dbName: $error" );
- }
+ $this->selectDomain( new DatabaseDomain( $dbName, null, $tablePrefix ) );
+ } else {
+ $this->currentDomain = new DatabaseDomain( null, null, $tablePrefix );
}
// Tell the server what we're communicating with
* Open a connection to a MySQL server
*
* @param string $realServer
+ * @param string|null $dbName
* @return mixed Raw connection
* @throws DBConnectionError
*/
- abstract protected function mysqlConnect( $realServer );
+ abstract protected function mysqlConnect( $realServer, $dbName );
/**
* Set the character set of the MySQL link
*/
public function listViews( $prefix = null, $fname = __METHOD__ ) {
// The name of the column containing the name of the VIEW
- $propertyName = 'Tables_in_' . $this->dbName;
+ $propertyName = 'Tables_in_' . $this->getDBname();
// Query for the VIEWS
$res = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
/**
* @param string $realServer
+ * @param string|null $dbName
* @return bool|mysqli
* @throws DBConnectionError
*/
- protected function mysqlConnect( $realServer ) {
+ protected function mysqlConnect( $realServer, $dbName ) {
# Avoid suppressed fatal error, which is very hard to track down
if ( !function_exists( 'mysqli_init' ) ) {
throw new DBConnectionError( $this, "MySQLi functions missing,"
}
$mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
- if ( $mysqli->real_connect( $realServer, $this->user,
- $this->password, $this->dbName, $port, $socket, $connFlags )
- ) {
+ if ( $mysqli->real_connect(
+ $realServer,
+ $this->user,
+ $this->password,
+ $dbName,
+ $port,
+ $socket,
+ $connFlags
+ ) ) {
return $mysqli;
}
return $conn->affected_rows;
}
- /**
- * @param string $db
- * @return bool
- */
- function selectDB( $db ) {
+ function doSelectDomain( DatabaseDomain $domain ) {
$conn = $this->getBindingHandle();
- $this->dbName = $db;
+ if ( $domain->getSchema() !== null ) {
+ throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+ }
+
+ $database = $domain->getDatabase();
+ if ( !$conn->select_db( $database ) ) {
+ throw new DBExpectedError( $this, "Could not select database '$database'." );
+ }
+
+ // Update that domain fields on success (no exception thrown)
+ $this->currentDomain = $domain;
- return $conn->select_db( $db );
+ return true;
}
/**
return false;
}
- protected function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
# Test for Postgres support, to avoid suppressed fatal error
if ( !function_exists( 'pg_connect' ) ) {
throw new DBConnectionError(
$this->server = $server;
$this->user = $user;
$this->password = $password;
- $this->dbName = $dbName;
$connectVars = [
// pg_connect() user $user as the default database. Since a database is *required*,
$this->query( "SET standard_conforming_strings = on", __METHOD__ );
$this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
- $this->determineCoreSchema( $this->schema );
- // The schema to be used is now in the search path; no need for explicit qualification
- $this->schema = '';
+ $this->determineCoreSchema( $schema );
+ $this->currentDomain = new DatabaseDomain( $dbName, $schema, $tablePrefix );
- return $this->conn;
+ return (bool)$this->conn;
+ }
+
+ protected function relationSchemaQualifier() {
+ if ( $this->coreSchema === $this->currentDomain->getSchema() ) {
+ // The schema to be used is now in the search path; no need for explicit qualification
+ return '';
+ }
+
+ return parent::relationSchemaQualifier();
}
public function databasesAreIndependent() {
return true;
}
- /**
- * 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
- * @param string $db
- * @return bool
- * @throws DBUnexpectedError
- */
- public function selectDB( $db ) {
- if ( $this->dbName !== $db ) {
- return (bool)$this->open( $this->server, $this->user, $this->password, $db );
+ public function doSelectDomain( DatabaseDomain $domain ) {
+ if ( $this->getDBname() !== $domain->getDatabase() ) {
+ // 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,
+ $domain->getDatabase(),
+ $domain->getSchema(),
+ $domain->getTablePrefix()
+ );
} else {
- return true;
+ $this->currentDomain = $domain;
}
+
+ return true;
}
/**
return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
}
- public function getDBname() {
- return $this->dbName;
- }
-
public function getServer() {
return $this->server;
}
if ( isset( $p['dbFilePath'] ) ) {
$this->dbPath = $p['dbFilePath'];
$lockDomain = md5( $this->dbPath );
+ // Use "X" for things like X.sqlite and ":memory:" for RAM-only DBs
+ if ( !isset( $p['dbname'] ) || !strlen( $p['dbname'] ) ) {
+ $p['dbname'] = preg_replace( '/\.sqlite\d?$/', '', basename( $this->dbPath ) );
+ }
} elseif ( isset( $p['dbDirectory'] ) ) {
$this->dbDir = $p['dbDirectory'];
$lockDomain = $p['dbname'];
*/
public static function newStandaloneInstance( $filename, array $p = [] ) {
$p['dbFilePath'] = $filename;
- $p['schema'] = false;
+ $p['schema'] = null;
$p['tablePrefix'] = '';
/** @var DatabaseSqlite $db */
$db = Database::factory( 'sqlite', $p );
protected function doInitConnection() {
if ( $this->dbPath !== null ) {
// Standalone .sqlite file mode.
- $this->openFile( $this->dbPath, $this->connectionParams['dbname'] );
+ $this->openFile(
+ $this->dbPath,
+ $this->connectionParams['dbname'],
+ $this->connectionParams['tablePrefix']
+ );
} elseif ( $this->dbDir !== null ) {
// Stock wiki mode using standard file names per DB
if ( strlen( $this->connectionParams['dbname'] ) ) {
$this->connectionParams['host'],
$this->connectionParams['user'],
$this->connectionParams['password'],
- $this->connectionParams['dbname']
+ $this->connectionParams['dbname'],
+ $this->connectionParams['schema'],
+ $this->connectionParams['tablePrefix']
);
} else {
// Caller will manually call open() later?
return false;
}
- protected function open( $server, $user, $pass, $dbName ) {
+ protected function open( $server, $user, $pass, $dbName, $schema, $tablePrefix ) {
$this->close();
$fileName = self::generateFileName( $this->dbDir, $dbName );
if ( !is_readable( $fileName ) ) {
throw new DBConnectionError( $this, "SQLite database not accessible" );
}
// Only $dbName is used, the other parameters are irrelevant for SQLite databases
- $this->openFile( $fileName, $dbName );
+ $this->openFile( $fileName, $dbName, $tablePrefix );
return (bool)$this->conn;
}
*
* @param string $fileName
* @param string $dbName
+ * @param string $tablePrefix
* @throws DBConnectionError
* @return PDO|bool SQL connection or false if failed
*/
- protected function openFile( $fileName, $dbName ) {
+ protected function openFile( $fileName, $dbName, $tablePrefix ) {
$err = false;
$this->dbPath = $fileName;
$this->opened = is_object( $this->conn );
if ( $this->opened ) {
- $this->dbName = $dbName;
+ $this->currentDomain = new DatabaseDomain( $dbName, null, $tablePrefix );
# Set error codes only, don't raise exceptions
$this->conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
# Enforce LIKE to be case sensitive, just like MySQL
/**
* Get/set the table prefix.
* @param string|null $prefix The table prefix to set, or omitted to leave it unchanged.
- * @return string The previous table prefix.
+ * @return string The previous table prefix
+ * @throws DBUnexpectedError
*/
public function tablePrefix( $prefix = null );
/**
* Get/set the db schema.
* @param string|null $schema The database schema to set, or omitted to leave it unchanged.
- * @return string The previous db schema.
+ * @return string The previous db schema
*/
public function dbSchema( $schema = null );
public function getFlag( $flag );
/**
+ * Return the currently selected domain ID
+ *
+ * Null components (database/schema) might change once a connection is established
+ *
* @return string
*/
public function getDomainID();
* Change the current database
*
* @param string $db
- * @return bool Success or failure
+ * @return bool True unless an exception was thrown
* @throws DBConnectionError If databasesAreIndependent() is true and an error occurs
+ * @throws DBError
+ * @deprecated Since 1.32
*/
public function selectDB( $db );
+ /**
+ * Set the current domain (database, schema, and table prefix)
+ *
+ * This will throw an error for some database types if the database unspecified
+ *
+ * @param string|DatabaseDomain $domain
+ * @since 1.32
+ * @throws DBConnectionError
+ */
+ public function selectDomain( $domain );
+
/**
* Get the current DB name
- * @return string
+ * @return string|null
*/
public function getDBname();
* @param int $i Server index
* @param string $domain Domain ID to open
* @param int $flags Class CONN_* constant bitfield
- * @return Database
+ * @return Database|bool Returns false on connection error
+ * @throws DBError When database selection fails
*/
private function openForeignConnection( $i, $domain, $flags = 0 ) {
$domainInstance = DatabaseDomain::newFromId( $domain );
- $dbName = $domainInstance->getDatabase();
- $prefix = $domainInstance->getTablePrefix();
$autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
if ( $autoCommit ) {
$connInUseKey = self::KEY_FOREIGN_INUSE;
}
+ /** @var Database $conn */
if ( isset( $this->conns[$connInUseKey][$i][$domain] ) ) {
// Reuse an in-use connection for the same domain
$conn = $this->conns[$connInUseKey][$i][$domain];
// Reuse a free connection from another domain
$conn = reset( $this->conns[$connFreeKey][$i] );
$oldDomain = key( $this->conns[$connFreeKey][$i] );
- if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
- $this->lastError = "Error selecting database '$dbName' on server " .
- $conn->getServer() . " from client host {$this->hostname}";
- $this->errorConnection = $conn;
- $conn = false;
+ if ( $domainInstance->getDatabase() !== null ) {
+ $conn->selectDomain( $domainInstance );
} else {
- $conn->tablePrefix( $prefix );
- unset( $this->conns[$connFreeKey][$i][$oldDomain] );
- // Note that if $domain is an empty string, getDomainID() might not match it
- $this->conns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
- $this->connLogger->debug( __METHOD__ .
- ": reusing free connection from $oldDomain for $domain" );
+ // Stay on the current database, but update the schema/prefix
+ $conn->dbSchema( $domainInstance->getSchema() );
+ $conn->tablePrefix( $domainInstance->getTablePrefix() );
}
+ unset( $this->conns[$connFreeKey][$i][$oldDomain] );
+ // Note that if $domain is an empty string, getDomainID() might not match it
+ $this->conns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
+ $this->connLogger->debug( __METHOD__ .
+ ": reusing free connection from $oldDomain for $domain" );
} else {
if ( !isset( $this->servers[$i] ) || !is_array( $this->servers[$i] ) ) {
throw new InvalidArgumentException( "No server with index '$i'." );
) );
}
- protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) {
+ protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
return $this->db;
}
}
use Title;
use WANObjectCache;
use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabaseDomain;
use Wikimedia\Rdbms\DatabaseSqlite;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\LoadBalancer;
* @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject
*/
private function getLoadBalancerMock( array $server ) {
+ $domain = new DatabaseDomain( $server['dbname'], null, $server['tablePrefix'] );
+
$lb = $this->getMockBuilder( LoadBalancer::class )
->setMethods( [ 'reallyOpenConnection' ] )
- ->setConstructorArgs( [ [ 'servers' => [ $server ] ] ] )
+ ->setConstructorArgs( [
+ [ 'servers' => [ $server ], 'localDomain' => $domain ]
+ ] )
->getMock();
$lb->method( 'reallyOpenConnection' )->willReturnCallback(
'rev_content_model' => 'GOATMODEL',
];
+ $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
$db = $this->getMock( IDatabase::class );
$db->expects( $this->any() )
->method( 'getDomainId' )
- ->will( $this->returnValue( wfWikiID() ) );
+ ->will( $this->returnValue( $domain ) );
$db->expects( $this->once() )
->method( 'selectRow' )
->with(
/**
* @return \PHPUnit_Framework_MockObject_MockObject|ILoadBalancer
*/
- private function getMockLoadBalancer() {
- return $this->getMockBuilder( ILoadBalancer::class )
+ private function getMockLoadBalancer( $localDomain ) {
+ $mock = $this->getMockBuilder( ILoadBalancer::class )
->disableOriginalConstructor()->getMock();
+
+ $mock->expects( $this->any() )
+ ->method( 'getLocalDomainID' )
+ ->willReturn( $localDomain );
+
+ return $mock;
}
/**
$mock = $this->getMockBuilder( ILBFactory::class )
->disableOriginalConstructor()->getMock();
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $localDomain = $lbFactory->getLocalDomainID();
+
+ $mock->expects( $this->any() )->method( 'getLocalDomainID' )->willReturn( $localDomain );
+
$mock->expects( $this->once() )
->method( 'getMainLB' )
->with( $this->equalTo( $expectedWiki ) )
- ->willReturnCallback( function ( $domain ) use ( $expectedWiki ) {
- return $this->getMockLoadBalancer();
+ ->willReturnCallback( function ( $domain ) use ( $localDomain ) {
+ return $this->getMockLoadBalancer( $localDomain );
} );
return $mock;
/** @dataProvider provideTestGet */
public function testGet( $tableName, $wiki, $expectedWiki ) {
$services = MediaWikiServices::getInstance();
- $db = wfGetDB( DB_MASTER );
- if ( $wiki === false ) {
- $wiki2 = $db->getWikiID();
- } else {
- $wiki2 = $wiki;
- }
+ $wiki2 = ( $wiki === false )
+ ? $services->getDBLoadBalancerFactory()->getLocalDomainID()
+ : $wiki;
$names = new NameTableStoreFactory(
$this->getMockLoadBalancerFactory( $expectedWiki ),
$services->getMainWANObjectCache(),
wfWarn( $msg );
};
$this->currentDomain = DatabaseDomain::newUnspecified();
- $this->open( 'localhost', 'testuser', 'password', 'testdb' );
+ $this->open( 'localhost', 'testuser', 'password', 'testdb', null, '' );
}
/**
return 'test';
}
- function open( $server, $user, $password, $dbName ) {
+ function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
$this->conn = (object)[ 'test' ];
return true;
$this->assertFalse( $db->isOpen() );
} else {
\Wikimedia\suppressWarnings();
- $this->assertFalse( $db->selectDB( 'garbage-db' ) );
+ try {
+ $this->assertFalse( $db->selectDB( 'garbage-db' ) );
+ $this->fail( "No error thrown." );
+ } catch ( \Wikimedia\Rdbms\DBExpectedError $e ) {
+ $this->assertEquals(
+ "Could not select database 'garbage-db'.",
+ $e->getMessage()
+ );
+ }
\Wikimedia\restoreWarnings();
}
}
private function getMockForViews() {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
- ->setMethods( [ 'fetchRow', 'query' ] )
+ ->setMethods( [ 'fetchRow', 'query', 'getDBname' ] )
->getMock();
$db->method( 'query' )
(object)[ 'Tables_in_' => 'view2' ],
(object)[ 'Tables_in_' => 'myview' ]
] ) );
+ $db->method( 'getDBname' )->willReturn( '' );
return $db;
}
public function testIndexAliases() {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
- ->setMethods( [ 'mysqlRealEscapeString' ] )
+ ->setMethods( [ 'mysqlRealEscapeString', 'dbSchema', 'tablePrefix' ] )
->getMock();
$db->method( 'mysqlRealEscapeString' )->willReturnCallback(
function ( $s ) {
public function testTableAliases() {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
- ->setMethods( [ 'mysqlRealEscapeString' ] )
+ ->setMethods( [ 'mysqlRealEscapeString', 'dbSchema', 'tablePrefix' ] )
->getMock();
$db->method( 'mysqlRealEscapeString' )->willReturnCallback(
function ( $s ) {
* @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
*/
public function testTransactionIdle_TRX() {
- $db = $this->getMockDB( [ 'isOpen', 'ping' ] );
+ $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->method( 'ping' )->willReturn( true );
+ $db->method( 'getDBname' )->willReturn( '' );
$db->setFlag( DBO_TRX );
$lbFactory = LBFactorySingle::newFromConnection( $db );
* @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
*/
public function testTransactionPreCommitOrIdle_TRX() {
- $db = $this->getMockDB( [ 'isOpen', 'ping' ] );
+ $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->method( 'ping' )->willReturn( true );
+ $db->method( 'getDBname' )->willReturn( 'unittest' );
$db->setFlag( DBO_TRX );
$lbFactory = LBFactorySingle::newFromConnection( $db );
* @covers Wikimedia\Rdbms\Database::lockIsFree
*/
public function testGetScopedLock() {
- $db = $this->getMockDB( [ 'isOpen' ] );
+ $db = $this->getMockDB( [ 'isOpen', 'getDBname' ] );
$db->method( 'isOpen' )->willReturn( true );
+ $db->method( 'getDBname' )->willReturn( 'unittest' );
$this->assertEquals( 0, $db->trxLevel() );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
* @covers Wikimedia\Rdbms\Database::tablePrefix
* @covers Wikimedia\Rdbms\Database::dbSchema
*/
- public function testMutators() {
+ public function testSchemaAndPrefixMutators() {
$old = $this->db->tablePrefix();
+ $oldDomain = $this->db->getDomainId();
$this->assertInternalType( 'string', $old, 'Prefix is string' );
- $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" );
- $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) );
- $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" );
+ $this->assertSame( $old, $this->db->tablePrefix(), "Prefix unchanged" );
+ $this->assertSame( $old, $this->db->tablePrefix( 'xxx' ) );
+ $this->assertSame( 'xxx', $this->db->tablePrefix(), "Prefix set" );
$this->db->tablePrefix( $old );
$this->assertNotEquals( 'xxx', $this->db->tablePrefix() );
+ $this->assertSame( $oldDomain, $this->db->getDomainId() );
$old = $this->db->dbSchema();
+ $oldDomain = $this->db->getDomainId();
$this->assertInternalType( 'string', $old, 'Schema is string' );
- $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" );
- $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) );
- $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" );
+ $this->assertSame( $old, $this->db->dbSchema(), "Schema unchanged" );
+ $this->assertSame( $old, $this->db->dbSchema( 'xxx' ) );
+ $this->assertSame( 'xxx', $this->db->dbSchema(), "Schema set" );
$this->db->dbSchema( $old );
$this->assertNotEquals( 'xxx', $this->db->dbSchema() );
+ $this->assertSame( $oldDomain, $this->db->getDomainId() );
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\Database::selectDomain
+ */
+ public function testSelectDomain() {
+ $oldDomain = $this->db->getDomainId();
+ $oldDatabase = $this->db->getDBname();
+ $oldSchema = $this->db->dbSchema();
+ $oldPrefix = $this->db->tablePrefix();
+
+ $this->db->selectDomain( 'testselectdb-xxx' );
+ $this->assertSame( 'testselectdb', $this->db->getDBname() );
+ $this->assertSame( '', $this->db->dbSchema() );
+ $this->assertSame( 'xxx', $this->db->tablePrefix() );
+
+ $this->db->selectDomain( $oldDomain );
+ $this->assertSame( $oldDatabase, $this->db->getDBname() );
+ $this->assertSame( $oldSchema, $this->db->dbSchema() );
+ $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
+ $this->assertSame( $oldDomain, $this->db->getDomainId() );
+
+ $this->db->selectDomain( 'testselectdb-schema-xxx' );
+ $this->assertSame( 'testselectdb', $this->db->getDBname() );
+ $this->assertSame( 'schema', $this->db->dbSchema() );
+ $this->assertSame( 'xxx', $this->db->tablePrefix() );
+
+ $this->db->selectDomain( $oldDomain );
+ $this->assertSame( $oldDatabase, $this->db->getDBname() );
+ $this->assertSame( $oldSchema, $this->db->dbSchema() );
+ $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
+ $this->assertSame( $oldDomain, $this->db->getDomainId() );
}
}