* @ingroup Database
*/
-/**
- * Base interface for all DBMS-specific code. At a bare minimum, all of the
- * following must be implemented to support MediaWiki
- *
- * @file
- * @ingroup Database
- */
-interface DatabaseType {
- /**
- * Get the type of the DBMS, as it appears in $wgDBtype.
- *
- * @return string
- */
- 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
- */
- 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
- */
- 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
- */
- function fetchRow( $res );
-
- /**
- * Get the number of rows in a result object
- *
- * @param mixed $res A SQL result
- * @return int
- */
- 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
- */
- 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
- */
- 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
- */
- 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
- */
- function dataSeek( $res, $row );
-
- /**
- * Get the last error number
- * @see http://www.php.net/mysql_errno
- *
- * @return int
- */
- function lastErrno();
-
- /**
- * Get a description of the last error
- * @see http://www.php.net/mysql_error
- *
- * @return string
- */
- 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
- */
- function fieldInfo( $table, $field );
-
- /**
- * Get information about an index into an object
- * @param string $table Table name
- * @param string $index Index name
- * @param string $fname Calling function name
- * @return mixed Database-specific index description class or false if the index does not exist
- */
- 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
- */
- function affectedRows();
-
- /**
- * Wrapper for addslashes()
- *
- * @param string $s String to be slashed.
- * @return string Slashed string.
- */
- 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
- */
- function getSoftwareLink();
-
- /**
- * A string describing the current software version, like from
- * mysql_get_server_info().
- *
- * @return string Version information from the database server.
- */
- function getServerVersion();
-
- /**
- * A string describing the current software version, and possibly
- * other details in a user-friendly way. Will be listed on Special:Version, etc.
- * Use getServerVersion() to get machine-friendly information.
- *
- * @return string Version information from the database server
- */
- function getServerInfo();
-}
-
/**
* Interface for classes that implement or wrap DatabaseBase
* @ingroup Database
* Database abstraction object
* @ingroup Database
*/
-abstract class DatabaseBase implements IDatabase, DatabaseType {
+abstract class DatabaseBase implements IDatabase {
/** Number of times to re-try an operation in case of deadlock */
const DEADLOCK_TRIES = 4;
$this->mPHPError = $errstr;
}
+ /**
+ * Create a log context to pass to wfLogDBError or other logging functions.
+ *
+ * @param array $extras Additional data to add to context
+ * @return array
+ */
+ protected function getLogContext( array $extras = array() ) {
+ return array_merge(
+ array(
+ 'db_server' => $this->mServer,
+ 'db_name' => $this->mDBname,
+ 'db_user' => $this->mUser,
+ ),
+ $extras
+ );
+ }
+
/**
* Closes a database connection.
* if it is open : commits any open transactions
# Keep track of whether the transaction has write queries pending
if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
$this->mTrxDoneWrites = true;
- Profiler::instance()->transactionWritingIn(
+ Profiler::instance()->getTransactionProfiler()->transactionWritingIn(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
$totalProf = '';
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
- if ( !Profiler::instance()->isStub() ) {
+ $profiler = Profiler::instance();
+ if ( !$profiler instanceof ProfilerStub ) {
# generalizeSQL will probably cut down the query to reasonable
# logging size most of the time. The substr is really just a sanity check.
if ( $isMaster ) {
# Include query transaction state
$queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
- $trx = $this->mTrxLevel ? 'TRX=yes' : 'TRX=no';
- wfProfileIn( $totalProf );
- wfProfileIn( $queryProf );
+ $totalProfSection = $profiler->scopedProfileIn( $totalProf );
+ $queryProfSection = $profiler->scopedProfileIn( $queryProf );
}
if ( $this->debug() ) {
throw new DBUnexpectedError( $this, "DB connection was already closed." );
}
+ # Log the query time and feed it into the DB trx profiler
+ if ( $queryProf != '' ) {
+ $queryStartTime = microtime( true );
+ $queryProfile = new ScopedCallback(
+ function() use ( $queryStartTime, $queryProf, $isMaster ) {
+ $trxProfiler = Profiler::instance()->getTransactionProfiler();
+ $trxProfiler->recordQueryCompletion( $queryProf, $queryStartTime, $isMaster );
+ }
+ );
+ }
+
# Do the query and handle errors
$ret = $this->doQuery( $commentedSql );
$lastError = $this->lastError();
$lastErrno = $this->lastErrno();
if ( $this->ping() ) {
- global $wgRequestTime;
wfDebug( "Reconnected\n" );
- $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
- : $commentedSql;
- $sqlx = strtr( $sqlx, "\t\n", ' ' );
- $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
- if ( $elapsed < 300 ) {
- # Not a database error to lose a transaction after a minute or two
- wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" );
- }
+ $server = $this->getServer();
+ $msg = __METHOD__ . ": lost connection to $server; reconnected";
+ wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
+
if ( $hadTrx ) {
# Leave $ret as false and let an error be reported.
# Callers may catch the exception and continue to use the DB.
$this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
}
- if ( !Profiler::instance()->isStub() ) {
- wfProfileOut( $queryProf );
- wfProfileOut( $totalProf );
- }
-
return $this->resultObject( $ret );
}
$this->ignoreErrors( $ignore );
} else {
$sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
- wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" );
+ wfLogDBError(
+ "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ 'errno' => $errno,
+ 'error' => $error,
+ 'sql1line' => $sql1line,
+ 'fname' => $fname,
+ ) )
+ );
wfDebug( "SQL ERROR: " . $error . "\n" );
throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
# Split database and table into proper variables.
# We reverse the explode so that database.table and table both output
# the correct table.
- $dbDetails = explode( '.', $name, 2 );
+ $dbDetails = explode( '.', $name, 3 );
if ( count( $dbDetails ) == 3 ) {
list( $database, $schema, $table ) = $dbDetails;
# We don't want any prefix added in this case
$msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
" performing implicit commit!";
wfWarn( $msg );
- wfLogDBError( $msg );
+ wfLogDBError( $msg,
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ 'fname' => $fname,
+ ) )
+ );
} else {
// if the transaction was automatic and has done write operations,
// log it if $wgDebugDBTransactions is enabled.
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut(
+ Profiler::instance()->getTransactionProfiler()->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
$this->runOnTransactionIdleCallbacks();
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut(
+ Profiler::instance()->getTransactionProfiler()->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
$this->runOnTransactionIdleCallbacks();
$this->mTrxPreCommitCallbacks = array(); // cancel
$this->mTrxAtomicLevels = new SplStack;
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut(
+ Profiler::instance()->getTransactionProfiler()->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
}
*
* - '{$var}' should be used for text and is passed through the database's
* addQuotes method.
- * - `{$var}` should be used for identifiers (eg: table and database names),
- * it is passed through the database's addIdentifierQuotes method which
+ * - `{$var}` should be used for identifiers (e.g. table and database names).
+ * It is passed through the database's addIdentifierQuotes method which
* can be overridden if the database uses something other than backticks.
- * - / *$var* / is just encoded, besides traditional table prefix and
- * table options its use should be avoided.
+ * - / *_* / or / *$wgDBprefix* / passes the name that follows through the
+ * database's tableName method.
+ * - / *i* / passes the name that follows through the database's indexName method.
+ * - In all other cases, / *$var* / is left unencoded. Except for table options,
+ * its use should be avoided. In 1.24 and older, string encoding was applied.
*
* @param string $ins SQL statement to replace variables in
* @return string The new SQL statement with variables replaced
*/
- protected function replaceSchemaVars( $ins ) {
- $vars = $this->getSchemaVars();
- foreach ( $vars as $var => $value ) {
- // replace '{$var}'
- $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
- // replace `{$var}`
- $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
- // replace /*$var*/
- $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
- }
-
- return $ins;
- }
-
- /**
- * Replace variables in sourced SQL
- *
- * @param string $ins
- * @return string
- */
protected function replaceVars( $ins ) {
- $ins = $this->replaceSchemaVars( $ins );
-
- // Table prefixes
- $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
- array( $this, 'tableNameCallback' ), $ins );
-
- // Index names
- $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
- array( $this, 'indexNameCallback' ), $ins );
-
- return $ins;
+ $that = $this;
+ $vars = $this->getSchemaVars();
+ return preg_replace_callback(
+ '!
+ /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
+ \'\{\$ (\w+) }\' | # 3. addQuotes
+ `\{\$ (\w+) }` | # 4. addIdentifierQuotes
+ /\*\$ (\w+) \*/ # 5. leave unencoded
+ !x',
+ function ( $m ) use ( $that, $vars ) {
+ // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
+ // check for both nonexistent keys *and* the empty string.
+ if ( isset( $m[1] ) && $m[1] !== '' ) {
+ if ( $m[1] === 'i' ) {
+ return $that->indexName( $m[2] );
+ } else {
+ return $that->tableName( $m[2] );
+ }
+ } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
+ return $that->addQuotes( $vars[$m[3]] );
+ } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
+ return $that->addIdentifierQuotes( $vars[$m[4]] );
+ } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
+ return $vars[$m[5]];
+ } else {
+ return $m[0];
+ }
+ },
+ $ins
+ );
}
/**
return array();
}
- /**
- * Table name callback
- *
- * @param array $matches
- * @return string
- */
- protected function tableNameCallback( $matches ) {
- return $this->tableName( $matches[1] );
- }
-
- /**
- * Index name callback
- *
- * @param array $matches
- * @return string
- */
- protected function indexNameCallback( $matches ) {
- return $this->indexName( $matches[1] );
- }
-
/**
* Check to see if a named lock is available. This is non-blocking.
*