<?php
-
/**
* @defgroup Database Database
*
* @file
* @ingroup Database
*/
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
/**
* Database abstraction object
* @ingroup Database
*/
-abstract class DatabaseBase implements IDatabase {
+abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
/** Number of times to re-try an operation in case of deadlock */
const DEADLOCK_TRIES = 4;
/** Minimum time to wait before retry, in microseconds */
protected $mPassword;
/** @var string */
protected $mDBname;
+ /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
+ protected $tableAliases = [];
/** @var bool */
protected $cliMode;
/** @var BagOStuff APC cache */
protected $srvCache;
+ /** @var LoggerInterface */
+ protected $connLogger;
+ /** @var LoggerInterface */
+ protected $queryLogger;
+ /** @var callback Error logging callback */
+ protected $errorLogger;
/** @var resource Database connection */
protected $mConn = null;
protected $mTrxPreCommitCallbacks = [];
/** @var array[] List of (callable, method name) */
protected $mTrxEndCallbacks = [];
- /** @var array[] Map of (name => (callable, method name)) */
+ /** @var callable[] Map of (name => callable) */
protected $mTrxRecurringCallbacks = [];
/** @var bool Whether to suppress triggering of transaction end callbacks */
protected $mTrxEndCallbacksSuppressed = false;
protected $mSchema;
/** @var integer */
protected $mFlags;
- /** @var bool */
- protected $mForeign;
/** @var array */
protected $mLBInfo = [];
/** @var bool|null */
/** @var TransactionProfiler */
protected $trxProfiler;
+ /**
+ * Constructor.
+ *
+ * FIXME: It is possible to construct a Database object with no associated
+ * connection object, by specifying no parameters to __construct(). This
+ * feature is deprecated and should be removed.
+ *
+ * IDatabase classes should not be constructed directly in external
+ * code. DatabaseBase::factory() should be used instead.
+ *
+ * @param array $params Parameters passed from DatabaseBase::factory()
+ */
+ function __construct( array $params ) {
+ $server = $params['host'];
+ $user = $params['user'];
+ $password = $params['password'];
+ $dbName = $params['dbname'];
+ $flags = $params['flags'];
+
+ $this->mSchema = $params['schema'];
+ $this->mTablePrefix = $params['tablePrefix'];
+
+ $this->cliMode = isset( $params['cliMode'] )
+ ? $params['cliMode']
+ : ( PHP_SAPI === 'cli' );
+
+ $this->mFlags = $flags;
+ if ( $this->mFlags & DBO_DEFAULT ) {
+ if ( $this->cliMode ) {
+ $this->mFlags &= ~DBO_TRX;
+ } else {
+ $this->mFlags |= DBO_TRX;
+ }
+ }
+
+ $this->mSessionVars = $params['variables'];
+
+ $this->srvCache = isset( $params['srvCache'] )
+ ? $params['srvCache']
+ : new HashBagOStuff();
+
+ $this->profiler = isset( $params['profiler'] )
+ ? $params['profiler']
+ : Profiler::instance(); // @TODO: remove global state
+ $this->trxProfiler = isset( $params['trxProfiler'] )
+ ? $params['trxProfiler']
+ : new TransactionProfiler();
+ $this->connLogger = isset( $params['connLogger'] )
+ ? $params['connLogger']
+ : new \Psr\Log\NullLogger();
+ $this->queryLogger = isset( $params['queryLogger'] )
+ ? $params['queryLogger']
+ : new \Psr\Log\NullLogger();
+
+ if ( $user ) {
+ $this->open( $server, $user, $password, $dbName );
+ }
+ }
+
+ /**
+ * Given a DB type, construct the name of the appropriate child class of
+ * IDatabase. This is designed to replace all of the manual stuff like:
+ * $class = 'Database' . ucfirst( strtolower( $dbType ) );
+ * as well as validate against the canonical list of DB types we have
+ *
+ * This factory function is mostly useful for when you need to connect to a
+ * database other than the MediaWiki default (such as for external auth,
+ * an extension, et cetera). Do not use this to connect to the MediaWiki
+ * database. Example uses in core:
+ * @see LoadBalancer::reallyOpenConnection()
+ * @see ForeignDBRepo::getMasterDB()
+ * @see WebInstallerDBConnect::execute()
+ *
+ * @since 1.18
+ *
+ * @param string $dbType A possible DB type
+ * @param array $p An array of options to pass to the constructor.
+ * Valid options are: host, user, password, dbname, flags, tablePrefix, schema, driver
+ * @return IDatabase|null If the database driver or extension cannot be found
+ * @throws InvalidArgumentException If the database driver or extension cannot be found
+ */
+ final public static function factory( $dbType, $p = [] ) {
+ global $wgCommandLineMode;
+
+ $canonicalDBTypes = [
+ 'mysql' => [ 'mysqli', 'mysql' ],
+ 'postgres' => [],
+ 'sqlite' => [],
+ 'oracle' => [],
+ 'mssql' => [],
+ ];
+
+ $driver = false;
+ $dbType = strtolower( $dbType );
+ if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
+ $possibleDrivers = $canonicalDBTypes[$dbType];
+ if ( !empty( $p['driver'] ) ) {
+ if ( in_array( $p['driver'], $possibleDrivers ) ) {
+ $driver = $p['driver'];
+ } else {
+ throw new InvalidArgumentException( __METHOD__ .
+ " type '$dbType' does not support driver '{$p['driver']}'" );
+ }
+ } else {
+ foreach ( $possibleDrivers as $posDriver ) {
+ if ( extension_loaded( $posDriver ) ) {
+ $driver = $posDriver;
+ break;
+ }
+ }
+ }
+ } else {
+ $driver = $dbType;
+ }
+ if ( $driver === false ) {
+ throw new InvalidArgumentException( __METHOD__ .
+ " no viable database extension found for type '$dbType'" );
+ }
+
+ // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
+ // and everything else doesn't use a schema (e.g. null)
+ // Although postgres and oracle support schemas, we don't use them (yet)
+ // to maintain backwards compatibility
+ $defaultSchemas = [
+ 'mssql' => 'get from global',
+ ];
+
+ $class = 'Database' . ucfirst( $driver );
+ if ( class_exists( $class ) && is_subclass_of( $class, 'IDatabase' ) ) {
+ // Resolve some defaults for b/c
+ $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
+ $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
+ $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'] : [];
+ $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : '';
+ if ( !isset( $p['schema'] ) ) {
+ $p['schema'] = isset( $defaultSchemas[$dbType] ) ? $defaultSchemas[$dbType] : null;
+ }
+ $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
+ $p['cliMode'] = $wgCommandLineMode;
+
+ $conn = new $class( $p );
+ if ( isset( $p['connLogger'] ) ) {
+ $conn->connLogger = $p['connLogger'];
+ }
+ if ( isset( $p['queryLogger'] ) ) {
+ $conn->queryLogger = $p['queryLogger'];
+ }
+ if ( isset( $p['errorLogger'] ) ) {
+ $conn->errorLogger = $p['errorLogger'];
+ } else {
+ $conn->errorLogger = [ MWExceptionHandler::class, 'logException' ];
+ }
+ } else {
+ $conn = null;
+ }
+
+ return $conn;
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->queryLogger = $logger;
+ }
+
public function getServerInfo() {
return $this->getServerVersion();
}
* - false to disable debugging
* - omitted or null to do nothing
*
- * @return bool|null Previous value of the flag
+ * @return bool Previous value of the flag
+ * @deprecated since 1.28; use setFlag()
*/
public function debug( $debug = null ) {
- return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
+ $res = $this->getFlag( DBO_DEBUG );
+ if ( $debug !== null ) {
+ $debug ? $this->setFlag( DBO_DEBUG ) : $this->clearFlag( DBO_DEBUG );
+ }
+
+ return $res;
}
public function bufferResults( $buffer = null ) {
- if ( is_null( $buffer ) ) {
- return !(bool)( $this->mFlags & DBO_NOBUFFER );
- } else {
- return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
+ $res = !$this->getFlag( DBO_NOBUFFER );
+ if ( $buffer !== null ) {
+ $buffer ? $this->clearFlag( DBO_NOBUFFER ) : $this->setFlag( DBO_NOBUFFER );
}
+
+ return $res;
}
/**
* @return bool The previous value of the flag.
*/
protected function ignoreErrors( $ignoreErrors = null ) {
- return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
+ $res = $this->getFlag( DBO_IGNORE );
+ if ( $ignoreErrors !== null ) {
+ $ignoreErrors ? $this->setFlag( DBO_IGNORE ) : $this->clearFlag( DBO_IGNORE );
+ }
+
+ return $res;
}
public function trxLevel() {
}
public function tablePrefix( $prefix = null ) {
- return wfSetVar( $this->mTablePrefix, $prefix );
+ $old = $this->mTablePrefix;
+ $this->mTablePrefix = $prefix;
+
+ return $old;
}
public function dbSchema( $schema = null ) {
- return wfSetVar( $this->mSchema, $schema );
+ $old = $this->mSchema;
+ $this->mSchema = $schema;
+
+ return $old;
}
/**
}
}
- /**
- * Set a lazy-connecting DB handle to the master DB (for replication status purposes)
- *
- * @param IDatabase $conn
- * @since 1.27
- */
public function setLazyMasterHandle( IDatabase $conn ) {
$this->lazyMasterHandle = $conn;
}
return $this->lazyMasterHandle;
}
- /**
- * @return TransactionProfiler
- */
- protected function getTransactionProfiler() {
- return $this->trxProfiler;
- }
-
/**
* @param TransactionProfiler $profiler
* @since 1.27
}
}
- /**
- * Return a path to the DBMS-specific SQL file if it exists,
- * otherwise default SQL file
- *
- * @param string $filename
- * @return string
- */
- private function getSqlFilePath( $filename ) {
- global $IP;
- $dbmsSpecificFilePath = "$IP/maintenance/" . $this->getType() . "/$filename";
- if ( file_exists( $dbmsSpecificFilePath ) ) {
- return $dbmsSpecificFilePath;
- } else {
- return "$IP/maintenance/$filename";
- }
- }
-
- /**
- * Return a path to the DBMS-specific schema file,
- * otherwise default to tables.sql
- *
- * @return string
- */
- public function getSchemaPath() {
- return $this->getSqlFilePath( 'tables.sql' );
- }
-
- /**
- * Return a path to the DBMS-specific update key file,
- * otherwise default to update-keys.sql
- *
- * @return string
- */
- public function getUpdateKeysPath() {
- return $this->getSqlFilePath( 'update-keys.sql' );
- }
-
/**
* Get information about an index into an object
* @param string $table Table name
*/
abstract function strencode( $s );
- /**
- * Constructor.
- *
- * FIXME: It is possible to construct a Database object with no associated
- * connection object, by specifying no parameters to __construct(). This
- * feature is deprecated and should be removed.
- *
- * DatabaseBase subclasses should not be constructed directly in external
- * code. DatabaseBase::factory() should be used instead.
- *
- * @param array $params Parameters passed from DatabaseBase::factory()
- */
- function __construct( array $params ) {
- global $wgDBprefix, $wgDBmwschema;
-
- $this->srvCache = ObjectCache::getLocalServerInstance( 'hash' );
-
- $server = $params['host'];
- $user = $params['user'];
- $password = $params['password'];
- $dbName = $params['dbname'];
- $flags = $params['flags'];
- $tablePrefix = $params['tablePrefix'];
- $schema = $params['schema'];
- $foreign = $params['foreign'];
-
- $this->cliMode = isset( $params['cliMode'] )
- ? $params['cliMode']
- : ( PHP_SAPI === 'cli' );
-
- $this->mFlags = $flags;
- if ( $this->mFlags & DBO_DEFAULT ) {
- if ( $this->cliMode ) {
- $this->mFlags &= ~DBO_TRX;
- } else {
- $this->mFlags |= DBO_TRX;
- }
- }
-
- $this->mSessionVars = $params['variables'];
-
- /** Get the default table prefix*/
- if ( $tablePrefix === 'get from global' ) {
- $this->mTablePrefix = $wgDBprefix;
- } else {
- $this->mTablePrefix = $tablePrefix;
- }
-
- /** Get the database schema*/
- if ( $schema === 'get from global' ) {
- $this->mSchema = $wgDBmwschema;
- } else {
- $this->mSchema = $schema;
- }
-
- $this->mForeign = $foreign;
-
- $this->profiler = isset( $params['profiler'] )
- ? $params['profiler']
- : Profiler::instance(); // @TODO: remove global state
- $this->trxProfiler = isset( $params['trxProfiler'] )
- ? $params['trxProfiler']
- : new TransactionProfiler();
-
- if ( $user ) {
- $this->open( $server, $user, $password, $dbName );
- }
-
- }
-
/**
* Called by serialize. Throw an exception when DB connection is serialized.
* This causes problems on some database engines because the connection is
* not restored on unserialize.
*/
public function __sleep() {
- throw new MWException( 'Database serialization may cause problems, since ' .
+ throw new RuntimeException( 'Database serialization may cause problems, since ' .
'the connection is not restored on wakeup.' );
}
- /**
- * Given a DB type, construct the name of the appropriate child class of
- * DatabaseBase. This is designed to replace all of the manual stuff like:
- * $class = 'Database' . ucfirst( strtolower( $dbType ) );
- * as well as validate against the canonical list of DB types we have
- *
- * This factory function is mostly useful for when you need to connect to a
- * database other than the MediaWiki default (such as for external auth,
- * an extension, et cetera). Do not use this to connect to the MediaWiki
- * database. Example uses in core:
- * @see LoadBalancer::reallyOpenConnection()
- * @see ForeignDBRepo::getMasterDB()
- * @see WebInstallerDBConnect::execute()
- *
- * @since 1.18
- *
- * @param string $dbType A possible DB type
- * @param array $p An array of options to pass to the constructor.
- * Valid options are: host, user, password, dbname, flags, tablePrefix, schema, driver
- * @throws MWException If the database driver or extension cannot be found
- * @return DatabaseBase|null DatabaseBase subclass or null
- */
- final public static function factory( $dbType, $p = [] ) {
- global $wgCommandLineMode;
-
- $canonicalDBTypes = [
- 'mysql' => [ 'mysqli', 'mysql' ],
- 'postgres' => [],
- 'sqlite' => [],
- 'oracle' => [],
- 'mssql' => [],
- ];
-
- $driver = false;
- $dbType = strtolower( $dbType );
- if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
- $possibleDrivers = $canonicalDBTypes[$dbType];
- if ( !empty( $p['driver'] ) ) {
- if ( in_array( $p['driver'], $possibleDrivers ) ) {
- $driver = $p['driver'];
- } else {
- throw new MWException( __METHOD__ .
- " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" );
- }
- } else {
- foreach ( $possibleDrivers as $posDriver ) {
- if ( extension_loaded( $posDriver ) ) {
- $driver = $posDriver;
- break;
- }
- }
- }
- } else {
- $driver = $dbType;
- }
- if ( $driver === false ) {
- throw new MWException( __METHOD__ .
- " no viable database extension found for type '$dbType'" );
- }
-
- // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
- // and everything else doesn't use a schema (e.g. null)
- // Although postgres and oracle support schemas, we don't use them (yet)
- // to maintain backwards compatibility
- $defaultSchemas = [
- 'mssql' => 'get from global',
- ];
-
- $class = 'Database' . ucfirst( $driver );
- if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
- // Resolve some defaults for b/c
- $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
- $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
- $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'] : [];
- $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global';
- if ( !isset( $p['schema'] ) ) {
- $p['schema'] = isset( $defaultSchemas[$dbType] ) ? $defaultSchemas[$dbType] : null;
- }
- $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
- $p['cliMode'] = $wgCommandLineMode;
-
- return new $class( $p );
- } else {
- return null;
- }
- }
-
protected function installErrorHandler() {
$this->mPHPError = false;
$this->htmlErrors = ini_set( 'html_errors', '0' );
- set_error_handler( [ $this, 'connectionErrorHandler' ] );
+ set_error_handler( [ $this, 'connectionerrorLogger' ] );
}
/**
* @param int $errno
* @param string $errstr
*/
- public function connectionErrorHandler( $errno, $errstr ) {
+ public function connectionerrorLogger( $errno, $errstr ) {
$this->mPHPError = $errstr;
}
/**
- * Create a log context to pass to wfLogDBError or other logging functions.
+ * Create a log context to pass to PSR logging functions.
*
* @param array $extras Additional data to add to context
* @return array
public function close() {
if ( $this->mConn ) {
if ( $this->trxLevel() ) {
- if ( !$this->mTrxAutomatic ) {
- wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
- " performing implicit commit before closing connection!" );
- }
-
$this->commit( __METHOD__, self::FLUSHING_INTERNAL );
}
$closed = $this->closeConnection();
$this->mConn = false;
} elseif ( $this->mTrxIdleCallbacks || $this->mTrxEndCallbacks ) { // sanity
- throw new MWException( "Transaction callbacks still pending." );
+ throw new RuntimeException( "Transaction callbacks still pending." );
} else {
$closed = true;
}
# Keep track of whether the transaction has write queries pending
if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWrite ) {
$this->mTrxDoneWrites = true;
- $this->getTransactionProfiler()->transactionWritingIn(
+ $this->trxProfiler->transactionWritingIn(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
if ( $this->debug() ) {
- wfDebugLog( 'queries', sprintf( "%s: %s", $this->mDBname, $commentedSql ) );
+ $this->queryLogger->debug( "{$this->mDBname} {$commentedSql}" );
}
# Avoid fatals if close() was called
$lastErrno = $this->lastErrno();
# Update state tracking to reflect transaction loss due to disconnection
$this->handleTransactionLoss();
- wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->reconnect() ) {
- wfDebug( "Reconnected\n" );
$msg = __METHOD__ . ": lost connection to {$this->getServer()}; reconnected";
- wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
+ $this->connLogger->warning( $msg );
+ $this->queryLogger->warning(
+ "$msg:\n" . ( new RuntimeException() )->getTraceAsString() );
if ( !$recoverable ) {
# Callers may catch the exception and continue to use the DB
$ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
}
} else {
- wfDebug( "Failed\n" );
+ $msg = __METHOD__ . ": lost connection to {$this->getServer()} permanently";
+ $this->connLogger->error( $msg );
}
}
# 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 ) {
- $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $queryProf = 'query-m: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
} else {
- $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $queryProf = 'query: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
}
# Include query transaction state
$this->mRTTEstimate = $queryRuntime;
}
- $this->getTransactionProfiler()->recordQueryCompletion(
+ $this->trxProfiler->recordQueryCompletion(
$queryProf, $startTime, $isWrite, $this->affectedRows()
);
MWDebug::query( $sql, $fname, $isMaster, $queryRuntime );
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
if ( $this->ignoreErrors() || $tempIgnore ) {
- wfDebug( "SQL ERROR (ignored): $error\n" );
+ $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
} else {
$sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
- wfLogDBError(
+ $this->queryLogger->error(
"{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
$this->getLogContext( [
'method' => __METHOD__,
'fname' => $fname,
] )
);
- wfDebug( "SQL ERROR: " . $error . "\n" );
+ $this->queryLogger->debug( "SQL ERROR: " . $error . "\n" );
throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
}
*
* @return array
*/
- protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
+ protected function prepare( $sql, $func = __METHOD__ ) {
/* MySQL doesn't support prepared statements (yet), so just
* pack up the query for reference. We'll manually replace
* the bits later.
} else {
$useIndex = '';
}
+ if ( isset( $options['IGNORE INDEX'] ) && is_string( $options['IGNORE INDEX'] ) ) {
+ $ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
+ } else {
+ $ignoreIndex = '';
+ }
- return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
+ return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
}
/**
$useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
? $options['USE INDEX']
: [];
+ $ignoreIndexes = ( isset( $options['IGNORE INDEX'] ) && is_array( $options['IGNORE INDEX'] ) )
+ ? $options['IGNORE INDEX']
+ : [];
if ( is_array( $table ) ) {
$from = ' FROM ' .
- $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
+ $this->tableNamesWithIndexClauseOrJOIN( $table, $useIndexes, $ignoreIndexes, $join_conds );
} elseif ( $table != '' ) {
if ( $table[0] == ' ' ) {
$from = ' FROM ' . $table;
} else {
$from = ' FROM ' .
- $this->tableNamesWithUseIndexOrJOIN( [ $table ], $useIndexes, [] );
+ $this->tableNamesWithIndexClauseOrJOIN( [ $table ], $useIndexes, $ignoreIndexes, [] );
}
} else {
$from = '';
}
- list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
+ list( $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ) =
$this->makeSelectOptions( $options );
if ( !empty( $conds ) ) {
if ( is_array( $conds ) ) {
$conds = $this->makeList( $conds, LIST_AND );
}
- $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
+ $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex WHERE $conds $preLimitTail";
} else {
- $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
+ $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
}
if ( isset( $options['LIMIT'] ) ) {
public function makeList( $a, $mode = LIST_COMMA ) {
if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
}
$first = true;
unset( $value[$nullKey] );
}
if ( count( $value ) == 0 && !$includeNull ) {
- throw new MWException( __METHOD__ . ": empty input for field $field" );
+ throw new InvalidArgumentException( __METHOD__ . ": empty input for field $field" );
} elseif ( count( $value ) == 0 ) {
// only check if $field is null
$list .= "$field IS NULL";
* @return string Full database name
*/
public function tableName( $name, $format = 'quoted' ) {
- global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema;
# Skip the entire process when we have a string quoted on both ends.
# Note that we check the end so that we will still quote any use of
# use of `database`.table. But won't break things if someone wants
$schema = null;
} else {
list( $table ) = $dbDetails;
- if ( $wgSharedDB !== null # We have a shared database
- && $this->mForeign == false # We're not working on a foreign database
- && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`'
- && in_array( $table, $wgSharedTables ) # A shared table is selected
- ) {
- $database = $wgSharedDB;
- $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema;
- $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
+ if ( isset( $this->tableAliases[$table] ) ) {
+ $database = $this->tableAliases[$table]['dbname'];
+ $schema = is_string( $this->tableAliases[$table]['schema'] )
+ ? $this->tableAliases[$table]['schema']
+ : $this->mSchema;
+ $prefix = is_string( $this->tableAliases[$table]['prefix'] )
+ ? $this->tableAliases[$table]['prefix']
+ : $this->mTablePrefix;
} else {
$database = null;
$schema = $this->mSchema; # Default schema
# Quote $table and apply the prefix if not quoted.
# $tableName might be empty if this is called from Database::replaceVars()
$tableName = "{$prefix}{$table}";
- if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) {
+ if ( $format == 'quoted'
+ && !$this->isQuotedIdentifier( $tableName ) && $tableName !== ''
+ ) {
$tableName = $this->addIdentifierQuotes( $tableName );
}
/**
* Get the aliased table name clause for a FROM clause
- * which might have a JOIN and/or USE INDEX clause
+ * which might have a JOIN and/or USE INDEX or IGNORE INDEX clause
*
* @param array $tables ( [alias] => table )
* @param array $use_index Same as for select()
+ * @param array $ignore_index Same as for select()
* @param array $join_conds Same as for select()
* @return string
*/
- protected function tableNamesWithUseIndexOrJOIN(
- $tables, $use_index = [], $join_conds = []
+ protected function tableNamesWithIndexClauseOrJOIN(
+ $tables, $use_index = [], $ignore_index = [], $join_conds = []
) {
$ret = [];
$retJOIN = [];
$use_index = (array)$use_index;
+ $ignore_index = (array)$ignore_index;
$join_conds = (array)$join_conds;
foreach ( $tables as $alias => $table ) {
$tableClause .= ' ' . $use;
}
}
+ if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
+ $ignore = $this->ignoreIndexClause( implode( ',', (array)$ignore_index[$alias] ) );
+ if ( $ignore != '' ) {
+ $tableClause .= ' ' . $ignore;
+ }
+ }
$on = $this->makeList( (array)$conds, LIST_AND );
if ( $on != '' ) {
$tableClause .= ' ON (' . $on . ')';
implode( ',', (array)$use_index[$alias] )
);
+ $ret[] = $tableClause;
+ } elseif ( isset( $ignore_index[$alias] ) ) {
+ // Is there an INDEX clause for this table?
+ $tableClause = $this->tableNameWithAlias( $table, $alias );
+ $tableClause .= ' ' . $this->ignoreIndexClause(
+ implode( ',', (array)$ignore_index[$alias] )
+ );
+
$ret[] = $tableClause;
} else {
$tableClause = $this->tableNameWithAlias( $table, $alias );
return '';
}
+ /**
+ * IGNORE INDEX clause. Unlikely to be useful for anything but MySQL. This
+ * is only needed because a) MySQL must be as efficient as possible due to
+ * its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about
+ * which index to pick. Anyway, other databases might have different
+ * indexes on a given table. So don't bother overriding this unless you're
+ * MySQL.
+ * @param string $index
+ * @return string
+ */
+ public function ignoreIndexClause( $index ) {
+ return '';
+ }
+
public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
$quotedTable = $this->tableName( $table );
$fname = __METHOD__
) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this,
- 'DatabaseBase::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
- $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
+ $res = $this->query( $sql, __METHOD__ );
$row = $this->fetchObject( $res );
$m = [];
public function delete( $table, $conds, $fname = __METHOD__ ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with no conditions' );
}
$table = $this->tableName( $table );
$selectOptions = [ $selectOptions ];
}
- list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
+ list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) = $this->makeSelectOptions(
+ $selectOptions );
if ( is_array( $srcTable ) ) {
$srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
$sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
" SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex ";
+ " FROM $srcTable $useIndex $ignoreIndex ";
if ( $conds != '*' ) {
if ( is_array( $conds ) ) {
return false;
}
- final public function onTransactionResolution( callable $callback ) {
+ final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
if ( !$this->mTrxLevel ) {
throw new DBUnexpectedError( $this, "No transaction is active." );
}
- $this->mTrxEndCallbacks[] = [ $callback, wfGetCaller() ];
+ $this->mTrxEndCallbacks[] = [ $callback, $fname ];
}
- final public function onTransactionIdle( callable $callback ) {
- $this->mTrxIdleCallbacks[] = [ $callback, wfGetCaller() ];
+ final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
+ $this->mTrxIdleCallbacks[] = [ $callback, $fname ];
if ( !$this->mTrxLevel ) {
$this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
}
}
- final public function onTransactionPreCommitOrIdle( callable $callback ) {
+ final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
if ( $this->mTrxLevel ) {
- $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
+ $this->mTrxPreCommitCallbacks[] = [ $callback, $fname ];
} else {
// If no transaction is active, then make one for this callback
$this->startAtomic( __METHOD__ );
final public function setTransactionListener( $name, callable $callback = null ) {
if ( $callback ) {
- $this->mTrxRecurringCallbacks[$name] = [ $callback, wfGetCaller() ];
+ $this->mTrxRecurringCallbacks[$name] = $callback;
} else {
unset( $this->mTrxRecurringCallbacks[$name] );
}
$this->clearFlag( DBO_TRX ); // restore auto-commit
}
} catch ( Exception $ex ) {
- MWExceptionHandler::logException( $ex );
+ call_user_func( $this->errorLogger, $ex );
$e = $e ?: $ex;
// Some callbacks may use startAtomic/endAtomic, so make sure
// their transactions are ended so other callbacks don't fail
list( $phpCallback ) = $callback;
call_user_func( $phpCallback );
} catch ( Exception $ex ) {
- MWExceptionHandler::logException( $ex );
+ call_user_func( $this->errorLogger, $ex );
$e = $e ?: $ex;
}
}
/** @var Exception $e */
$e = null; // first exception
- foreach ( $this->mTrxRecurringCallbacks as $callback ) {
+ foreach ( $this->mTrxRecurringCallbacks as $phpCallback ) {
try {
- list( $phpCallback ) = $callback;
$phpCallback( $trigger, $this );
} catch ( Exception $ex ) {
- MWExceptionHandler::logException( $ex );
+ call_user_func( $this->errorLogger, $ex );
$e = $e ?: $ex;
}
}
} else {
// @TODO: make this an exception at some point
$msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
- wfLogDBError( $msg );
- wfWarn( $msg );
+ $this->queryLogger->error( $msg );
return; // join the main transaction set
}
} elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
// @TODO: make this an exception at some point
$msg = "$fname: Implicit transaction expected (DBO_TRX set).";
- wfLogDBError( $msg );
- wfWarn( $msg );
+ $this->queryLogger->error( $msg );
return; // let any writes be in the main transaction
}
$this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
$this->mTrxAutomaticAtomic = false;
$this->mTrxAtomicLevels = [];
- $this->mTrxShortId = wfRandomString( 12 );
+ $this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
$this->mTrxWriteDuration = 0.0;
$this->mTrxWriteQueryCount = 0;
$this->mTrxWriteAdjDuration = 0.0;
}
} else {
if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to commit, something got out of sync." );
+ $this->queryLogger->error( "$fname: No transaction to commit, something got out of sync." );
return; // nothing to do
} elseif ( $this->mTrxAutomatic ) {
// @TODO: make this an exception at some point
$msg = "$fname: Explicit commit of implicit transaction.";
- wfLogDBError( $msg );
- wfWarn( $msg );
+ $this->queryLogger->error( $msg );
return; // wait for the main transaction set commit round
}
}
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
$this->mDoneWrites = microtime( true );
- $this->getTransactionProfiler()->transactionWritingOut(
+ $this->trxProfiler->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
}
}
} else {
if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to rollback, something got out of sync." );
+ $this->queryLogger->error(
+ "$fname: No transaction to rollback, something got out of sync." );
return; // nothing to do
} elseif ( $this->getFlag( DBO_TRX ) ) {
throw new DBUnexpectedError(
$this->doRollback( $fname );
$this->mTrxAtomicLevels = [];
if ( $this->mTrxDoneWrites ) {
- $this->getTransactionProfiler()->transactionWritingOut(
+ $this->trxProfiler->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
* @param string $newName Name of table to be created
* @param bool $temporary Whether the new table should be temporary
* @param string $fname Calling function name
- * @throws MWException
+ * @throws RuntimeException
* @return bool True if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
$fname = __METHOD__
) {
- throw new MWException(
- 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
+ throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
function listTables( $prefix = null, $fname = __METHOD__ ) {
- throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
+ throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
/**
*
* @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
* @param string $fname Name of calling function
- * @throws MWException
+ * @throws RuntimeException
* @return array
* @since 1.22
*/
public function listViews( $prefix = null, $fname = __METHOD__ ) {
- throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
+ throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
/**
* Differentiates between a TABLE and a VIEW
*
* @param string $name Name of the database-structure to test.
- * @throws MWException
+ * @throws RuntimeException
* @return bool
* @since 1.22
*/
public function isView( $name ) {
- throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
+ throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
public function timestamp( $ts = 0 ) {
* generated dynamically using $filename
* @param bool|callable $inputCallback Optional function called for each
* complete line sent
- * @throws Exception|MWException
* @return bool|string
+ * @throws Exception
*/
public function sourceFile(
$filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
MediaWiki\restoreWarnings();
if ( false === $fp ) {
- throw new MWException( "Could not open \"{$filename}\".\n" );
+ throw new RuntimeException( "Could not open \"{$filename}\".\n" );
}
if ( !$fname ) {
return $error;
}
- /**
- * Get the full path of a patch file. Originally based on archive()
- * from updaters.inc. Keep in mind this always returns a patch, as
- * it fails back to MySQL if no DB-specific patch can be found
- *
- * @param string $patch The name of the patch, like patch-something.sql
- * @return string Full path to patch file
- */
- public function patchPath( $patch ) {
- global $IP;
-
- $dbType = $this->getType();
- if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
- return "$IP/maintenance/$dbType/archives/$patch";
- } else {
- return "$IP/maintenance/archives/$patch";
- }
- }
-
public function setSchemaVars( $vars ) {
$this->mSchemaVars = $vars;
}
// There is a good chance an exception was thrown, causing any early return
// from the caller. Let any error handler get a chance to issue rollback().
// If there isn't one, let the error bubble up and trigger server-side rollback.
- $this->onTransactionResolution( function () use ( $lockKey, $fname ) {
- $this->unlock( $lockKey, $fname );
- } );
+ $this->onTransactionResolution(
+ function () use ( $lockKey, $fname ) {
+ $this->unlock( $lockKey, $fname );
+ },
+ $fname
+ );
} else {
$this->unlock( $lockKey, $fname );
}
} );
- $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
+ $this->commit( $fname, self::FLUSHING_INTERNAL );
return $unlocker;
}
return is_string( $reason ) ? $reason : false;
}
+ public function setTableAliases( array $aliases ) {
+ $this->tableAliases = $aliases;
+ }
+
/**
* @since 1.19
* @return string