use Psr\Log\NullLogger;
use Wikimedia\ScopedCallback;
use Wikimedia\Timestamp\ConvertibleTimestamp;
-use Wikimedia;
+use Wikimedia\AtEase\AtEase;
use BagOStuff;
use HashBagOStuff;
use LogicException;
/** @var int Writes to this temporary table effect lastDoneWrites() */
private static $TEMP_PSEUDO_PERMANENT = 2;
- /** Number of times to re-try an operation in case of deadlock */
+ /** @var int Number of times to re-try an operation in case of deadlock */
private static $DEADLOCK_TRIES = 4;
- /** Minimum time to wait before retry, in microseconds */
+ /** @var int Minimum time to wait before retry, in microseconds */
private static $DEADLOCK_DELAY_MIN = 500000;
- /** Maximum time to wait before retry */
+ /** @var int Maximum time to wait before retry */
private static $DEADLOCK_DELAY_MAX = 1500000;
- /** How long before it is worth doing a dummy query to test the connection */
+ /** @var int How long before it is worth doing a dummy query to test the connection */
private static $PING_TTL = 1.0;
+ /** @var string Dummy SQL query */
private static $PING_QUERY = 'SELECT 1 AS ping';
+ /** @var float Guess of how many seconds it takes to replicate a small insert */
private static $TINY_WRITE_SEC = 0.010;
+ /** @var float Consider a write slow if it took more than this many seconds */
private static $SLOW_WRITE_SEC = 0.500;
+ /** @var float Assume an insert of this many rows or less should be fast to replicate */
private static $SMALL_WRITE_ROWS = 100;
/**
*/
final public function initConnection() {
if ( $this->isOpen() ) {
- throw new LogicException( __METHOD__ . ': already connected.' );
+ throw new LogicException( __METHOD__ . ': already connected' );
}
// Establish the connection
$this->doInitConnection();
$this->connectionParams['tablePrefix']
);
} else {
- throw new InvalidArgumentException( "No database user provided." );
+ throw new InvalidArgumentException( "No database user provided" );
}
}
/**
* Open a new connection to the database (closing any existing one)
*
- * @param string $server Database server host
- * @param string $user Database user name
- * @param string $password Database user password
- * @param string $dbName Database name
+ * @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 $schema Database schema name
* @param string $tablePrefix Table prefix
* @throws DBConnectionError
*
* This also connects to the database immediately upon object construction
*
- * @param string $dbType A possible DB type (sqlite, mysql, postgres,...)
- * @param array $p Parameter map with keys:
+ * @param string $type A possible DB type (sqlite, mysql, postgres,...)
+ * @param array $params Parameter map with keys:
* - host : The hostname of the DB server
* - user : The name of the database user the client operates under
* - password : The password for the database user
* @throws InvalidArgumentException If the database driver or extension cannot be found
* @since 1.18
*/
- final public static function factory( $dbType, $p = [], $connect = self::NEW_CONNECTED ) {
- $class = self::getClass( $dbType, $p['driver'] ?? null );
+ final public static function factory( $type, $params = [], $connect = self::NEW_CONNECTED ) {
+ $class = self::getClass( $type, $params['driver'] ?? null );
if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) {
- // Resolve some defaults for b/c
- $p['host'] = $p['host'] ?? false;
- $p['user'] = $p['user'] ?? false;
- $p['password'] = $p['password'] ?? false;
- $p['dbname'] = $p['dbname'] ?? false;
- $p['flags'] = $p['flags'] ?? 0;
- $p['variables'] = $p['variables'] ?? [];
- $p['tablePrefix'] = $p['tablePrefix'] ?? '';
- $p['schema'] = $p['schema'] ?? null;
- $p['cliMode'] = $p['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
- $p['agent'] = $p['agent'] ?? '';
- if ( !isset( $p['connLogger'] ) ) {
- $p['connLogger'] = new NullLogger();
- }
- if ( !isset( $p['queryLogger'] ) ) {
- $p['queryLogger'] = new NullLogger();
- }
- $p['profiler'] = $p['profiler'] ?? null;
- if ( !isset( $p['trxProfiler'] ) ) {
- $p['trxProfiler'] = new TransactionProfiler();
- }
- if ( !isset( $p['errorLogger'] ) ) {
- $p['errorLogger'] = function ( Exception $e ) {
+ $params += [
+ 'host' => null,
+ 'user' => null,
+ 'password' => null,
+ 'dbname' => null,
+ 'schema' => null,
+ 'tablePrefix' => '',
+ 'flags' => 0,
+ 'variables' => [],
+ 'cliMode' => ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ),
+ 'agent' => basename( $_SERVER['SCRIPT_NAME'] ) . '@' . gethostname()
+ ];
+
+ $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'],
+ // Objects and callbacks
+ 'profiler' => $params['profiler'] ?? null,
+ 'trxProfiler' => $params['trxProfiler'] ?? new TransactionProfiler(),
+ 'connLogger' => $params['connLogger'] ?? new NullLogger(),
+ 'queryLogger' => $params['queryLogger'] ?? new NullLogger(),
+ 'errorLogger' => $params['errorLogger'] ?? function ( Exception $e ) {
trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
- };
- }
- if ( !isset( $p['deprecationLogger'] ) ) {
- $p['deprecationLogger'] = function ( $msg ) {
+ },
+ 'deprecationLogger' => $params['deprecationLogger'] ?? function ( $msg ) {
trigger_error( $msg, E_USER_DEPRECATED );
- };
- }
+ }
+ ] + $params;
/** @var Database $conn */
- $conn = new $class( $p );
- if ( $connect == self::NEW_CONNECTED ) {
+ $conn = new $class( $normalizedParams );
+ if ( $connect === self::NEW_CONNECTED ) {
$conn->initConnection();
}
} else {
public function dbSchema( $schema = null ) {
if ( strlen( $schema ) && $this->getDBname() === null ) {
- throw new DBUnexpectedError( $this, "Cannot set schema to '$schema'; no database set." );
+ throw new DBUnexpectedError( $this, "Cannot set schema to '$schema'; no database set" );
}
$old = $this->currentDomain->getSchema();
return null;
}
- public function setLBInfo( $name, $value = null ) {
- if ( is_null( $value ) ) {
- $this->lbInfo = $name;
+ public function setLBInfo( $nameOrArray, $value = null ) {
+ if ( is_array( $nameOrArray ) ) {
+ $this->lbInfo = $nameOrArray;
+ } elseif ( is_string( $nameOrArray ) ) {
+ if ( $value !== null ) {
+ $this->lbInfo[$nameOrArray] = $value;
+ } else {
+ unset( $this->lbInfo[$nameOrArray] );
+ }
} else {
- $this->lbInfo[$name] = $value;
+ throw new InvalidArgumentException( "Got non-string key" );
}
}
public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
if ( ( $flag & self::DBO_IGNORE ) ) {
- throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
+ throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed" );
}
if ( $remember === self::REMEMBER_PRIOR ) {
public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
if ( ( $flag & self::DBO_IGNORE ) ) {
- throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
+ throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed" );
}
if ( $remember === self::REMEMBER_PRIOR ) {
$levels = $this->flatAtomicSectionList();
$exception = new DBUnexpectedError(
$this,
- __METHOD__ . ": atomic sections $levels are still open."
+ __METHOD__ . ": atomic sections $levels are still open"
);
} elseif ( $this->trxAutomatic ) {
// Only the connection manager can commit non-empty DBO_TRX transactions
$exception = new DBUnexpectedError(
$this,
__METHOD__ .
- ": mass commit/rollback of peer transaction required (DBO_TRX set)."
+ ": mass commit/rollback of peer transaction required (DBO_TRX set)"
);
}
} else {
// back, even if empty.
$exception = new DBUnexpectedError(
$this,
- __METHOD__ . ": transaction is still open (from {$this->trxFname})."
+ __METHOD__ . ": transaction is still open (from {$this->trxFname})"
);
}
if ( $this->trxEndCallbacksSuppressed ) {
$exception = $exception ?: new DBUnexpectedError(
$this,
- __METHOD__ . ': callbacks are suppressed; cannot properly commit.'
+ __METHOD__ . ': callbacks are suppressed; cannot properly commit'
);
}
$fnames = $this->pendingWriteAndCallbackCallers();
if ( $fnames ) {
throw new RuntimeException(
- "Transaction callbacks are still pending:\n" . implode( ', ', $fnames )
+ "Transaction callbacks are still pending: " . implode( ', ', $fnames )
);
}
}
*/
final protected function assertHasConnectionHandle() {
if ( !$this->isOpen() ) {
- throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ throw new DBUnexpectedError( $this, "DB connection was already closed" );
}
}
if ( $this->getLBInfo( 'replica' ) === true ) {
throw new DBReadOnlyRoleError(
$this,
- 'Write operations are not allowed on replica database connections.'
+ 'Write operations are not allowed on replica database connections'
);
}
$reason = $this->getReadOnlyReason();
private function assertQueryIsCurrentlyAllowed( $sql, $fname ) {
$verb = $this->getQueryVerb( $sql );
if ( $verb === 'USE' ) {
- throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead." );
+ throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead" );
}
if ( $verb === 'ROLLBACK' ) { // transaction/savepoint
if ( $this->trxStatus < self::STATUS_TRX_OK ) {
throw new DBTransactionStateError(
$this,
- "Cannot execute query from $fname while transaction status is ERROR.",
+ "Cannot execute query from $fname while transaction status is ERROR",
[],
$this->trxStatusCause
);
*/
public function reportQueryError( $error, $errno, $sql, $fname, $ignore = false ) {
if ( $ignore ) {
- $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
+ $this->queryLogger->debug( "SQL ERROR (ignored): $error" );
} else {
$exception = $this->getQueryExceptionAndLog( $error, $errno, $sql, $fname );
'trace' => ( new RuntimeException() )->getTraceAsString()
] )
);
- $this->queryLogger->debug( "SQL ERROR: " . $error . "\n" );
+ $this->queryLogger->debug( "SQL ERROR: " . $error . "" );
if ( $this->wasQueryTimeout( $error, $errno ) ) {
$e = new DBQueryTimeoutError( $this, $error, $errno, $sql, $fname );
} elseif ( $this->wasConnectionError( $errno ) ) {
// functions. Discourage use of such queries to encourage compatibility.
call_user_func(
$this->deprecationLogger,
- __METHOD__ . ": aggregation used with a locking SELECT ($fname)."
+ __METHOD__ . ": aggregation used with a locking SELECT ($fname)"
);
}
} elseif ( count( $var ) == 1 ) {
$column = $var[0] ?? reset( $var );
} else {
- throw new DBUnexpectedError( $this, __METHOD__ . ': got multiple columns.' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ': got multiple columns' );
}
} else {
$column = $var;
if ( $name instanceof Subquery ) {
throw new DBUnexpectedError(
$this,
- __METHOD__ . ': got Subquery instance when expecting a string.'
+ __METHOD__ . ': got Subquery instance when expecting a string'
);
}
# surrounded by symbols which may be considered word breaks.
if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
$this->queryLogger->warning(
- __METHOD__ . ": use of subqueries is not supported this way.",
+ __METHOD__ . ": use of subqueries is not supported this way",
[ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
);
} elseif ( $table instanceof Subquery ) {
$quotedTable = (string)$table;
} else {
- throw new InvalidArgumentException( "Table must be a string or Subquery." );
+ throw new InvalidArgumentException( "Table must be a string or Subquery" );
}
if ( $alias === false || $alias === $table ) {
if ( $table instanceof Subquery ) {
- throw new InvalidArgumentException( "Subquery table missing alias." );
+ throw new InvalidArgumentException( "Subquery table missing alias" );
}
return $quotedTable;
public function limitResult( $sql, $limit, $offset = false ) {
if ( !is_numeric( $limit ) ) {
- throw new DBUnexpectedError( $this,
- "Invalid non-numeric limit passed to limitResult()\n" );
+ throw new DBUnexpectedError(
+ $this,
+ "Invalid non-numeric limit passed to " . __METHOD__
+ );
}
// This version works in MySQL and SQLite. It will very likely need to be
// overridden for most other RDBMS subclasses.
final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
if ( !$this->trxLevel() ) {
- throw new DBUnexpectedError( $this, "No transaction is active." );
+ throw new DBUnexpectedError( $this, "No transaction is active" );
}
$this->trxEndCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
}
final public function onAtomicSectionCancel( callable $callback, $fname = __METHOD__ ) {
if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
- throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
+ throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)" );
}
$this->trxSectionCancelCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
}
*/
public function runOnTransactionIdleCallbacks( $trigger ) {
if ( $this->trxLevel() ) { // sanity
- throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open.' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open' );
}
if ( $this->trxEndCallbacksSuppressed ) {
final public function endAtomic( $fname = __METHOD__ ) {
if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
- throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
+ throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)" );
}
// Check if the current section matches $fname
if ( $savedFname !== $fname ) {
throw new DBUnexpectedError(
$this,
- "Invalid atomic section ended (got $fname but expected $savedFname)."
+ "Invalid atomic section ended (got $fname but expected $savedFname)"
);
}
$fname = __METHOD__, AtomicSectionIdentifier $sectionId = null
) {
if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
- throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
+ throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)" );
}
$excisedIds = [];
if ( $savedFname !== $fname ) {
throw new DBUnexpectedError(
$this,
- "Invalid atomic section ended (got $fname but expected $savedFname)."
+ "Invalid atomic section ended (got $fname but expected $savedFname)"
);
}
$this->trxStatus = self::STATUS_TRX_ERROR;
$this->trxStatusCause = new DBUnexpectedError(
$this,
- "Uncancelable atomic section canceled (got $fname)."
+ "Uncancelable atomic section canceled (got $fname)"
);
}
} finally {
final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
static $modes = [ self::TRANSACTION_EXPLICIT, self::TRANSACTION_INTERNAL ];
if ( !in_array( $mode, $modes, true ) ) {
- throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'." );
+ throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'" );
}
// Protect against mismatched atomic section, transaction nesting, and snapshot loss
if ( $this->trxLevel() ) {
if ( $this->trxAtomicLevels ) {
$levels = $this->flatAtomicSectionList();
- $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
+ $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open";
throw new DBUnexpectedError( $this, $msg );
} elseif ( !$this->trxAutomatic ) {
- $msg = "$fname: Explicit transaction already active (from {$this->trxFname}).";
+ $msg = "$fname: Explicit transaction already active (from {$this->trxFname})";
throw new DBUnexpectedError( $this, $msg );
} else {
- $msg = "$fname: Implicit transaction already active (from {$this->trxFname}).";
+ $msg = "$fname: Implicit transaction already active (from {$this->trxFname})";
throw new DBUnexpectedError( $this, $msg );
}
} elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
- $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
+ $msg = "$fname: Implicit transaction expected (DBO_TRX set)";
throw new DBUnexpectedError( $this, $msg );
}
final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
static $modes = [ self::FLUSHING_ONE, self::FLUSHING_ALL_PEERS, self::FLUSHING_INTERNAL ];
if ( !in_array( $flush, $modes, true ) ) {
- throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'." );
+ throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'" );
}
if ( $this->trxLevel() && $this->trxAtomicLevels ) {
$levels = $this->flatAtomicSectionList();
throw new DBUnexpectedError(
$this,
- "$fname: Got COMMIT while atomic sections $levels are still open."
+ "$fname: Got COMMIT while atomic sections $levels are still open"
);
}
} elseif ( !$this->trxAutomatic ) {
throw new DBUnexpectedError(
$this,
- "$fname: Flushing an explicit transaction, getting out of sync."
+ "$fname: Flushing an explicit transaction, getting out of sync"
);
}
} elseif ( !$this->trxLevel() ) {
$this->queryLogger->error(
- "$fname: No transaction to commit, something got out of sync." );
+ "$fname: No transaction to commit, something got out of sync" );
return; // nothing to do
} elseif ( $this->trxAutomatic ) {
throw new DBUnexpectedError(
$this,
- "$fname: Expected mass commit of all peer transactions (DBO_TRX set)."
+ "$fname: Expected mass commit of all peer transactions (DBO_TRX set)"
);
}
) {
throw new DBUnexpectedError(
$this,
- "$fname: Expected mass rollback of all peer transactions (DBO_TRX set)."
+ "$fname: Expected mass rollback of all peer transactions (DBO_TRX set)"
);
}
}
}
- public function flushSnapshot( $fname = __METHOD__ ) {
- if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
+ public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
+ if ( $this->explicitTrxActive() ) {
+ // Committing this transaction would break callers that assume it is still open
+ throw new DBUnexpectedError(
+ $this,
+ "$fname: Cannot flush snapshot; " .
+ "explicit transaction '{$this->trxFname}' is still open"
+ );
+ } elseif ( $this->writesOrCallbacksPending() ) {
// This only flushes transactions to clear snapshots, not to write data
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
- "$fname: Cannot flush snapshot because writes are pending ($fnames)."
+ "$fname: Cannot flush snapshot; " .
+ "writes from transaction {$this->trxFname} are still pending ($fnames)"
+ );
+ } elseif (
+ $this->trxLevel() &&
+ $this->getTransactionRoundId() &&
+ $flush !== self::FLUSHING_INTERNAL &&
+ $flush !== self::FLUSHING_ALL_PEERS
+ ) {
+ $this->queryLogger->warning(
+ "$fname: Expected mass snapshot flush of all peer transactions " .
+ "in the explicit transactions round '{$this->getTransactionRoundId()}'",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
);
}
$this->server,
$this->user,
$this->password,
- $this->getDBname(),
- $this->dbSchema(),
+ $this->currentDomain->getDatabase(),
+ $this->currentDomain->getSchema(),
$this->tablePrefix()
);
$this->lastPing = microtime( true );
$fname = false,
callable $inputCallback = null
) {
- Wikimedia\suppressWarnings();
+ AtEase::suppressWarnings();
$fp = fopen( $filename, 'r' );
- Wikimedia\restoreWarnings();
+ AtEase::restoreWarnings();
if ( $fp === false ) {
- throw new RuntimeException( "Could not open \"{$filename}\".\n" );
+ throw new RuntimeException( "Could not open \"{$filename}\"" );
}
if ( !$fname ) {
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
- "$fname: Cannot flush pre-lock snapshot because writes are pending ($fnames)."
+ "$fname: Cannot flush pre-lock snapshot; " .
+ "writes from transaction {$this->trxFname} are still pending ($fnames)"
);
}
final public function lockTables( array $read, array $write, $method ) {
if ( $this->writesOrCallbacksPending() ) {
- throw new DBUnexpectedError( $this, "Transaction writes or callbacks still pending." );
+ throw new DBUnexpectedError( $this, "Transaction writes or callbacks still pending" );
}
if ( $this->tableLocksHaveTransactionScope() ) {
*/
protected function getReadOnlyReason() {
$reason = $this->getLBInfo( 'readOnlyReason' );
+ if ( is_string( $reason ) ) {
+ return $reason;
+ } elseif ( $this->getLBInfo( 'replica' ) ) {
+ return "Server is configured in the role of a read-only replica database.";
+ }
- return is_string( $reason ) ? $reason : false;
+ return false;
}
public function setTableAliases( array $aliases ) {
if ( !$this->conn ) {
throw new DBUnexpectedError(
$this,
- 'DB connection was already closed or the connection dropped.'
+ 'DB connection was already closed or the connection dropped'
);
}
*/
public function __clone() {
$this->connLogger->warning(
- "Cloning " . static::class . " is not recommended; forking connection:\n" .
- ( new RuntimeException() )->getTraceAsString()
+ "Cloning " . static::class . " is not recommended; forking connection",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
);
if ( $this->isOpen() ) {
$this->server,
$this->user,
$this->password,
- $this->getDBname(),
- $this->dbSchema(),
+ $this->currentDomain->getDatabase(),
+ $this->currentDomain->getSchema(),
$this->tablePrefix()
);
$this->lastPing = microtime( true );
*/
public function __sleep() {
throw new RuntimeException( 'Database serialization may cause problems, since ' .
- 'the connection is not restored on wakeup.' );
+ 'the connection is not restored on wakeup' );
}
/**
*/
public function __destruct() {
if ( $this->trxLevel() && $this->trxDoneWrites ) {
- trigger_error( "Uncommitted DB writes (transaction from {$this->trxFname})." );
+ trigger_error( "Uncommitted DB writes (transaction from {$this->trxFname})" );
}
$danglingWriters = $this->pendingWriteAndCallbackCallers();
if ( $danglingWriters ) {
$fnames = implode( ', ', $danglingWriters );
- trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
+ trigger_error( "DB transaction writes or callbacks still pending ($fnames)" );
}
if ( $this->conn ) {
// Avoid connection leaks for sanity. Normally, resources close at script completion.
// The connection might already be closed in zend/hhvm by now, so suppress warnings.
- Wikimedia\suppressWarnings();
+ AtEase::suppressWarnings();
$this->closeConnection();
- Wikimedia\restoreWarnings();
- $this->conn = false;
+ AtEase::restoreWarnings();
+ $this->conn = null;
}
}
}