diff options
Diffstat (limited to 'includes/db/DatabaseMysql.php')
-rw-r--r-- | includes/db/DatabaseMysql.php | 240 |
1 files changed, 197 insertions, 43 deletions
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php index ea7ef5b9..ed276ec5 100644 --- a/includes/db/DatabaseMysql.php +++ b/includes/db/DatabaseMysql.php @@ -1,5 +1,12 @@ <?php /** + * This is the MySQL database abstraction layer. + * + * @file + * @ingroup Database + */ + +/** * Database abstraction object for mySQL * Inherit all methods and properties of Database::Database() * @@ -24,11 +31,8 @@ class DatabaseMysql extends DatabaseBase { global $wgAllDBsAreLocalhost; wfProfileIn( __METHOD__ ); - # Test for missing mysql.so - # First try to load it - if (!@extension_loaded('mysql')) { - @dl('mysql.so'); - } + # Load mysql.so if we don't have it + wfDl( 'mysql' ); # Fail now # Otherwise we get a suppressed fatal error, which is very hard to track down @@ -48,8 +52,6 @@ class DatabaseMysql extends DatabaseBase { $this->mPassword = $password; $this->mDBname = $dbName; - $success = false; - wfProfileIn("dbconnect-$server"); # The kernel's default SYN retransmission period is far too slow for us, @@ -72,10 +74,10 @@ class DatabaseMysql extends DatabaseBase { # Create a new connection... $this->mConn = mysql_connect( $realServer, $user, $password, true ); } - if ($this->mConn === false) { + #if ( $this->mConn === false ) { #$iplus = $i + 1; - #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n"); - } + #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n"); + #} } $phpError = $this->restoreErrorHandler(); # Always log connection errors @@ -88,9 +90,8 @@ class DatabaseMysql extends DatabaseBase { wfDebug( "DB connection error\n" ); wfDebug( "Server: $server, User: $user, Password: " . substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" ); - $success = false; } - + wfProfileOut("dbconnect-$server"); if ( $dbName != '' && $this->mConn !== false ) { @@ -114,9 +115,15 @@ class DatabaseMysql extends DatabaseBase { 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__ ); } - // Turn off strict mode - $this->query( "SET sql_mode = ''", __METHOD__ ); } // Turn off strict mode if it is on @@ -233,34 +240,35 @@ class DatabaseMysql extends DatabaseBase { } function affectedRows() { return mysql_affected_rows( $this->mConn ); } - + /** * Estimate rows in dataset * Returns estimated count, based on EXPLAIN output * Takes same arguments as Database::select() */ - public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) { + public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) { $options['EXPLAIN'] = true; $res = $this->select( $table, $vars, $conds, $fname, $options ); - if ( $res === false ) + if ( $res === false ) { return false; + } if ( !$this->numRows( $res ) ) { - $this->freeResult($res); return 0; } $rows = 1; - while( $plan = $this->fetchObject( $res ) ) { + foreach ( $res as $plan ) { $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero } - - $this->freeResult($res); - return $rows; + return $rows; } function fieldInfo( $table, $field ) { $table = $this->tableName( $table ); - $res = $this->query( "SELECT * FROM $table LIMIT 1" ); + $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true ); + if ( !$res ) { + return false; + } $n = mysql_num_fields( $res->result ); for( $i = 0; $i < $n; $i++ ) { $meta = mysql_fetch_field( $res->result, $i ); @@ -271,32 +279,108 @@ class DatabaseMysql extends DatabaseBase { return false; } + /** + * Get information about an index into an object + * Returns false if the index does not exist + */ + 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; + } + function selectDB( $db ) { $this->mDBname = $db; return mysql_select_db( $db, $this->mConn ); } function strencode( $s ) { - return mysql_real_escape_string( $s, $this->mConn ); + $sQuoted = mysql_real_escape_string( $s, $this->mConn ); + + if($sQuoted === false) { + $this->ping(); + $sQuoted = mysql_real_escape_string( $s, $this->mConn ); + } + return $sQuoted; + } + + /** + * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes". + */ + public function addIdentifierQuotes( $s ) { + return "`" . $this->strencode( $s ) . "`"; } function ping() { - if( !function_exists( 'mysql_ping' ) ) { - wfDebug( "Tried to call mysql_ping but this is ancient PHP version. Faking it!\n" ); - return true; - } $ping = mysql_ping( $this->mConn ); if ( $ping ) { return true; } - // Need to reconnect manually in MySQL client 5.0.13+ - if ( version_compare( mysql_get_client_info(), '5.0.13', '>=' ) ) { - mysql_close( $this->mConn ); - $this->mOpened = false; - $this->mConn = false; - $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname ); - return true; + mysql_close( $this->mConn ); + $this->mOpened = false; + $this->mConn = false; + $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname ); + return true; + } + + /** + * Returns slave lag. + * At the moment, this will only work if the DB user has the PROCESS privilege + * @result int + */ + function getLag() { + if ( !is_null( $this->mFakeSlaveLag ) ) { + wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" ); + return $this->mFakeSlaveLag; + } + $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ ); + if( !$res ) { + return false; + } + # Find slave SQL thread + foreach( $res as $row ) { + /* 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; } @@ -313,7 +397,7 @@ class DatabaseMysql extends DatabaseBase { return 'LOW_PRIORITY'; } - function getSoftwareLink() { + public static function getSoftwareLink() { return '[http://www.mysql.com/ MySQL]'; } @@ -330,7 +414,6 @@ class DatabaseMysql extends DatabaseBase { $lockName = $this->addQuotes( $lockName ); $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method ); $row = $this->fetchObject( $result ); - $this->freeResult( $result ); if( $row->lockstatus == 1 ) { return true; @@ -340,6 +423,9 @@ class DatabaseMysql extends DatabaseBase { } } + /** + * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock + */ public function unlock( $lockName, $method ) { $lockName = $this->addQuotes( $lockName ); $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method ); @@ -351,8 +437,8 @@ class DatabaseMysql extends DatabaseBase { $items = array(); foreach( $write as $table ) { - $tbl = $this->tableName( $table ) . - ( $lowPriority ? ' LOW_PRIORITY' : '' ) . + $tbl = $this->tableName( $table ) . + ( $lowPriority ? ' LOW_PRIORITY' : '' ) . ' WRITE'; $items[] = $tbl; } @@ -367,6 +453,16 @@ class DatabaseMysql extends DatabaseBase { $this->query( "UNLOCK TABLES", $method ); } + /** + * 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'; + } + public function setBigSelects( $value = true ) { if ( $value === 'default' ) { if ( $this->mDefaultBigSelects === null ) { @@ -382,7 +478,10 @@ class DatabaseMysql extends DatabaseBase { $this->query( "SET sql_big_selects=$encValue", __METHOD__ ); } - + public function unixTimestamp( $field ) { + return "UNIX_TIMESTAMP($field)"; + } + /** * Determines if the last failure was due to a deadlock */ @@ -391,7 +490,7 @@ class DatabaseMysql extends DatabaseBase { } /** - * Determines if the last query error was something that should be dealt + * Determines if the last query error was something that should be dealt * with by pinging the connection and reissuing the query */ function wasErrorReissuable() { @@ -402,7 +501,7 @@ class DatabaseMysql extends DatabaseBase { * Determines if the last failure was due to the database being read-only. */ function wasReadOnlyError() { - return $this->lastErrno() == 1223 || + return $this->lastErrno() == 1223 || ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false ); } @@ -420,7 +519,7 @@ class DatabaseMysql extends DatabaseBase { $res = $this->query( "SHOW CREATE TABLE $oldName" ); $row = $this->fetchRow( $res ); $oldQuery = $row[1]; - $query = preg_replace( '/CREATE TABLE `(.*?)`/', + $query = preg_replace( '/CREATE TABLE `(.*?)`/', "CREATE $tmp TABLE `$newName`", $oldQuery ); if ($oldQuery === $query) { # Couldn't do replacement @@ -432,6 +531,11 @@ class DatabaseMysql extends DatabaseBase { $this->query( $query, $fname ); } + protected function getDefaultSchemaVars() { + $vars = parent::getDefaultSchemaVars(); + $vars['wgDBTableOptions'] = $GLOBALS['wgDBTableOptions']; + return $vars; + } } /** @@ -439,6 +543,56 @@ class DatabaseMysql extends DatabaseBase { */ class Database extends DatabaseMysql {} +/** + * 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; + } + + function name() { + return $this->name; + } + + function tableName() { + return $this->tableName; + } + + function type() { + return $this->type; + } + + function isNullable() { + return $this->nullable; + } + + function defaultValue() { + return $this->default; + } + + function isKey() { + return $this->is_key; + } + + function isMultipleKey() { + return $this->is_multiple; + } +} + class MySQLMasterPos { var $file, $pos; |