diff options
Diffstat (limited to 'includes/db/Database.php')
-rw-r--r-- | includes/db/Database.php | 310 |
1 files changed, 105 insertions, 205 deletions
diff --git a/includes/db/Database.php b/includes/db/Database.php index 0c0248da..1e54f554 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -26,13 +26,6 @@ */ /** - * Interface for classes that implement or wrap DatabaseBase - * @ingroup Database - */ -interface IDatabase { -} - -/** * Database abstraction object * @ingroup Database */ @@ -65,10 +58,11 @@ abstract class DatabaseBase implements IDatabase { protected $mSchema; protected $mFlags; protected $mForeign; - protected $mErrorCount = 0; protected $mLBInfo = array(); protected $mDefaultBigSelects = null; protected $mSchemaVars = false; + /** @var array */ + protected $mSessionVars = array(); protected $preparedArgs; @@ -142,6 +136,13 @@ abstract class DatabaseBase implements IDatabase { private $mTrxAutomaticAtomic = false; /** + * Track the seconds spent in write queries for the current transaction + * + * @var float + */ + private $mTrxWriteDuration = 0.0; + + /** * @since 1.21 * @var resource File handle for upgrade */ @@ -228,7 +229,7 @@ abstract class DatabaseBase implements IDatabase { * @param null|bool $ignoreErrors * @return bool The previous value of the flag. */ - public function ignoreErrors( $ignoreErrors = null ) { + protected function ignoreErrors( $ignoreErrors = null ) { return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors ); } @@ -258,15 +259,6 @@ abstract class DatabaseBase implements IDatabase { } /** - * Get/set the number of errors logged. Only useful when errors are ignored - * @param int $count The count to set, or omitted to leave it unchanged. - * @return int The error count - */ - public function errorCount( $count = null ) { - return wfSetVar( $this->mErrorCount, $count ); - } - - /** * Get/set the table prefix. * @param string $prefix The table prefix to set, or omitted to leave it unchanged. * @return string The previous table prefix. @@ -474,6 +466,18 @@ abstract class DatabaseBase implements IDatabase { } /** + * Get the time spend running write queries for this + * + * High times could be due to scanning, updates, locking, and such + * + * @return float|bool Returns false if not transaction is active + * @since 1.26 + */ + public function pendingWriteQueryDuration() { + return $this->mTrxLevel ? $this->mTrxWriteDuration : false; + } + + /** * Is a connection to the database open? * @return bool */ @@ -592,125 +596,6 @@ abstract class DatabaseBase implements IDatabase { } /** - * Get the type of the DBMS, as it appears in $wgDBtype. - * - * @return string - */ - abstract function getType(); - - /** - * Open a connection to the database. Usually aborts on failure - * - * @param string $server Database server host - * @param string $user Database user name - * @param string $password Database user password - * @param string $dbName Database name - * @return bool - * @throws DBConnectionError - */ - abstract function open( $server, $user, $password, $dbName ); - - /** - * Fetch the next row from the given result object, in object form. - * Fields can be retrieved with $row->fieldname, with fields acting like - * member variables. - * If no more rows are available, false is returned. - * - * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc. - * @return stdClass|bool - * @throws DBUnexpectedError Thrown if the database returns an error - */ - abstract function fetchObject( $res ); - - /** - * Fetch the next row from the given result object, in associative array - * form. Fields are retrieved with $row['fieldname']. - * If no more rows are available, false is returned. - * - * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc. - * @return array|bool - * @throws DBUnexpectedError Thrown if the database returns an error - */ - abstract function fetchRow( $res ); - - /** - * Get the number of rows in a result object - * - * @param mixed $res A SQL result - * @return int - */ - abstract function numRows( $res ); - - /** - * Get the number of fields in a result object - * @see http://www.php.net/mysql_num_fields - * - * @param mixed $res A SQL result - * @return int - */ - abstract function numFields( $res ); - - /** - * Get a field name in a result object - * @see http://www.php.net/mysql_field_name - * - * @param mixed $res A SQL result - * @param int $n - * @return string - */ - abstract function fieldName( $res, $n ); - - /** - * Get the inserted value of an auto-increment row - * - * The value inserted should be fetched from nextSequenceValue() - * - * Example: - * $id = $dbw->nextSequenceValue( 'page_page_id_seq' ); - * $dbw->insert( 'page', array( 'page_id' => $id ) ); - * $id = $dbw->insertId(); - * - * @return int - */ - abstract function insertId(); - - /** - * Change the position of the cursor in a result object - * @see http://www.php.net/mysql_data_seek - * - * @param mixed $res A SQL result - * @param int $row - */ - abstract function dataSeek( $res, $row ); - - /** - * Get the last error number - * @see http://www.php.net/mysql_errno - * - * @return int - */ - abstract function lastErrno(); - - /** - * Get a description of the last error - * @see http://www.php.net/mysql_error - * - * @return string - */ - abstract function lastError(); - - /** - * mysql_fetch_field() wrapper - * Returns false if the field doesn't exist - * - * @param string $table Table name - * @param string $field Field name - * - * @return Field - */ - abstract function fieldInfo( $table, $field ); - - /** * Get information about an index into an object * @param string $table Table name * @param string $index Index name @@ -720,14 +605,6 @@ abstract class DatabaseBase implements IDatabase { abstract function indexInfo( $table, $index, $fname = __METHOD__ ); /** - * Get the number of rows affected by the last write query - * @see http://www.php.net/mysql_affected_rows - * - * @return int - */ - abstract function affectedRows(); - - /** * Wrapper for addslashes() * * @param string $s String to be slashed. @@ -736,24 +613,6 @@ abstract class DatabaseBase implements IDatabase { abstract function strencode( $s ); /** - * Returns a wikitext link to the DB's website, e.g., - * return "[http://www.mysql.com/ MySQL]"; - * Should at least contain plain text, if for some reason - * your database has no website. - * - * @return string Wikitext of a link to the server software's web site - */ - abstract function getSoftwareLink(); - - /** - * A string describing the current software version, like from - * mysql_get_server_info(). - * - * @return string Version information from the database server. - */ - abstract function getServerVersion(); - - /** * Constructor. * * FIXME: It is possible to construct a Database object with no associated @@ -794,15 +653,17 @@ abstract class DatabaseBase implements IDatabase { } } + $this->mSessionVars = $params['variables']; + /** Get the default table prefix*/ - if ( $tablePrefix == 'get from global' ) { + if ( $tablePrefix === 'get from global' ) { $this->mTablePrefix = $wgDBprefix; } else { $this->mTablePrefix = $tablePrefix; } /** Get the database schema*/ - if ( $schema == 'get from global' ) { + if ( $schema === 'get from global' ) { $this->mSchema = $wgDBmwschema; } else { $this->mSchema = $schema; @@ -892,10 +753,6 @@ abstract class DatabaseBase implements IDatabase { // Although postgres and oracle support schemas, we don't use them (yet) // to maintain backwards compatibility $defaultSchemas = array( - 'mysql' => null, - 'postgres' => null, - 'sqlite' => null, - 'oracle' => null, 'mssql' => 'get from global', ); @@ -907,8 +764,11 @@ abstract class DatabaseBase implements IDatabase { $p['password'] = isset( $p['password'] ) ? $p['password'] : false; $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false; $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0; + $p['variables'] = isset( $p['variables'] ) ? $p['variables'] : array(); $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'; - $p['schema'] = isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType]; + if ( !isset( $p['schema'] ) ) { + $p['schema'] = isset( $defaultSchemas[$dbType] ) ? $defaultSchemas[$dbType] : null; + } $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false; return new $class( $p ); @@ -998,6 +858,17 @@ abstract class DatabaseBase implements IDatabase { } /** + * Make sure isOpen() returns true as a sanity check + * + * @throws DBUnexpectedError + */ + protected function assertOpen() { + if ( !$this->isOpen() ) { + throw new DBUnexpectedError( $this, "DB connection was already closed." ); + } + } + + /** * Closes underlying database connection * @since 1.20 * @return bool Whether connection was closed successfully @@ -1034,7 +905,7 @@ abstract class DatabaseBase implements IDatabase { * @param string $sql * @return bool */ - public function isWriteQuery( $sql ) { + protected function isWriteQuery( $sql ) { return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql ); } @@ -1047,7 +918,7 @@ abstract class DatabaseBase implements IDatabase { * @param string $sql * @return bool */ - public function isTransactableQuery( $sql ) { + protected function isTransactableQuery( $sql ) { $verb = substr( $sql, 0, strcspn( $sql, " \t\r\n" ) ); return !in_array( $verb, array( 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ) ); } @@ -1153,13 +1024,12 @@ abstract class DatabaseBase implements IDatabase { $queryId = MWDebug::query( $sql, $fname, $isMaster ); # Avoid fatals if close() was called - if ( !$this->isOpen() ) { - throw new DBUnexpectedError( $this, "DB connection was already closed." ); - } + $this->assertOpen(); # Do the query and handle errors $startTime = microtime( true ); $ret = $this->doQuery( $commentedSql ); + $queryRuntime = microtime( true ) - $startTime; # Log the query time and feed it into the DB trx profiler $this->getTransactionProfiler()->recordQueryCompletion( $queryProf, $startTime, $isWriteQuery, $this->affectedRows() ); @@ -1191,6 +1061,7 @@ abstract class DatabaseBase implements IDatabase { # Should be safe to silently retry (no trx and thus no callbacks) $startTime = microtime( true ); $ret = $this->doQuery( $commentedSql ); + $queryRuntime = microtime( true ) - $startTime; # Log the query time and feed it into the DB trx profiler $this->getTransactionProfiler()->recordQueryCompletion( $queryProf, $startTime, $isWriteQuery, $this->affectedRows() ); @@ -1211,6 +1082,10 @@ abstract class DatabaseBase implements IDatabase { $queryProfSection = false; $totalProfSection = false; + if ( $isWriteQuery && $this->mTrxLevel ) { + $this->mTrxWriteDuration += $queryRuntime; + } + return $res; } @@ -1226,8 +1101,6 @@ abstract class DatabaseBase implements IDatabase { * @throws DBQueryError */ public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { - ++$this->mErrorCount; - if ( $this->ignoreErrors() || $tempIgnore ) { wfDebug( "SQL ERROR (ignored): $error\n" ); } else { @@ -1421,6 +1294,7 @@ abstract class DatabaseBase implements IDatabase { * @param string|array $options The query options. See DatabaseBase::select() for details. * * @return bool|array The values from the field, or false on failure + * @throws DBUnexpectedError * @since 1.25 */ public function selectFieldValues( @@ -1881,7 +1755,7 @@ abstract class DatabaseBase implements IDatabase { ) { $rows = 0; $sql = $this->selectSQLText( $table, '1', $conds, $fname, $options ); - $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count" ); + $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count", $fname ); if ( $res ) { $row = $this->fetchRow( $res ); @@ -2015,9 +1889,6 @@ abstract class DatabaseBase implements IDatabase { * This causes a multi-row INSERT on DBMSs that support it. The keys in * each subarray must be identical to each other, and in the same order. * - * Usually throws a DBQueryError on failure. If errors are explicitly ignored, - * returns success. - * * $options is an array of options, with boolean options encoded as values * with numeric keys, in the same style as $options in * DatabaseBase::select(). Supported options are: @@ -2033,6 +1904,9 @@ abstract class DatabaseBase implements IDatabase { * @param string $fname Calling function name (use __METHOD__) for logs/profiling * @param array $options Array of options * + * @throws DBQueryError Usually throws a DBQueryError on failure. If errors are explicitly ignored, + * returns success. + * * @return bool */ public function insert( $table, $a, $fname = __METHOD__, $options = array() ) { @@ -2455,7 +2329,7 @@ abstract class DatabaseBase implements IDatabase { } # Quote $schema and merge it with the table name if needed - if ( $schema !== null ) { + if ( strlen( $schema ) ) { if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) { $schema = $this->addIdentifierQuotes( $schema ); } @@ -2842,6 +2716,7 @@ abstract class DatabaseBase implements IDatabase { $rows = array( $rows ); } + // @FXIME: this is not atomic, but a trx would break affectedRows() foreach ( $rows as $row ) { # Delete rows which collide if ( $uniqueIndexes ) { @@ -3303,6 +3178,17 @@ abstract class DatabaseBase implements IDatabase { } /** + * Determines if the given query error was a connection drop + * STUB + * + * @param integer|string $errno + * @return bool + */ + public function wasConnectionError( $errno ) { + return false; + } + + /** * Perform a deadlock-prone transaction. * * This function invokes a callback function to perform a set of write @@ -3318,7 +3204,8 @@ abstract class DatabaseBase implements IDatabase { * iteration, or false on error, for example if the retry limit was * reached. * - * @return bool + * @return mixed + * @throws DBQueryError */ public function deadlockLoop() { $args = func_get_args(); @@ -3332,15 +3219,13 @@ abstract class DatabaseBase implements IDatabase { $this->begin( __METHOD__ ); + $retVal = null; $e = null; do { try { $retVal = call_user_func_array( $function, $args ); break; } catch ( DBQueryError $e ) { - $error = $this->lastError(); - $errno = $this->lastErrno(); - $sql = $this->lastQuery(); if ( $this->wasDeadlock() ) { // Retry after a randomized delay usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) ); @@ -3525,7 +3410,11 @@ abstract class DatabaseBase implements IDatabase { if ( !$this->mTrxLevel ) { $this->begin( $fname ); $this->mTrxAutomatic = true; - $this->mTrxAutomaticAtomic = true; + // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result + // in all changes being in one transaction to keep requests transactional. + if ( !$this->getFlag( DBO_TRX ) ) { + $this->mTrxAutomaticAtomic = true; + } } $this->mTrxAtomicLevels->push( $fname ); @@ -3605,19 +3494,18 @@ abstract class DatabaseBase implements IDatabase { } $this->runOnTransactionPreCommitCallbacks(); + $writeTime = $this->pendingWriteQueryDuration(); $this->doCommit( $fname ); if ( $this->mTrxDoneWrites ) { $this->mDoneWrites = microtime( true ); $this->getTransactionProfiler()->transactionWritingOut( - $this->mServer, $this->mDBname, $this->mTrxShortId ); + $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime ); } $this->runOnTransactionIdleCallbacks(); } # Avoid fatals if close() was called - if ( !$this->isOpen() ) { - throw new DBUnexpectedError( $this, "DB connection was already closed." ); - } + $this->assertOpen(); $this->doBegin( $fname ); $this->mTrxTimestamp = microtime( true ); @@ -3629,6 +3517,7 @@ abstract class DatabaseBase implements IDatabase { $this->mTrxIdleCallbacks = array(); $this->mTrxPreCommitCallbacks = array(); $this->mTrxShortId = wfRandomString( 12 ); + $this->mTrxWriteDuration = 0.0; } /** @@ -3681,16 +3570,15 @@ abstract class DatabaseBase implements IDatabase { } # Avoid fatals if close() was called - if ( !$this->isOpen() ) { - throw new DBUnexpectedError( $this, "DB connection was already closed." ); - } + $this->assertOpen(); $this->runOnTransactionPreCommitCallbacks(); + $writeTime = $this->pendingWriteQueryDuration(); $this->doCommit( $fname ); if ( $this->mTrxDoneWrites ) { $this->mDoneWrites = microtime( true ); $this->getTransactionProfiler()->transactionWritingOut( - $this->mServer, $this->mDBname, $this->mTrxShortId ); + $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime ); } $this->runOnTransactionIdleCallbacks(); } @@ -3739,9 +3627,7 @@ abstract class DatabaseBase implements IDatabase { } # Avoid fatals if close() was called - if ( !$this->isOpen() ) { - throw new DBUnexpectedError( $this, "DB connection was already closed." ); - } + $this->assertOpen(); $this->doRollback( $fname ); $this->mTrxIdleCallbacks = array(); // cancel @@ -3794,6 +3680,7 @@ abstract class DatabaseBase implements IDatabase { * @param string $prefix Only show tables with this prefix, e.g. mw_ * @param string $fname Calling function name * @throws MWException + * @return array */ function listTables( $prefix = null, $fname = __METHOD__ ) { throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); @@ -3816,6 +3703,7 @@ abstract class DatabaseBase implements IDatabase { * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_ * @param string $fname Name of calling function * @throws MWException + * @return array * @since 1.22 */ public function listViews( $prefix = null, $fname = __METHOD__ ) { @@ -3827,6 +3715,7 @@ abstract class DatabaseBase implements IDatabase { * * @param string $name Name of the database-structure to test. * @throws MWException + * @return bool * @since 1.22 */ public function isView( $name ) { @@ -3988,9 +3877,9 @@ abstract class DatabaseBase implements IDatabase { public function sourceFile( $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $fp = fopen( $filename, 'r' ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( false === $fp ) { throw new MWException( "Could not open \"{$filename}\".\n" ); @@ -4205,7 +4094,7 @@ abstract class DatabaseBase implements IDatabase { } /** - * Check to see if a named lock is available. This is non-blocking. + * Check to see if a named lock is available (non-blocking) * * @param string $lockName Name of lock to poll * @param string $method Name of method calling us @@ -4219,8 +4108,7 @@ abstract class DatabaseBase implements IDatabase { /** * Acquire a named lock * - * Abstracted from Filestore::lock() so child classes can implement for - * their own needs. + * Named locks are not related to transactions * * @param string $lockName Name of lock to aquire * @param string $method Name of method calling us @@ -4232,7 +4120,9 @@ abstract class DatabaseBase implements IDatabase { } /** - * Release a lock. + * Release a lock + * + * Named locks are not related to transactions * * @param string $lockName Name of lock to release * @param string $method Name of method calling us @@ -4246,6 +4136,16 @@ abstract class DatabaseBase implements IDatabase { } /** + * Check to see if a named lock used by lock() use blocking queues + * + * @return bool + * @since 1.26 + */ + public function namedLocksEnqueue() { + return false; + } + + /** * Lock specific tables * * @param array $read Array of tables to lock for read access @@ -4328,7 +4228,7 @@ abstract class DatabaseBase implements IDatabase { * @return string */ public function decodeExpiry( $expiry, $format = TS_MW ) { - return ( $expiry == '' || $expiry == $this->getInfinity() ) + return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) ? 'infinity' : wfTimestamp( $format, $expiry ); } |