* @since 1.22
* @see Database
*/
-abstract class DatabaseMysqlBase extends Database {
+abstract class DatabaseMysqlBase extends DatabaseBase {
/** @var MysqlMasterPos */
protected $lastKnownReplicaPos;
/** @var string Method to detect replica DB lag */
protected $sslCAPath;
/** @var string[]|null */
protected $sslCiphers;
+ /** @var string sql_mode value to send on connection */
+ protected $sqlMode;
+ /** @var bool Use experimental UTF-8 transmission encoding */
+ protected $utf8Mode;
+
/** @var string|null */
private $serverVersion = null;
$this->$var = $params[$var];
}
}
+ $this->sqlMode = isset( $params['sqlMode'] ) ? $params['sqlMode'] : '';
+ $this->utf8Mode = !empty( $params['utf8Mode'] );
}
/**
* @return bool
*/
function open( $server, $user, $password, $dbName ) {
- global $wgAllDBsAreLocalhost, $wgSQLMode;
-
# Close/unset connection handle
$this->close();
- # Debugging hack -- fake cluster
- $realServer = $wgAllDBsAreLocalhost ? 'localhost' : $server;
$this->mServer = $server;
$this->mUser = $user;
$this->mPassword = $password;
$this->installErrorHandler();
try {
- $this->mConn = $this->mysqlConnect( $realServer );
+ $this->mConn = $this->mysqlConnect( $this->mServer );
} catch ( Exception $ex ) {
$this->restoreErrorHandler();
throw $ex;
if ( !$error ) {
$error = $this->lastError();
}
- wfLogDBError(
+ $this->queryLogger->error(
"Error connecting to {db_server}: {error}",
$this->getLogContext( [
'method' => __METHOD__,
'error' => $error,
] )
);
- wfDebug( "DB connection error\n" .
+ $this->queryLogger->debug( "DB connection error\n" .
"Server: $server, User: $user, Password: " .
substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
$success = $this->selectDB( $dbName );
MediaWiki\restoreWarnings();
if ( !$success ) {
- wfLogDBError(
+ $this->queryLogger->error(
"Error selecting database {db_name} on server {db_server}",
$this->getLogContext( [
'method' => __METHOD__,
] )
);
- wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
- "from client host " . wfHostname() . "\n" );
+ $this->queryLogger->debug(
+ "Error selecting database $dbName on server {$this->mServer}" );
$this->reportConnectionError( "Error selecting database $dbName" );
}
// 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( $wgSQLMode ) ) {
- $set[] = 'sql_mode = ' . $this->addQuotes( $wgSQLMode );
+ 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)
// Use doQuery() to avoid opening implicit transactions (DBO_TRX)
$success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
if ( !$success ) {
- wfLogDBError(
+ $this->queryLogger->error(
'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
$this->getLogContext( [
'method' => __METHOD__,
* @return bool
*/
protected function connectInitCharset() {
- global $wgDBmysql5;
-
- if ( $wgDBmysql5 ) {
+ if ( $this->utf8Mode ) {
// Tell the server we're communicating with it in UTF-8.
// This may engage various charset conversions.
return $this->mysqlSetCharset( 'utf8' );
// Standard method: use master server ID (works with stock pt-heartbeat)
$masterInfo = $this->getMasterServerInfo();
if ( !$masterInfo ) {
- wfLogDBError(
+ $this->queryLogger->error(
"Unable to query master of {db_server} for server ID",
$this->getLogContext( [
'method' => __METHOD__
return max( $nowUnix - $timeUnix, 0.0 );
}
- wfLogDBError(
+ $this->queryLogger->error(
"Unable to find pt-heartbeat row for {db_server}",
$this->getLogContext( [
'method' => __METHOD__
return true;
}
- wfDebug( __METHOD__ . " failed to acquire lock\n" );
+ $this->queryLogger->debug( __METHOD__ . " failed to acquire lock\n" );
return false;
}
return true;
}
- wfDebug( __METHOD__ . " failed to release lock\n" );
+ $this->queryLogger->debug( __METHOD__ . " failed to release lock\n" );
return false;
}
*/
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
}
}
-/**
- * Utility class.
- * @ingroup Database
- */
-class MySQLField implements Field {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary,
- $is_numeric, $is_blob, $is_unsigned, $is_zerofill;
-
- function __construct( $info ) {
- $this->name = $info->name;
- $this->tablename = $info->table;
- $this->default = $info->def;
- $this->max_length = $info->max_length;
- $this->nullable = !$info->not_null;
- $this->is_pk = $info->primary_key;
- $this->is_unique = $info->unique_key;
- $this->is_multiple = $info->multiple_key;
- $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
- $this->type = $info->type;
- $this->binary = isset( $info->binary ) ? $info->binary : false;
- $this->is_numeric = isset( $info->numeric ) ? $info->numeric : false;
- $this->is_blob = isset( $info->blob ) ? $info->blob : false;
- $this->is_unsigned = isset( $info->unsigned ) ? $info->unsigned : false;
- $this->is_zerofill = isset( $info->zerofill ) ? $info->zerofill : false;
- }
-
- /**
- * @return string
- */
- function name() {
- return $this->name;
- }
-
- /**
- * @return string
- */
- function tableName() {
- return $this->tablename;
- }
-
- /**
- * @return string
- */
- function type() {
- return $this->type;
- }
-
- /**
- * @return bool
- */
- function isNullable() {
- return $this->nullable;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- /**
- * @return bool
- */
- function isKey() {
- return $this->is_key;
- }
-
- /**
- * @return bool
- */
- function isMultipleKey() {
- return $this->is_multiple;
- }
-
- /**
- * @return bool
- */
- function isBinary() {
- return $this->binary;
- }
-
- /**
- * @return bool
- */
- function isNumeric() {
- return $this->is_numeric;
- }
-
- /**
- * @return bool
- */
- function isBlob() {
- return $this->is_blob;
- }
-
- /**
- * @return bool
- */
- function isUnsigned() {
- return $this->is_unsigned;
- }
-
- /**
- * @return bool
- */
- function isZerofill() {
- return $this->is_zerofill;
- }
-}
-
-/**
- * DBMasterPos class for MySQL/MariaDB
- *
- * Note that master positions and sync logic here make some assumptions:
- * - Binlog-based usage assumes single-source replication and non-hierarchical replication.
- * - GTID-based usage allows getting/syncing with multi-source replication. It is assumed
- * that GTID sets are complete (e.g. include all domains on the server).
- */
-class MySQLMasterPos implements DBMasterPos {
- /** @var string Binlog file */
- public $file;
- /** @var int Binglog file position */
- public $pos;
- /** @var string[] GTID list */
- public $gtids = [];
- /** @var float UNIX timestamp */
- public $asOfTime = 0.0;
-
- /**
- * @param string $file Binlog file name
- * @param integer $pos Binlog position
- * @param string $gtid Comma separated GTID set [optional]
- */
- function __construct( $file, $pos, $gtid = '' ) {
- $this->file = $file;
- $this->pos = $pos;
- $this->gtids = array_map( 'trim', explode( ',', $gtid ) );
- $this->asOfTime = microtime( true );
- }
-
- /**
- * @return string <binlog file>/<position>, e.g db1034-bin.000976/843431247
- */
- function __toString() {
- return "{$this->file}/{$this->pos}";
- }
-
- function asOfTime() {
- return $this->asOfTime;
- }
-
- function hasReached( DBMasterPos $pos ) {
- if ( !( $pos instanceof self ) ) {
- throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
- }
-
- // Prefer GTID comparisons, which work with multi-tier replication
- $thisPosByDomain = $this->getGtidCoordinates();
- $thatPosByDomain = $pos->getGtidCoordinates();
- if ( $thisPosByDomain && $thatPosByDomain ) {
- $reached = true;
- // Check that this has positions GTE all of those in $pos for all domains in $pos
- foreach ( $thatPosByDomain as $domain => $thatPos ) {
- $thisPos = isset( $thisPosByDomain[$domain] ) ? $thisPosByDomain[$domain] : -1;
- $reached = $reached && ( $thatPos <= $thisPos );
- }
-
- return $reached;
- }
-
- // Fallback to the binlog file comparisons
- $thisBinPos = $this->getBinlogCoordinates();
- $thatBinPos = $pos->getBinlogCoordinates();
- if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
- return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
- }
-
- // Comparing totally different binlogs does not make sense
- return false;
- }
-
- function channelsMatch( DBMasterPos $pos ) {
- if ( !( $pos instanceof self ) ) {
- throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
- }
-
- // Prefer GTID comparisons, which work with multi-tier replication
- $thisPosDomains = array_keys( $this->getGtidCoordinates() );
- $thatPosDomains = array_keys( $pos->getGtidCoordinates() );
- if ( $thisPosDomains && $thatPosDomains ) {
- // Check that this has GTIDs for all domains in $pos
- return !array_diff( $thatPosDomains, $thisPosDomains );
- }
-
- // Fallback to the binlog file comparisons
- $thisBinPos = $this->getBinlogCoordinates();
- $thatBinPos = $pos->getBinlogCoordinates();
-
- return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
- }
-
- /**
- * @note: this returns false for multi-source replication GTID sets
- * @see https://mariadb.com/kb/en/mariadb/gtid
- * @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
- * @return array Map of (domain => integer position) or false
- */
- protected function getGtidCoordinates() {
- $gtidInfos = [];
- foreach ( $this->gtids as $gtid ) {
- $m = [];
- // MariaDB style: <domain>-<server id>-<sequence number>
- if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
- $gtidInfos[(int)$m[1]] = (int)$m[2];
- // MySQL style: <UUID domain>:<sequence number>
- } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
- $gtidInfos[$m[1]] = (int)$m[2];
- } else {
- $gtidInfos = [];
- break; // unrecognized GTID
- }
-
- }
-
- return $gtidInfos;
- }
-
- /**
- * @see http://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
- * @see http://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
- * @return array|bool (binlog, (integer file number, integer position)) or false
- */
- protected function getBinlogCoordinates() {
- $m = [];
- if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
- return [ 'binlog' => $m[1], 'pos' => [ (int)$m[2], (int)$m[3] ] ];
- }
-
- return false;
- }
-}