2009-06-15 18:39:41 +00:00
|
|
|
<?php
|
2010-08-01 21:13:44 +00:00
|
|
|
/**
|
|
|
|
|
* This is the MySQL database abstraction layer.
|
|
|
|
|
*
|
|
|
|
|
* @file
|
|
|
|
|
* @ingroup Database
|
|
|
|
|
*/
|
|
|
|
|
|
2009-06-15 18:39:41 +00:00
|
|
|
/**
|
|
|
|
|
* Database abstraction object for mySQL
|
|
|
|
|
* Inherit all methods and properties of Database::Database()
|
|
|
|
|
*
|
|
|
|
|
* @ingroup Database
|
|
|
|
|
* @see Database
|
|
|
|
|
*/
|
|
|
|
|
class DatabaseMysql extends DatabaseBase {
|
2011-09-16 17:58:50 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2010-01-08 00:31:24 +00:00
|
|
|
function getType() {
|
|
|
|
|
return 'mysql';
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $sql string
|
2011-11-29 21:04:20 +00:00
|
|
|
* @return resource
|
|
|
|
|
*/
|
2011-06-20 12:09:22 +00:00
|
|
|
protected function doQuery( $sql ) {
|
2009-06-15 18:39:41 +00:00
|
|
|
if( $this->bufferResults() ) {
|
|
|
|
|
$ret = mysql_query( $sql, $this->mConn );
|
|
|
|
|
} else {
|
|
|
|
|
$ret = mysql_unbuffered_query( $sql, $this->mConn );
|
|
|
|
|
}
|
|
|
|
|
return $ret;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $server string
|
|
|
|
|
* @param $user string
|
|
|
|
|
* @param $password string
|
|
|
|
|
* @param $dbName string
|
2011-11-29 21:04:20 +00:00
|
|
|
* @return bool
|
|
|
|
|
* @throws DBConnectionError
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function open( $server, $user, $password, $dbName ) {
|
|
|
|
|
global $wgAllDBsAreLocalhost;
|
|
|
|
|
wfProfileIn( __METHOD__ );
|
|
|
|
|
|
2010-08-26 22:39:14 +00:00
|
|
|
# Load mysql.so if we don't have it
|
2010-06-14 18:09:19 +00:00
|
|
|
wfDl( 'mysql' );
|
2009-06-15 18:39:41 +00:00
|
|
|
|
|
|
|
|
# Fail now
|
|
|
|
|
# Otherwise we get a suppressed fatal error, which is very hard to track down
|
|
|
|
|
if ( !function_exists( 'mysql_connect' ) ) {
|
|
|
|
|
throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Debugging hack -- fake cluster
|
|
|
|
|
if ( $wgAllDBsAreLocalhost ) {
|
|
|
|
|
$realServer = 'localhost';
|
|
|
|
|
} else {
|
|
|
|
|
$realServer = $server;
|
|
|
|
|
}
|
|
|
|
|
$this->close();
|
|
|
|
|
$this->mServer = $server;
|
|
|
|
|
$this->mUser = $user;
|
|
|
|
|
$this->mPassword = $password;
|
|
|
|
|
$this->mDBname = $dbName;
|
|
|
|
|
|
|
|
|
|
wfProfileIn("dbconnect-$server");
|
|
|
|
|
|
|
|
|
|
# The kernel's default SYN retransmission period is far too slow for us,
|
|
|
|
|
# so we use a short timeout plus a manual retry. Retrying means that a small
|
|
|
|
|
# but finite rate of SYN packet loss won't cause user-visible errors.
|
|
|
|
|
$this->mConn = false;
|
|
|
|
|
if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) {
|
|
|
|
|
$numAttempts = 2;
|
|
|
|
|
} else {
|
|
|
|
|
$numAttempts = 1;
|
|
|
|
|
}
|
|
|
|
|
$this->installErrorHandler();
|
|
|
|
|
for ( $i = 0; $i < $numAttempts && !$this->mConn; $i++ ) {
|
|
|
|
|
if ( $i > 1 ) {
|
|
|
|
|
usleep( 1000 );
|
|
|
|
|
}
|
|
|
|
|
if ( $this->mFlags & DBO_PERSISTENT ) {
|
|
|
|
|
$this->mConn = mysql_pconnect( $realServer, $user, $password );
|
|
|
|
|
} else {
|
|
|
|
|
# Create a new connection...
|
|
|
|
|
$this->mConn = mysql_connect( $realServer, $user, $password, true );
|
|
|
|
|
}
|
2010-10-24 21:02:57 +00:00
|
|
|
#if ( $this->mConn === false ) {
|
2009-06-15 18:39:41 +00:00
|
|
|
#$iplus = $i + 1;
|
2010-05-19 02:12:59 +00:00
|
|
|
#wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
|
2010-10-24 21:02:57 +00:00
|
|
|
#}
|
2009-06-15 18:39:41 +00:00
|
|
|
}
|
|
|
|
|
$phpError = $this->restoreErrorHandler();
|
|
|
|
|
# Always log connection errors
|
|
|
|
|
if ( !$this->mConn ) {
|
|
|
|
|
$error = $this->lastError();
|
|
|
|
|
if ( !$error ) {
|
|
|
|
|
$error = $phpError;
|
|
|
|
|
}
|
|
|
|
|
wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
|
|
|
|
|
wfDebug( "DB connection error\n" );
|
|
|
|
|
wfDebug( "Server: $server, User: $user, Password: " .
|
|
|
|
|
substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" );
|
|
|
|
|
}
|
2010-05-19 02:12:59 +00:00
|
|
|
|
2009-06-15 18:39:41 +00:00
|
|
|
wfProfileOut("dbconnect-$server");
|
|
|
|
|
|
|
|
|
|
if ( $dbName != '' && $this->mConn !== false ) {
|
2011-07-04 15:00:30 +00:00
|
|
|
wfSuppressWarnings();
|
|
|
|
|
$success = mysql_select_db( $dbName, $this->mConn );
|
|
|
|
|
wfRestoreWarnings();
|
2009-06-15 18:39:41 +00:00
|
|
|
if ( !$success ) {
|
|
|
|
|
$error = "Error selecting database $dbName on server {$this->mServer} " .
|
|
|
|
|
"from client host " . wfHostname() . "\n";
|
|
|
|
|
wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n");
|
|
|
|
|
wfDebug( $error );
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
# Delay USE query
|
|
|
|
|
$success = (bool)$this->mConn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $success ) {
|
2011-11-23 16:27:48 +00:00
|
|
|
// Tell the server we're communicating with it in UTF-8.
|
|
|
|
|
// This may engage various charset conversions.
|
|
|
|
|
global $wgDBmysql5;
|
|
|
|
|
if( $wgDBmysql5 ) {
|
|
|
|
|
$this->query( 'SET NAMES utf8', __METHOD__ );
|
|
|
|
|
} else {
|
|
|
|
|
$this->query( 'SET NAMES binary', __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
// Set SQL mode, default is turning them all off, can be overridden or skipped with null
|
|
|
|
|
global $wgSQLMode;
|
|
|
|
|
if ( is_string( $wgSQLMode ) ) {
|
|
|
|
|
$mode = $this->addQuotes( $wgSQLMode );
|
|
|
|
|
$this->query( "SET sql_mode = $mode", __METHOD__ );
|
2009-06-15 18:39:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Turn off strict mode if it is on
|
|
|
|
|
} else {
|
|
|
|
|
$this->reportConnectionError( $phpError );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->mOpened = $success;
|
|
|
|
|
wfProfileOut( __METHOD__ );
|
|
|
|
|
return $success;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function close() {
|
|
|
|
|
$this->mOpened = false;
|
|
|
|
|
if ( $this->mConn ) {
|
|
|
|
|
if ( $this->trxLevel() ) {
|
2009-12-14 23:05:35 +00:00
|
|
|
$this->commit();
|
2009-06-15 18:39:41 +00:00
|
|
|
}
|
|
|
|
|
return mysql_close( $this->mConn );
|
|
|
|
|
} else {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $res ResultWrapper
|
2011-11-29 21:04:20 +00:00
|
|
|
* @throws DBUnexpectedError
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function freeResult( $res ) {
|
|
|
|
|
if ( $res instanceof ResultWrapper ) {
|
|
|
|
|
$res = $res->result;
|
|
|
|
|
}
|
2011-07-04 15:00:30 +00:00
|
|
|
wfSuppressWarnings();
|
|
|
|
|
$ok = mysql_free_result( $res );
|
|
|
|
|
wfRestoreWarnings();
|
|
|
|
|
if ( !$ok ) {
|
2009-06-15 18:39:41 +00:00
|
|
|
throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $res ResultWrapper
|
2011-11-29 21:04:20 +00:00
|
|
|
* @return object|stdClass
|
|
|
|
|
* @throws DBUnexpectedError
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function fetchObject( $res ) {
|
|
|
|
|
if ( $res instanceof ResultWrapper ) {
|
|
|
|
|
$res = $res->result;
|
|
|
|
|
}
|
2011-07-04 15:00:30 +00:00
|
|
|
wfSuppressWarnings();
|
|
|
|
|
$row = mysql_fetch_object( $res );
|
|
|
|
|
wfRestoreWarnings();
|
2009-06-15 18:39:41 +00:00
|
|
|
if( $this->lastErrno() ) {
|
|
|
|
|
throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
|
|
|
|
|
}
|
|
|
|
|
return $row;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $res ResultWrapper
|
2011-11-29 21:04:20 +00:00
|
|
|
* @return array
|
|
|
|
|
* @throws DBUnexpectedError
|
|
|
|
|
*/
|
2011-08-01 16:01:13 +00:00
|
|
|
function fetchRow( $res ) {
|
2009-06-15 18:39:41 +00:00
|
|
|
if ( $res instanceof ResultWrapper ) {
|
|
|
|
|
$res = $res->result;
|
|
|
|
|
}
|
2011-07-04 15:00:30 +00:00
|
|
|
wfSuppressWarnings();
|
|
|
|
|
$row = mysql_fetch_array( $res );
|
|
|
|
|
wfRestoreWarnings();
|
2009-06-15 18:39:41 +00:00
|
|
|
if ( $this->lastErrno() ) {
|
|
|
|
|
throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
|
|
|
|
|
}
|
|
|
|
|
return $row;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @throws DBUnexpectedError
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $res ResultWrapper
|
2011-09-16 17:58:50 +00:00
|
|
|
* @return int
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function numRows( $res ) {
|
|
|
|
|
if ( $res instanceof ResultWrapper ) {
|
|
|
|
|
$res = $res->result;
|
|
|
|
|
}
|
2011-07-04 15:00:30 +00:00
|
|
|
wfSuppressWarnings();
|
|
|
|
|
$n = mysql_num_rows( $res );
|
|
|
|
|
wfRestoreWarnings();
|
2009-06-15 18:39:41 +00:00
|
|
|
if( $this->lastErrno() ) {
|
|
|
|
|
throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
|
|
|
|
|
}
|
|
|
|
|
return $n;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $res ResultWrapper
|
2011-09-16 17:58:50 +00:00
|
|
|
* @return int
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function numFields( $res ) {
|
|
|
|
|
if ( $res instanceof ResultWrapper ) {
|
|
|
|
|
$res = $res->result;
|
|
|
|
|
}
|
|
|
|
|
return mysql_num_fields( $res );
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $res ResultWrapper
|
|
|
|
|
* @param $n string
|
2011-09-16 17:58:50 +00:00
|
|
|
* @return string
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function fieldName( $res, $n ) {
|
|
|
|
|
if ( $res instanceof ResultWrapper ) {
|
|
|
|
|
$res = $res->result;
|
|
|
|
|
}
|
|
|
|
|
return mysql_field_name( $res, $n );
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return int
|
|
|
|
|
*/
|
|
|
|
|
function insertId() {
|
|
|
|
|
return mysql_insert_id( $this->mConn );
|
|
|
|
|
}
|
2009-06-15 18:39:41 +00:00
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $res ResultWrapper
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $row
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function dataSeek( $res, $row ) {
|
|
|
|
|
if ( $res instanceof ResultWrapper ) {
|
|
|
|
|
$res = $res->result;
|
|
|
|
|
}
|
|
|
|
|
return mysql_data_seek( $res, $row );
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* @return int
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function lastErrno() {
|
|
|
|
|
if ( $this->mConn ) {
|
|
|
|
|
return mysql_errno( $this->mConn );
|
|
|
|
|
} else {
|
|
|
|
|
return mysql_errno();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function lastError() {
|
|
|
|
|
if ( $this->mConn ) {
|
|
|
|
|
# Even if it's non-zero, it can still be invalid
|
|
|
|
|
wfSuppressWarnings();
|
|
|
|
|
$error = mysql_error( $this->mConn );
|
|
|
|
|
if ( !$error ) {
|
|
|
|
|
$error = mysql_error();
|
|
|
|
|
}
|
|
|
|
|
wfRestoreWarnings();
|
|
|
|
|
} else {
|
|
|
|
|
$error = mysql_error();
|
|
|
|
|
}
|
|
|
|
|
if( $error ) {
|
|
|
|
|
$error .= ' (' . $this->mServer . ')';
|
|
|
|
|
}
|
|
|
|
|
return $error;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return int
|
|
|
|
|
*/
|
|
|
|
|
function affectedRows() {
|
|
|
|
|
return mysql_affected_rows( $this->mConn );
|
|
|
|
|
}
|
2010-05-19 02:12:59 +00:00
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $table string
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $uniqueIndexes
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $rows array
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $fname string
|
|
|
|
|
* @return ResultWrapper
|
|
|
|
|
*/
|
2011-06-23 03:14:11 +00:00
|
|
|
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMysql::replace' ) {
|
|
|
|
|
return $this->nativeReplace( $table, $rows, $fname );
|
|
|
|
|
}
|
|
|
|
|
|
2009-10-21 12:21:09 +00:00
|
|
|
/**
|
|
|
|
|
* Estimate rows in dataset
|
|
|
|
|
* Returns estimated count, based on EXPLAIN output
|
|
|
|
|
* Takes same arguments as Database::select()
|
2011-09-16 17:58:50 +00:00
|
|
|
*
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $table string|array
|
|
|
|
|
* @param $vars string|array
|
|
|
|
|
* @param $conds string|array
|
|
|
|
|
* @param $fname string
|
|
|
|
|
* @param $options string|array
|
2011-09-16 17:58:50 +00:00
|
|
|
* @return int
|
2009-10-21 12:21:09 +00:00
|
|
|
*/
|
2010-11-23 11:21:00 +00:00
|
|
|
public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) {
|
2009-10-21 12:21:09 +00:00
|
|
|
$options['EXPLAIN'] = true;
|
|
|
|
|
$res = $this->select( $table, $vars, $conds, $fname, $options );
|
2010-08-25 17:45:00 +00:00
|
|
|
if ( $res === false ) {
|
2009-10-21 12:21:09 +00:00
|
|
|
return false;
|
2010-08-25 17:45:00 +00:00
|
|
|
}
|
2009-10-21 12:21:09 +00:00
|
|
|
if ( !$this->numRows( $res ) ) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rows = 1;
|
2010-10-13 23:11:40 +00:00
|
|
|
foreach ( $res as $plan ) {
|
2009-10-21 12:21:09 +00:00
|
|
|
$rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
|
|
|
|
|
}
|
2010-05-19 02:12:59 +00:00
|
|
|
return $rows;
|
2009-10-21 12:21:09 +00:00
|
|
|
}
|
2009-06-15 18:39:41 +00:00
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @param $table string
|
|
|
|
|
* @param $field string
|
|
|
|
|
* @return bool|MySQLField
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function fieldInfo( $table, $field ) {
|
|
|
|
|
$table = $this->tableName( $table );
|
2010-07-02 10:01:09 +00:00
|
|
|
$res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
|
|
|
|
|
if ( !$res ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2009-06-15 18:39:41 +00:00
|
|
|
$n = mysql_num_fields( $res->result );
|
|
|
|
|
for( $i = 0; $i < $n; $i++ ) {
|
|
|
|
|
$meta = mysql_fetch_field( $res->result, $i );
|
|
|
|
|
if( $field == $meta->name ) {
|
|
|
|
|
return new MySQLField($meta);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-23 11:14:48 +00:00
|
|
|
/**
|
|
|
|
|
* Get information about an index into an object
|
|
|
|
|
* Returns false if the index does not exist
|
2011-09-16 17:58:50 +00:00
|
|
|
*
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $table string
|
|
|
|
|
* @param $index string
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $fname string
|
2011-09-16 17:58:50 +00:00
|
|
|
* @return false|array
|
2010-11-23 11:14:48 +00:00
|
|
|
*/
|
|
|
|
|
function indexInfo( $table, $index, $fname = 'DatabaseMysql::indexInfo' ) {
|
|
|
|
|
# SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
|
|
|
|
|
# SHOW INDEX should work for 3.x and up:
|
|
|
|
|
# http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
|
|
|
|
|
$table = $this->tableName( $table );
|
|
|
|
|
$index = $this->indexName( $index );
|
|
|
|
|
$sql = 'SHOW INDEX FROM ' . $table;
|
|
|
|
|
$res = $this->query( $sql, $fname );
|
|
|
|
|
|
|
|
|
|
if ( !$res ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$result = array();
|
|
|
|
|
|
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
|
if ( $row->Key_name == $index ) {
|
|
|
|
|
$result[] = $row;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return empty( $result ) ? false : $result;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @param $db
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function selectDB( $db ) {
|
|
|
|
|
$this->mDBname = $db;
|
|
|
|
|
return mysql_select_db( $db, $this->mConn );
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @param $s string
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function strencode( $s ) {
|
2010-05-19 02:12:59 +00:00
|
|
|
$sQuoted = mysql_real_escape_string( $s, $this->mConn );
|
|
|
|
|
|
|
|
|
|
if($sQuoted === false) {
|
|
|
|
|
$this->ping();
|
|
|
|
|
$sQuoted = mysql_real_escape_string( $s, $this->mConn );
|
|
|
|
|
}
|
|
|
|
|
return $sQuoted;
|
2009-06-15 18:39:41 +00:00
|
|
|
}
|
|
|
|
|
|
2010-12-04 15:35:36 +00:00
|
|
|
/**
|
|
|
|
|
* MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
|
2011-09-16 17:58:50 +00:00
|
|
|
*
|
|
|
|
|
* @param $s string
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
2010-12-04 15:35:36 +00:00
|
|
|
*/
|
|
|
|
|
public function addIdentifierQuotes( $s ) {
|
|
|
|
|
return "`" . $this->strencode( $s ) . "`";
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @param $name string
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2011-04-12 16:34:12 +00:00
|
|
|
public function isQuotedIdentifier( $name ) {
|
2011-09-16 17:58:50 +00:00
|
|
|
return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
|
2011-04-12 16:34:12 +00:00
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function ping() {
|
|
|
|
|
$ping = mysql_ping( $this->mConn );
|
|
|
|
|
if ( $ping ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2010-05-19 02:12:59 +00:00
|
|
|
mysql_close( $this->mConn );
|
|
|
|
|
$this->mOpened = false;
|
|
|
|
|
$this->mConn = false;
|
|
|
|
|
$this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
|
|
|
|
|
return true;
|
2009-06-15 18:39:41 +00:00
|
|
|
}
|
|
|
|
|
|
2010-07-02 13:17:28 +00:00
|
|
|
/**
|
|
|
|
|
* Returns slave lag.
|
2011-06-23 03:14:11 +00:00
|
|
|
*
|
2011-11-25 00:16:34 +00:00
|
|
|
* This will do a SHOW SLAVE STATUS
|
2011-06-23 03:14:11 +00:00
|
|
|
*
|
2011-08-14 19:49:07 +00:00
|
|
|
* @return int
|
2010-07-02 13:17:28 +00:00
|
|
|
*/
|
|
|
|
|
function getLag() {
|
|
|
|
|
if ( !is_null( $this->mFakeSlaveLag ) ) {
|
|
|
|
|
wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
|
|
|
|
|
return $this->mFakeSlaveLag;
|
|
|
|
|
}
|
2011-06-23 03:14:11 +00:00
|
|
|
|
2011-11-23 16:27:48 +00:00
|
|
|
return $this->getLagFromSlaveStatus();
|
2011-06-23 03:14:11 +00:00
|
|
|
}
|
|
|
|
|
|
2011-08-14 19:49:07 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool|int
|
|
|
|
|
*/
|
2011-06-23 03:14:11 +00:00
|
|
|
function getLagFromSlaveStatus() {
|
|
|
|
|
$res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
|
|
|
|
|
if ( !$res ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$row = $res->fetchObject();
|
|
|
|
|
if ( !$row ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( strval( $row->Seconds_Behind_Master ) === '' ) {
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
return intval( $row->Seconds_Behind_Master );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-14 19:49:07 +00:00
|
|
|
/**
|
2011-11-23 16:27:48 +00:00
|
|
|
* @deprecated in 1.19, use getLagFromSlaveStatus
|
|
|
|
|
*
|
2011-08-14 19:49:07 +00:00
|
|
|
* @return bool|int
|
|
|
|
|
*/
|
2011-06-23 03:14:11 +00:00
|
|
|
function getLagFromProcesslist() {
|
2011-12-13 05:19:05 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.19' );
|
2010-07-02 13:17:28 +00:00
|
|
|
$res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
|
2010-09-28 11:39:10 +00:00
|
|
|
if( !$res ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2010-07-02 13:17:28 +00:00
|
|
|
# Find slave SQL thread
|
2010-09-28 11:39:10 +00:00
|
|
|
foreach( $res as $row ) {
|
2010-07-02 13:17:28 +00:00
|
|
|
/* This should work for most situations - when default db
|
|
|
|
|
* for thread is not specified, it had no events executed,
|
|
|
|
|
* and therefore it doesn't know yet how lagged it is.
|
|
|
|
|
*
|
|
|
|
|
* Relay log I/O thread does not select databases.
|
|
|
|
|
*/
|
|
|
|
|
if ( $row->User == 'system user' &&
|
|
|
|
|
$row->State != 'Waiting for master to send event' &&
|
|
|
|
|
$row->State != 'Connecting to master' &&
|
|
|
|
|
$row->State != 'Queueing master event to the relay log' &&
|
|
|
|
|
$row->State != 'Waiting for master update' &&
|
|
|
|
|
$row->State != 'Requesting binlog dump' &&
|
|
|
|
|
$row->State != 'Waiting to reconnect after a failed master event read' &&
|
|
|
|
|
$row->State != 'Reconnecting after a failed master event read' &&
|
|
|
|
|
$row->State != 'Registering slave on master'
|
|
|
|
|
) {
|
|
|
|
|
# This is it, return the time (except -ve)
|
|
|
|
|
if ( $row->Time > 0x7fffffff ) {
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
return $row->Time;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2011-08-01 16:01:13 +00:00
|
|
|
|
2011-06-23 03:14:11 +00:00
|
|
|
/**
|
|
|
|
|
* Wait for the slave to catch up to a given master position.
|
|
|
|
|
*
|
|
|
|
|
* @param $pos DBMasterPos object
|
|
|
|
|
* @param $timeout Integer: the maximum number of seconds to wait for synchronisation
|
2011-11-29 21:04:20 +00:00
|
|
|
* @return bool|string
|
2011-06-23 03:14:11 +00:00
|
|
|
*/
|
|
|
|
|
function masterPosWait( DBMasterPos $pos, $timeout ) {
|
|
|
|
|
$fname = 'DatabaseBase::masterPosWait';
|
|
|
|
|
wfProfileIn( $fname );
|
|
|
|
|
|
|
|
|
|
# Commit any open transactions
|
|
|
|
|
if ( $this->mTrxLevel ) {
|
|
|
|
|
$this->commit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !is_null( $this->mFakeSlaveLag ) ) {
|
|
|
|
|
$status = parent::masterPosWait( $pos, $timeout );
|
|
|
|
|
wfProfileOut( $fname );
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
|
|
|
|
|
$encFile = $this->addQuotes( $pos->file );
|
|
|
|
|
$encPos = intval( $pos->pos );
|
|
|
|
|
$sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
|
|
|
|
|
$res = $this->doQuery( $sql );
|
|
|
|
|
|
|
|
|
|
if ( $res && $row = $this->fetchRow( $res ) ) {
|
|
|
|
|
wfProfileOut( $fname );
|
|
|
|
|
return $row[0];
|
|
|
|
|
} else {
|
|
|
|
|
wfProfileOut( $fname );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the position of the master from SHOW SLAVE STATUS
|
|
|
|
|
*
|
|
|
|
|
* @return MySQLMasterPos|false
|
|
|
|
|
*/
|
|
|
|
|
function getSlavePos() {
|
|
|
|
|
if ( !is_null( $this->mFakeSlaveLag ) ) {
|
|
|
|
|
return parent::getSlavePos();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
|
|
|
|
|
$row = $this->fetchObject( $res );
|
|
|
|
|
|
|
|
|
|
if ( $row ) {
|
|
|
|
|
$pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
|
|
|
|
|
return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the position of the master from SHOW MASTER STATUS
|
|
|
|
|
*
|
|
|
|
|
* @return MySQLMasterPos|false
|
|
|
|
|
*/
|
|
|
|
|
function getMasterPos() {
|
|
|
|
|
if ( $this->mFakeMaster ) {
|
|
|
|
|
return parent::getMasterPos();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
|
|
|
|
|
$row = $this->fetchObject( $res );
|
|
|
|
|
|
|
|
|
|
if ( $row ) {
|
|
|
|
|
return new MySQLMasterPos( $row->File, $row->Position );
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-07-02 13:17:28 +00:00
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2009-06-15 18:39:41 +00:00
|
|
|
function getServerVersion() {
|
|
|
|
|
return mysql_get_server_info( $this->mConn );
|
|
|
|
|
}
|
2009-06-16 21:00:38 +00:00
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @param $index
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2009-06-16 21:00:38 +00:00
|
|
|
function useIndexClause( $index ) {
|
|
|
|
|
return "FORCE INDEX (" . $this->indexName( $index ) . ")";
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2009-06-16 21:00:38 +00:00
|
|
|
function lowPriorityOption() {
|
|
|
|
|
return 'LOW_PRIORITY';
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2010-08-22 20:55:07 +00:00
|
|
|
public static function getSoftwareLink() {
|
2009-06-16 21:00:38 +00:00
|
|
|
return '[http://www.mysql.com/ MySQL]';
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2009-07-20 02:20:15 +00:00
|
|
|
function standardSelectDistinct() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* @param $options array
|
|
|
|
|
*/
|
2011-11-23 19:25:59 +00:00
|
|
|
public function setSessionOptions( array $options ) {
|
|
|
|
|
if ( isset( $options['connTimeout'] ) ) {
|
|
|
|
|
$timeout = (int)$options['connTimeout'];
|
|
|
|
|
$this->query( "SET net_read_timeout=$timeout" );
|
|
|
|
|
$this->query( "SET net_write_timeout=$timeout" );
|
|
|
|
|
}
|
2009-06-16 21:00:38 +00:00
|
|
|
}
|
2009-06-25 00:40:12 +00:00
|
|
|
|
2012-01-11 20:19:55 +00:00
|
|
|
protected function streamStatementEnd( &$sql, &$newLine ) {
|
|
|
|
|
if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
|
|
|
|
|
preg_match( '/^DELIMITER\s+(\S+)/' , $newLine, $m );
|
|
|
|
|
$this->delimiter = $m[1];
|
|
|
|
|
$newLine = '';
|
|
|
|
|
}
|
|
|
|
|
return parent::streamStatementEnd( $sql, $newLine );
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $lockName string
|
|
|
|
|
* @param $method string
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $timeout int
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2009-06-25 00:40:12 +00:00
|
|
|
public function lock( $lockName, $method, $timeout = 5 ) {
|
|
|
|
|
$lockName = $this->addQuotes( $lockName );
|
|
|
|
|
$result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
|
|
|
|
|
$row = $this->fetchObject( $result );
|
|
|
|
|
|
|
|
|
|
if( $row->lockstatus == 1 ) {
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
wfDebug( __METHOD__." failed to acquire lock\n" );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-10 10:15:59 +00:00
|
|
|
/**
|
|
|
|
|
* FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $lockName string
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $method string
|
2011-12-05 16:50:58 +00:00
|
|
|
* @return bool
|
2010-07-10 10:15:59 +00:00
|
|
|
*/
|
2009-06-25 00:40:12 +00:00
|
|
|
public function unlock( $lockName, $method ) {
|
|
|
|
|
$lockName = $this->addQuotes( $lockName );
|
|
|
|
|
$result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
|
|
|
|
|
$row = $this->fetchObject( $result );
|
|
|
|
|
return $row->lockstatus;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $read array
|
|
|
|
|
* @param $write array
|
|
|
|
|
* @param $method string
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $lowPriority bool
|
|
|
|
|
*/
|
2009-07-29 23:41:16 +00:00
|
|
|
public function lockTables( $read, $write, $method, $lowPriority = true ) {
|
2009-06-25 00:40:12 +00:00
|
|
|
$items = array();
|
|
|
|
|
|
|
|
|
|
foreach( $write as $table ) {
|
2010-05-19 02:12:59 +00:00
|
|
|
$tbl = $this->tableName( $table ) .
|
|
|
|
|
( $lowPriority ? ' LOW_PRIORITY' : '' ) .
|
2009-07-29 23:41:16 +00:00
|
|
|
' WRITE';
|
|
|
|
|
$items[] = $tbl;
|
2009-06-25 00:40:12 +00:00
|
|
|
}
|
|
|
|
|
foreach( $read as $table ) {
|
|
|
|
|
$items[] = $this->tableName( $table ) . ' READ';
|
|
|
|
|
}
|
|
|
|
|
$sql = "LOCK TABLES " . implode( ',', $items );
|
2009-07-27 23:36:30 +00:00
|
|
|
$this->query( $sql, $method );
|
2009-06-25 00:40:12 +00:00
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* @param $method string
|
|
|
|
|
*/
|
2009-06-25 00:40:12 +00:00
|
|
|
public function unlockTables( $method ) {
|
|
|
|
|
$this->query( "UNLOCK TABLES", $method );
|
|
|
|
|
}
|
2010-01-18 20:54:43 +00:00
|
|
|
|
2010-07-10 10:15:59 +00:00
|
|
|
/**
|
|
|
|
|
* Get search engine class. All subclasses of this
|
|
|
|
|
* need to implement this if they wish to use searching.
|
|
|
|
|
*
|
|
|
|
|
* @return String
|
|
|
|
|
*/
|
|
|
|
|
public function getSearchEngine() {
|
|
|
|
|
return 'SearchMySQL';
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* @param bool $value
|
|
|
|
|
* @return mixed
|
|
|
|
|
*/
|
2009-07-09 00:14:43 +00:00
|
|
|
public function setBigSelects( $value = true ) {
|
|
|
|
|
if ( $value === 'default' ) {
|
|
|
|
|
if ( $this->mDefaultBigSelects === null ) {
|
|
|
|
|
# Function hasn't been called before so it must already be set to the default
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
$value = $this->mDefaultBigSelects;
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $this->mDefaultBigSelects === null ) {
|
|
|
|
|
$this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' );
|
|
|
|
|
}
|
|
|
|
|
$encValue = $value ? '1' : '0';
|
|
|
|
|
$this->query( "SET sql_big_selects=$encValue", __METHOD__ );
|
|
|
|
|
}
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
|
2011-06-18 20:26:31 +00:00
|
|
|
/**
|
|
|
|
|
* DELETE where the condition is a join. MySql uses multi-table deletes.
|
2011-12-05 16:50:58 +00:00
|
|
|
* @param $delTable string
|
|
|
|
|
* @param $joinTable string
|
|
|
|
|
* @param $delVar string
|
|
|
|
|
* @param $joinVar string
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $conds array|string
|
|
|
|
|
* @param $fname bool
|
2011-12-05 16:50:58 +00:00
|
|
|
* @return bool|ResultWrapper
|
2011-06-18 20:26:31 +00:00
|
|
|
*/
|
|
|
|
|
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
|
|
|
|
|
if ( !$conds ) {
|
|
|
|
|
throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$delTable = $this->tableName( $delTable );
|
|
|
|
|
$joinTable = $this->tableName( $joinTable );
|
|
|
|
|
$sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
|
|
|
|
|
|
|
|
|
|
if ( $conds != '*' ) {
|
|
|
|
|
$sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->query( $sql, $fname );
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-24 19:55:38 +00:00
|
|
|
/**
|
|
|
|
|
* Determines how long the server has been up
|
|
|
|
|
*
|
|
|
|
|
* @return int
|
|
|
|
|
*/
|
|
|
|
|
function getServerUptime() {
|
|
|
|
|
$vars = $this->getMysqlStatus( 'Uptime' );
|
|
|
|
|
return (int)$vars['Uptime'];
|
|
|
|
|
}
|
|
|
|
|
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
/**
|
|
|
|
|
* Determines if the last failure was due to a deadlock
|
2011-09-16 17:58:50 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
*/
|
|
|
|
|
function wasDeadlock() {
|
|
|
|
|
return $this->lastErrno() == 1213;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-23 23:45:46 +00:00
|
|
|
/**
|
|
|
|
|
* Determines if the last failure was due to a lock timeout
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
function wasLockTimeout() {
|
|
|
|
|
return $this->lastErrno() == 1205;
|
|
|
|
|
}
|
|
|
|
|
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
/**
|
2010-05-19 02:12:59 +00:00
|
|
|
* Determines if the last query error was something that should be dealt
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
* with by pinging the connection and reissuing the query
|
2011-09-16 17:58:50 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
*/
|
|
|
|
|
function wasErrorReissuable() {
|
|
|
|
|
return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determines if the last failure was due to the database being read-only.
|
2011-09-16 17:58:50 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
*/
|
|
|
|
|
function wasReadOnlyError() {
|
2010-05-19 02:12:59 +00:00
|
|
|
return $this->lastErrno() == 1223 ||
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* @param $oldName
|
|
|
|
|
* @param $newName
|
|
|
|
|
* @param $temporary bool
|
|
|
|
|
* @param $fname string
|
|
|
|
|
*/
|
2009-11-06 10:17:44 +00:00
|
|
|
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseMysql::duplicateTableStructure' ) {
|
2009-12-11 19:53:10 +00:00
|
|
|
$tmp = $temporary ? 'TEMPORARY ' : '';
|
2011-11-23 16:27:48 +00:00
|
|
|
$newName = $this->addIdentifierQuotes( $newName );
|
|
|
|
|
$oldName = $this->addIdentifierQuotes( $oldName );
|
|
|
|
|
$query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
|
2009-12-11 19:53:10 +00:00
|
|
|
$this->query( $query, $fname );
|
2009-11-06 10:17:44 +00:00
|
|
|
}
|
2011-08-01 16:01:13 +00:00
|
|
|
|
2010-12-28 17:15:50 +00:00
|
|
|
/**
|
|
|
|
|
* List all tables on the database
|
|
|
|
|
*
|
|
|
|
|
* @param $prefix Only show tables with this prefix, e.g. mw_
|
|
|
|
|
* @param $fname String: calling function name
|
2011-11-29 21:04:20 +00:00
|
|
|
* @return array
|
2010-12-28 17:15:50 +00:00
|
|
|
*/
|
|
|
|
|
function listTables( $prefix = null, $fname = 'DatabaseMysql::listTables' ) {
|
|
|
|
|
$result = $this->query( "SHOW TABLES", $fname);
|
2011-05-30 01:57:53 +00:00
|
|
|
|
2010-12-28 17:15:50 +00:00
|
|
|
$endArray = array();
|
2011-05-30 01:57:53 +00:00
|
|
|
|
|
|
|
|
foreach( $result as $table ) {
|
2010-12-28 17:15:50 +00:00
|
|
|
$vars = get_object_vars($table);
|
|
|
|
|
$table = array_pop( $vars );
|
2011-05-30 01:57:53 +00:00
|
|
|
|
2011-03-27 01:22:44 +00:00
|
|
|
if( !$prefix || strpos( $table, $prefix ) === 0 ) {
|
2010-12-28 17:15:50 +00:00
|
|
|
$endArray[] = $table;
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-08-01 16:01:13 +00:00
|
|
|
|
2010-12-28 17:15:50 +00:00
|
|
|
return $endArray;
|
|
|
|
|
}
|
2009-11-06 10:17:44 +00:00
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @param $tableName
|
|
|
|
|
* @param $fName string
|
|
|
|
|
* @return bool|ResultWrapper
|
|
|
|
|
*/
|
2010-12-28 01:19:16 +00:00
|
|
|
public function dropTable( $tableName, $fName = 'DatabaseMysql::dropTable' ) {
|
2011-11-10 20:39:23 +00:00
|
|
|
if( !$this->tableExists( $tableName, $fName ) ) {
|
2010-12-28 01:19:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-01 06:17:40 +00:00
|
|
|
/**
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
* Fixed a bug causing the installer to ignore the "engine" and "charset" settings when installing a MySQL database.
* Fixed a bug causing the engine and charset settings to not be properly preserved when adding new tables on upgrade.
* Fixed total breakage of SQLite upgrade, by reusing the administrative connection to the SQLite database instead of creating a new one when wfGetDB() is called. Added LBFactory_Single to support this.
* Introduced a "schema variable" concept to DatabaseBase to avoid the use of globals for communication between the installer and the Database. Removed a lot of old global variable names from Database::replaceVars(), most were only added on a whim and were never used.
* Introduced DatabaseInstaller::getSchemaVars(), to allow schema variables to be supplied by the DatabaseInstaller child classes.
* Removed messages config-mysql-egine-mismatch [sic] and config-mysql-charset-mismatch. In the old installer it was possible for users to request a certain character set for an upgrade, but in the new installer the question is never asked. So these warnings were shown whenever a non-default character set or engine was used in the old database.
* In MysqlInstaller::preUpgrade(), fixed the incorrect strings used to identify the MySQL character sets: mysql5 instead of utf8 and mysql5-binary instead of binary.
* On install, initialise the site_stats table, using code copied from the old installer. Unlike the old installer, use SiteStats to increment the user count when the initial user is added.
* Fixed several instances of inappropriate call-by-reference.
* Replaced call_user_func_array() with call_user_func() where possible, it is shorter and simpler.
* Moved the caching boilerplate for DatabaseInstaller::getConnection() to the base class, and have the derived classes override an uncached function openConnection() instead. Updates r80892.
* In MysqlInstaller::getLocalSettings(), escape PHP strings correctly with LocalSettingsGenerator::escapePhpString().
* Reduce timeout for checks in dirIsExecutable() to 3 seconds, so that it doesn't take 30s to run when apache is in single-threaded mode for debugging.
* MySQL and SQLite have been tested and they appear to work. PostgreSQL upgrade is totally broken, apparently it was like that before I started. The Oracle code is untested.
2011-01-25 07:37:48 +00:00
|
|
|
protected function getDefaultSchemaVars() {
|
|
|
|
|
$vars = parent::getDefaultSchemaVars();
|
2011-11-01 06:17:40 +00:00
|
|
|
$vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
|
2011-12-02 21:50:42 +00:00
|
|
|
$vars['wgDBTableOptions'] = str_replace( 'CHARSET=mysql4', 'CHARSET=binary', $vars['wgDBTableOptions'] );
|
* Fixed a bug causing the installer to ignore the "engine" and "charset" settings when installing a MySQL database.
* Fixed a bug causing the engine and charset settings to not be properly preserved when adding new tables on upgrade.
* Fixed total breakage of SQLite upgrade, by reusing the administrative connection to the SQLite database instead of creating a new one when wfGetDB() is called. Added LBFactory_Single to support this.
* Introduced a "schema variable" concept to DatabaseBase to avoid the use of globals for communication between the installer and the Database. Removed a lot of old global variable names from Database::replaceVars(), most were only added on a whim and were never used.
* Introduced DatabaseInstaller::getSchemaVars(), to allow schema variables to be supplied by the DatabaseInstaller child classes.
* Removed messages config-mysql-egine-mismatch [sic] and config-mysql-charset-mismatch. In the old installer it was possible for users to request a certain character set for an upgrade, but in the new installer the question is never asked. So these warnings were shown whenever a non-default character set or engine was used in the old database.
* In MysqlInstaller::preUpgrade(), fixed the incorrect strings used to identify the MySQL character sets: mysql5 instead of utf8 and mysql5-binary instead of binary.
* On install, initialise the site_stats table, using code copied from the old installer. Unlike the old installer, use SiteStats to increment the user count when the initial user is added.
* Fixed several instances of inappropriate call-by-reference.
* Replaced call_user_func_array() with call_user_func() where possible, it is shorter and simpler.
* Moved the caching boilerplate for DatabaseInstaller::getConnection() to the base class, and have the derived classes override an uncached function openConnection() instead. Updates r80892.
* In MysqlInstaller::getLocalSettings(), escape PHP strings correctly with LocalSettingsGenerator::escapePhpString().
* Reduce timeout for checks in dirIsExecutable() to 3 seconds, so that it doesn't take 30s to run when apache is in single-threaded mode for debugging.
* MySQL and SQLite have been tested and they appear to work. PostgreSQL upgrade is totally broken, apparently it was like that before I started. The Oracle code is untested.
2011-01-25 07:37:48 +00:00
|
|
|
return $vars;
|
|
|
|
|
}
|
2011-06-23 03:14:11 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get status information from SHOW STATUS in an associative array
|
|
|
|
|
*
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param $which string
|
2011-06-23 03:14:11 +00:00
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
function getMysqlStatus( $which = "%" ) {
|
|
|
|
|
$res = $this->query( "SHOW STATUS LIKE '{$which}'" );
|
|
|
|
|
$status = array();
|
|
|
|
|
|
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
|
$status[$row->Variable_name] = $row->Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-15 18:39:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Legacy support: Database == DatabaseMysql
|
2011-09-16 17:58:50 +00:00
|
|
|
*
|
|
|
|
|
* @deprecated in 1.16
|
2009-06-15 18:39:41 +00:00
|
|
|
*/
|
|
|
|
|
class Database extends DatabaseMysql {}
|
|
|
|
|
|
2010-11-21 19:56:51 +00:00
|
|
|
/**
|
|
|
|
|
* Utility class.
|
|
|
|
|
* @ingroup Database
|
|
|
|
|
*/
|
|
|
|
|
class MySQLField implements Field {
|
|
|
|
|
private $name, $tablename, $default, $max_length, $nullable,
|
|
|
|
|
$is_pk, $is_unique, $is_multiple, $is_key, $type;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2010-11-21 19:56:51 +00:00
|
|
|
function name() {
|
|
|
|
|
return $this->name;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2010-11-21 19:56:51 +00:00
|
|
|
function tableName() {
|
|
|
|
|
return $this->tableName;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2010-11-21 19:56:51 +00:00
|
|
|
function type() {
|
|
|
|
|
return $this->type;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2010-11-21 19:56:51 +00:00
|
|
|
function isNullable() {
|
|
|
|
|
return $this->nullable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function defaultValue() {
|
|
|
|
|
return $this->default;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2010-11-21 19:56:51 +00:00
|
|
|
function isKey() {
|
|
|
|
|
return $this->is_key;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2010-11-21 19:56:51 +00:00
|
|
|
function isMultipleKey() {
|
|
|
|
|
return $this->is_multiple;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-23 03:14:11 +00:00
|
|
|
class MySQLMasterPos implements DBMasterPos {
|
2009-06-15 18:39:41 +00:00
|
|
|
var $file, $pos;
|
|
|
|
|
|
|
|
|
|
function __construct( $file, $pos ) {
|
|
|
|
|
$this->file = $file;
|
|
|
|
|
$this->pos = $pos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function __toString() {
|
|
|
|
|
return "{$this->file}/{$this->pos}";
|
|
|
|
|
}
|
|
|
|
|
}
|