use DateTime;
use DateTimeZone;
-use Wikimedia;
+use Wikimedia\AtEase\AtEase;
use InvalidArgumentException;
use Exception;
use RuntimeException;
* - sslCiphers : array list of allowable ciphers [default: null]
* @param array $params
*/
- function __construct( array $params ) {
+ public function __construct( array $params ) {
$this->lagDetectionMethod = $params['lagDetectionMethod'] ?? 'Seconds_Behind_Master';
$this->lagDetectionOptions = $params['lagDetectionOptions'] ?? [];
$this->useGTIDs = !empty( $params['useGTIDs' ] );
}
protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
- # Close/unset connection handle
$this->close();
+ if ( $schema !== null ) {
+ throw new DBExpectedError( $this, __CLASS__ . ": cannot use schemas ('$schema')" );
+ }
+
$this->server = $server;
$this->user = $user;
$this->password = $password;
$error = $error ?: $this->lastError();
$this->connLogger->error(
"Error connecting to {db_server}: {error}",
- $this->getLogContext( [
- 'method' => __METHOD__,
- 'error' => $error,
- ] )
+ $this->getLogContext( [ 'method' => __METHOD__, 'error' => $error ] )
);
$this->connLogger->debug( "DB connection error\n" .
"Server: $server, User: $user, Password: " .
substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
-
throw new DBConnectionError( $this, $error );
}
- if ( strlen( $dbName ) ) {
- $this->selectDomain( new DatabaseDomain( $dbName, null, $tablePrefix ) );
- } else {
- $this->currentDomain = new DatabaseDomain( null, null, $tablePrefix );
- }
-
- // Tell the server what we're communicating with
- if ( !$this->connectInitCharset() ) {
- $error = $this->lastError();
- $this->queryLogger->error(
- "Error setting character set: {error}",
- $this->getLogContext( [
- 'method' => __METHOD__,
- 'error' => $this->lastError(),
- ] )
+ try {
+ $this->currentDomain = new DatabaseDomain(
+ strlen( $dbName ) ? $dbName : null,
+ null,
+ $tablePrefix
);
- throw new DBConnectionError( $this, "Error setting character set: $error" );
- }
- // Abstract over any insane MySQL defaults
- $set = [ 'group_concat_max_len = 262144' ];
- // Set SQL mode, default is turning them all off, can be overridden or skipped with null
- if ( is_string( $this->sqlMode ) ) {
- $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode );
- }
- // Set any custom settings defined by site config
- // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
- foreach ( $this->connectionVariables as $var => $val ) {
- // Escape strings but not numbers to avoid MySQL complaining
- if ( !is_int( $val ) && !is_float( $val ) ) {
- $val = $this->addQuotes( $val );
+ // Abstract over any insane MySQL defaults
+ $set = [ 'group_concat_max_len = 262144' ];
+ // Set SQL mode, default is turning them all off, can be overridden or skipped with null
+ if ( is_string( $this->sqlMode ) ) {
+ $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode );
+ }
+ // Set any custom settings defined by site config
+ // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
+ foreach ( $this->connectionVariables as $var => $val ) {
+ // Escape strings but not numbers to avoid MySQL complaining
+ if ( !is_int( $val ) && !is_float( $val ) ) {
+ $val = $this->addQuotes( $val );
+ }
+ $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
}
- $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
- }
- if ( $set ) {
- // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
- $success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
- if ( !$success ) {
- $error = $this->lastError();
- $this->queryLogger->error(
- 'Error setting MySQL variables on server {db_server}: {error}',
- $this->getLogContext( [
- 'method' => __METHOD__,
- 'error' => $error,
- ] )
+ if ( $set ) {
+ $this->query(
+ 'SET ' . implode( ', ', $set ),
+ __METHOD__,
+ self::QUERY_IGNORE_DBO_TRX | self::QUERY_NO_RETRY
);
- throw new DBConnectionError( $this, "Error setting MySQL variables: $error" );
}
+ } catch ( Exception $e ) {
+ // Connection was not fully initialized and is not safe for use
+ $this->conn = false;
}
- $this->opened = true;
-
return true;
}
- /**
- * Set the character set information right after connection
- * @return bool
- */
- protected function connectInitCharset() {
- if ( $this->utf8Mode ) {
- // Tell the server we're communicating with it in UTF-8.
- // This may engage various charset conversions.
- return $this->mysqlSetCharset( 'utf8' );
- } else {
- return $this->mysqlSetCharset( 'binary' );
- }
- }
-
protected function doSelectDomain( DatabaseDomain $domain ) {
if ( $domain->getSchema() !== null ) {
- throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+ throw new DBExpectedError(
+ $this,
+ __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
+ );
}
$database = $domain->getDatabase();
*/
abstract protected function mysqlConnect( $realServer, $dbName );
- /**
- * Set the character set of the MySQL link
- *
- * @param string $charset
- * @return bool
- */
- abstract protected function mysqlSetCharset( $charset );
-
/**
* @param IResultWrapper|resource $res
* @throws DBUnexpectedError
*/
public function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- Wikimedia\suppressWarnings();
- $ok = $this->mysqlFreeResult( $res );
- Wikimedia\restoreWarnings();
+ AtEase::suppressWarnings();
+ $ok = $this->mysqlFreeResult( ResultWrapper::unwrap( $res ) );
+ AtEase::restoreWarnings();
if ( !$ok ) {
throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
}
* @throws DBUnexpectedError
*/
public function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- Wikimedia\suppressWarnings();
- $row = $this->mysqlFetchObject( $res );
- Wikimedia\restoreWarnings();
+ AtEase::suppressWarnings();
+ $row = $this->mysqlFetchObject( ResultWrapper::unwrap( $res ) );
+ AtEase::restoreWarnings();
$errno = $this->lastErrno();
// Unfortunately, mysql_fetch_object does not reset the last errno.
* @throws DBUnexpectedError
*/
public function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- Wikimedia\suppressWarnings();
- $row = $this->mysqlFetchArray( $res );
- Wikimedia\restoreWarnings();
+ AtEase::suppressWarnings();
+ $row = $this->mysqlFetchArray( ResultWrapper::unwrap( $res ) );
+ AtEase::restoreWarnings();
$errno = $this->lastErrno();
// Unfortunately, mysql_fetch_array does not reset the last errno.
* @return int
*/
function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
+ if ( is_bool( $res ) ) {
+ $n = 0;
+ } else {
+ AtEase::suppressWarnings();
+ $n = $this->mysqlNumRows( ResultWrapper::unwrap( $res ) );
+ AtEase::restoreWarnings();
}
- Wikimedia\suppressWarnings();
- $n = !is_bool( $res ) ? $this->mysqlNumRows( $res ) : 0;
- Wikimedia\restoreWarnings();
// Unfortunately, mysql_num_rows does not reset the last errno.
// We are not checking for any errors here, since
* @return int
*/
public function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $this->mysqlNumFields( $res );
+ return $this->mysqlNumFields( ResultWrapper::unwrap( $res ) );
}
/**
* @return string
*/
public function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $this->mysqlFieldName( $res, $n );
+ return $this->mysqlFieldName( ResultWrapper::unwrap( $res ), $n );
}
/**
* @return string
*/
public function fieldType( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $this->mysqlFieldType( $res, $n );
+ return $this->mysqlFieldType( ResultWrapper::unwrap( $res ), $n );
}
/**
* @return bool
*/
public function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $this->mysqlDataSeek( $res, $row );
+ return $this->mysqlDataSeek( ResultWrapper::unwrap( $res ), $row );
}
/**
public function lastError() {
if ( $this->conn ) {
# Even if it's non-zero, it can still be invalid
- Wikimedia\suppressWarnings();
+ AtEase::suppressWarnings();
$error = $this->mysqlError( $this->conn );
if ( !$error ) {
$error = $this->mysqlError();
}
- Wikimedia\restoreWarnings();
+ AtEase::restoreWarnings();
} else {
$error = $this->mysqlError();
}
*/
public function fieldInfo( $table, $field ) {
$table = $this->tableName( $table );
- $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
+ $flags = self::QUERY_SILENCE_ERRORS;
+ $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, $flags );
if ( !$res ) {
return false;
}
- $n = $this->mysqlNumFields( $res->result );
+ $n = $this->mysqlNumFields( ResultWrapper::unwrap( $res ) );
for ( $i = 0; $i < $n; $i++ ) {
- $meta = $this->mysqlFetchField( $res->result, $i );
+ $meta = $this->mysqlFetchField( ResultWrapper::unwrap( $res ), $i );
if ( $field == $meta->name ) {
return new MySQLField( $meta );
}
* @return bool|int
*/
protected function getLagFromSlaveStatus() {
- $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+ $flags = self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX;
+ $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__, $flags );
$row = $res ? $res->fetchObject() : false;
// If the server is not replicating, there will be no row
if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
// Connect to and query the master; catch errors to avoid outages
try {
- $res = $conn->query( 'SELECT @@server_id AS id', $fname );
+ $flags = self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX;
+ $res = $conn->query( 'SELECT @@server_id AS id', $fname, $flags );
$row = $res ? $res->fetchObject() : false;
$id = $row ? (int)$row->id : 0;
} catch ( DBError $e ) {
// percision field is not supported in MySQL <= 5.5.
$res = $this->query(
"SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1",
- __METHOD__
+ __METHOD__,
+ self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX
);
$row = $res ? $res->fetchObject() : false;
} finally {
$this->srvCache->makeGlobalKey( 'mysql-server-id', $this->getServer() ),
self::SERVER_ID_CACHE_TTL,
function () use ( $fname ) {
- $res = $this->query( "SELECT @@server_id AS id", $fname );
+ $flags = self::QUERY_IGNORE_DBO_TRX;
+ $res = $this->query( "SELECT @@server_id AS id", $fname, $flags );
+
return intval( $this->fetchObject( $res )->id );
}
);
* @return string|null
*/
protected function getServerUUID() {
+ $fname = __METHOD__;
return $this->srvCache->getWithSetCallback(
$this->srvCache->makeGlobalKey( 'mysql-server-uuid', $this->getServer() ),
self::SERVER_ID_CACHE_TTL,
- function () {
- $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'server_uuid'" );
+ function () use ( $fname ) {
+ $flags = self::QUERY_IGNORE_DBO_TRX;
+ $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'server_uuid'", $fname, $flags );
$row = $this->fetchObject( $res );
return $row ? $row->Value : null;
*/
protected function getServerGTIDs( $fname = __METHOD__ ) {
$map = [];
+
+ $flags = self::QUERY_IGNORE_DBO_TRX;
// Get global-only variables like gtid_executed
- $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_%'", $fname );
+ $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_%'", $fname, $flags );
foreach ( $res as $row ) {
$map[$row->Variable_name] = $row->Value;
}
// Get session-specific (e.g. gtid_domain_id since that is were writes will log)
- $res = $this->query( "SHOW SESSION VARIABLES LIKE 'gtid_%'", $fname );
+ $res = $this->query( "SHOW SESSION VARIABLES LIKE 'gtid_%'", $fname, $flags );
foreach ( $res as $row ) {
$map[$row->Variable_name] = $row->Value;
}
* @return string[] Latest available server status row
*/
protected function getServerRoleStatus( $role, $fname = __METHOD__ ) {
- return $this->query( "SHOW $role STATUS", $fname )->fetchRow() ?: [];
+ $flags = self::QUERY_IGNORE_DBO_TRX;
+
+ return $this->query( "SHOW $role STATUS", $fname, $flags )->fetchRow() ?: [];
}
public function serverIsReadOnly() {
- $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ );
+ $flags = self::QUERY_IGNORE_DBO_TRX;
+ $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__, $flags );
$row = $this->fetchObject( $res );
return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
*/
public function setSessionOptions( array $options ) {
if ( isset( $options['connTimeout'] ) ) {
+ $flags = self::QUERY_IGNORE_DBO_TRX;
$timeout = (int)$options['connTimeout'];
- $this->query( "SET net_read_timeout=$timeout" );
- $this->query( "SET net_write_timeout=$timeout" );
+ $this->query( "SET net_read_timeout=$timeout", __METHOD__, $flags );
+ $this->query( "SET net_write_timeout=$timeout", __METHOD__, $flags );
}
}
}
$encName = $this->addQuotes( $this->makeLockName( $lockName ) );
- $result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method );
- $row = $this->fetchObject( $result );
+
+ $flags = self::QUERY_IGNORE_DBO_TRX;
+ $res = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method, $flags );
+ $row = $this->fetchObject( $res );
return ( $row->lockstatus == 1 );
}
*/
public function lock( $lockName, $method, $timeout = 5 ) {
$encName = $this->addQuotes( $this->makeLockName( $lockName ) );
- $result = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method );
- $row = $this->fetchObject( $result );
+
+ $flags = self::QUERY_IGNORE_DBO_TRX;
+ $res = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method, $flags );
+ $row = $this->fetchObject( $res );
if ( $row->lockstatus == 1 ) {
parent::lock( $lockName, $method, $timeout ); // record
*/
public function unlock( $lockName, $method ) {
$encName = $this->addQuotes( $this->makeLockName( $lockName ) );
- $result = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method );
- $row = $this->fetchObject( $result );
+
+ $flags = self::QUERY_IGNORE_DBO_TRX;
+ $res = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method, $flags );
+ $row = $this->fetchObject( $res );
if ( $row->lockstatus == 1 ) {
parent::unlock( $lockName, $method ); // record
}
$sql = "LOCK TABLES " . implode( ',', $items );
- $this->query( $sql, $method );
+ $this->query( $sql, $method, self::QUERY_IGNORE_DBO_TRX );
return true;
}
protected function doUnlockTables( $method ) {
- $this->query( "UNLOCK TABLES", $method );
+ $this->query( "UNLOCK TABLES", $method, self::QUERY_IGNORE_DBO_TRX );
return true;
}
(bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
}
$encValue = $value ? '1' : '0';
- $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
+ $this->query( "SET sql_big_selects=$encValue", __METHOD__, self::QUERY_IGNORE_DBO_TRX );
}
/**
* @return array
*/
private function getMysqlStatus( $which = "%" ) {
- $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
+ $flags = self::QUERY_IGNORE_DBO_TRX;
+ $res = $this->query( "SHOW STATUS LIKE '{$which}'", __METHOD__, $flags );
$status = [];
foreach ( $res as $row ) {