$server = $this->servers[$i];
$server['serverIndex'] = $i;
$server['autoCommitOnly'] = $autoCommit;
- if ( $this->localDomain->getDatabase() !== null ) {
- // Use the local domain table prefix if the local domain is specified
- $server['tablePrefix'] = $this->localDomain->getTablePrefix();
- }
$conn = $this->reallyOpenConnection( $server, $this->localDomain );
$host = $this->getServerName( $i );
if ( $conn->isOpen() ) {
* @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'." );
$this->errorConnection = $conn;
$conn = false;
} else {
- $conn->tablePrefix( $prefix ); // as specified
// 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" );
* Returns a Database object whether or not the connection was successful.
*
* @param array $server
- * @param DatabaseDomain $domainOverride Use an unspecified domain to not select any database
+ * @param DatabaseDomain $domain Domain the connection is for, possibly unspecified
* @return Database
* @throws DBAccessError
* @throws InvalidArgumentException
*/
- protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) {
+ protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
if ( $this->disabled ) {
throw new DBAccessError();
}
- // Handle $domainOverride being a specified or an unspecified domain
- if ( $domainOverride->getDatabase() === null ) {
- // Normally, an RDBMS requires a DB name specified on connection and the $server
- // configuration array is assumed to already specify an appropriate DB name.
+ 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
$server['dbname'] = null;
}
} else {
- $server['dbname'] = $domainOverride->getDatabase();
- $server['schema'] = $domainOverride->getSchema();
+ $server['dbname'] = $domain->getDatabase();
+ }
+
+ 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;
}
public function closeAll() {
- $this->forEachOpenConnection( function ( IDatabase $conn ) {
+ $fname = __METHOD__;
+ $this->forEachOpenConnection( function ( IDatabase $conn ) use ( $fname ) {
$host = $conn->getServer();
$this->connLogger->debug(
- __METHOD__ . ": closing connection to database '$host'." );
+ $fname . ": closing connection to database '$host'." );
$conn->close();
} );
// If atomic sections or explicit transactions are still open, some caller must have
// caught an exception but failed to properly rollback any changes. Detect that and
// throw and error (causing rollback).
- if ( $conn->explicitTrxActive() ) {
- throw new DBTransactionError(
- $conn,
- "Explicit transaction still active. A caller may have caught an error."
- );
- }
+ $conn->assertNoOpenTransactions();
// Assert that the time to replicate the transaction will be sane.
// If this fails, then all DB transactions will be rollback back together.
$time = $conn->pendingWriteQueryDuration( $conn::ESTIMATE_DB_APPLY );
} );
$e = null; // first exception
+ $fname = __METHOD__;
// Loop until callbacks stop adding callbacks on other connections
do {
// Run any pending callbacks for each connection...
}
);
// Clear out any active transactions left over from callbacks...
- $this->forEachOpenMasterConnection( function ( Database $conn ) use ( &$e ) {
+ $this->forEachOpenMasterConnection( function ( Database $conn ) use ( &$e, $fname ) {
if ( $conn->writesPending() ) {
// A callback from another handle wrote to this one and DBO_TRX is set
- $this->queryLogger->warning( __METHOD__ . ": found writes pending." );
+ $this->queryLogger->warning( $fname . ": found writes pending." );
$fnames = implode( ', ', $conn->pendingWriteAndCallbackCallers() );
$this->queryLogger->warning(
- __METHOD__ . ": found writes pending ($fnames).",
+ $fname . ": found writes pending ($fnames).",
[
'db_server' => $conn->getServer(),
'db_name' => $conn->getDBname()
} elseif ( $conn->trxLevel() ) {
// A callback from another handle read from this one and DBO_TRX is set,
// which can easily happen if there is only one DB (no replicas)
- $this->queryLogger->debug( __METHOD__ . ": found empty transaction." );
+ $this->queryLogger->debug( $fname . ": found empty transaction." );
}
try {
- $conn->commit( __METHOD__, $conn::FLUSHING_ALL_PEERS );
+ $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
} catch ( Exception $ex ) {
$e = $e ?: $ex;
}