summaryrefslogtreecommitdiff
path: root/includes/db/Database.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/db/Database.php')
-rw-r--r--includes/db/Database.php517
1 files changed, 387 insertions, 130 deletions
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 65a74abf..10645608 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -24,13 +24,6 @@
* @ingroup Database
*/
-/** Number of times to re-try an operation in case of deadlock */
-define( 'DEADLOCK_TRIES', 4 );
-/** Minimum time to wait before retry, in microseconds */
-define( 'DEADLOCK_DELAY_MIN', 500000 );
-/** Maximum time to wait before retry */
-define( 'DEADLOCK_DELAY_MAX', 1500000 );
-
/**
* Base interface for all DBMS-specific code. At a bare minimum, all of the
* following must be implemented to support MediaWiki
@@ -165,7 +158,7 @@ interface DatabaseType {
* @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 = 'Database::indexInfo' );
+ function indexInfo( $table, $index, $fname = __METHOD__ );
/**
* Get the number of rows affected by the last write query
@@ -191,7 +184,7 @@ interface DatabaseType {
*
* @return string: wikitext of a link to the server software's web site
*/
- static function getSoftwareLink();
+ function getSoftwareLink();
/**
* A string describing the current software version, like from
@@ -212,10 +205,22 @@ interface DatabaseType {
}
/**
+ * Interface for classes that implement or wrap DatabaseBase
+ * @ingroup Database
+ */
+interface IDatabase {}
+
+/**
* Database abstraction object
* @ingroup Database
*/
-abstract class DatabaseBase implements DatabaseType {
+abstract class DatabaseBase implements IDatabase, DatabaseType {
+ /** Number of times to re-try an operation in case of deadlock */
+ const DEADLOCK_TRIES = 4;
+ /** Minimum time to wait before retry, in microseconds */
+ const DEADLOCK_DELAY_MIN = 500000;
+ /** Maximum time to wait before retry */
+ const DEADLOCK_DELAY_MAX = 1500000;
# ------------------------------------------------------------------------------
# Variables
@@ -230,14 +235,14 @@ abstract class DatabaseBase implements DatabaseType {
protected $mConn = null;
protected $mOpened = false;
- /**
- * @since 1.20
- * @var array of Closure
- */
+ /** @var callable[] */
protected $mTrxIdleCallbacks = array();
+ /** @var callable[] */
+ protected $mTrxPreCommitCallbacks = array();
protected $mTablePrefix;
protected $mFlags;
+ protected $mForeign;
protected $mTrxLevel = 0;
protected $mErrorCount = 0;
protected $mLBInfo = array();
@@ -282,6 +287,12 @@ abstract class DatabaseBase implements DatabaseType {
*/
protected $fileHandle = null;
+ /**
+ * @since 1.22
+ * @var Process cache of VIEWs names in the database
+ */
+ protected $allViews = null;
+
# ------------------------------------------------------------------------------
# Accessors
# ------------------------------------------------------------------------------
@@ -355,6 +366,8 @@ abstract class DatabaseBase implements DatabaseType {
* code should use lastErrno() and lastError() to handle the
* situation as appropriate.
*
+ * Do not use this function outside of the Database classes.
+ *
* @param $ignoreErrors bool|null
*
* @return bool The previous value of the flag.
@@ -553,12 +566,14 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if there is a transaction open with possible write
- * queries or transaction idle callbacks waiting on it to finish.
+ * queries or transaction pre-commit/idle callbacks waiting on it to finish.
*
* @return bool
*/
public function writesOrCallbacksPending() {
- return $this->mTrxLevel && ( $this->mTrxDoneWrites || $this->mTrxIdleCallbacks );
+ return $this->mTrxLevel && (
+ $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
+ );
}
/**
@@ -575,7 +590,6 @@ abstract class DatabaseBase implements DatabaseType {
* @param $flag Integer: DBO_* constants from Defines.php:
* - DBO_DEBUG: output some debug info (same as debug())
* - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
- * - DBO_IGNORE: ignore errors (same as ignoreErrors())
* - DBO_TRX: automatically start transactions
* - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
* and removes it in command line mode
@@ -584,7 +598,7 @@ abstract class DatabaseBase implements DatabaseType {
public function setFlag( $flag ) {
global $wgDebugDBTransactions;
$this->mFlags |= $flag;
- if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) {
+ if ( ( $flag & DBO_TRX ) & $wgDebugDBTransactions ) {
wfDebug( "Implicit transactions are now disabled.\n" );
}
}
@@ -654,15 +668,28 @@ abstract class DatabaseBase implements DatabaseType {
/**
* 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.
+ *
+ * FIXME: The long list of formal parameters here is not really appropriate
+ * for MySQL, and not at all appropriate for any other DBMS. It should be
+ * replaced by named parameters as in DatabaseBase::factory().
+ *
+ * DatabaseBase subclasses should not be constructed directly in external
+ * code. DatabaseBase::factory() should be used instead.
+ *
* @param string $server database server host
* @param string $user database user name
* @param string $password database user password
* @param string $dbName database name
* @param $flags
* @param string $tablePrefix database table prefixes. By default use the prefix gave in LocalSettings.php
+ * @param bool $foreign disable some operations specific to local databases
*/
function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $flags = 0, $tablePrefix = 'get from global'
+ $flags = 0, $tablePrefix = 'get from global', $foreign = false
) {
global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions;
@@ -689,6 +716,8 @@ abstract class DatabaseBase implements DatabaseType {
$this->mTablePrefix = $tablePrefix;
}
+ $this->mForeign = $foreign;
+
if ( $user ) {
$this->open( $server, $user, $password, $dbName );
}
@@ -706,7 +735,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* 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( $type ) );
+ * $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
@@ -714,7 +743,6 @@ abstract class DatabaseBase implements DatabaseType {
* an extension, et cetera). Do not use this to connect to the MediaWiki
* database. Example uses in core:
* @see LoadBalancer::reallyOpenConnection()
- * @see ExternalUser_MediaWiki::initFromCond()
* @see ForeignDBRepo::getMasterDB()
* @see WebInstaller_DBConnect::execute()
*
@@ -722,24 +750,55 @@ abstract class DatabaseBase implements DatabaseType {
*
* @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
+ * Valid options are: host, user, password, dbname, flags, tablePrefix, driver
* @return DatabaseBase subclass or null
*/
final public static function factory( $dbType, $p = array() ) {
$canonicalDBTypes = array(
- 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql'
+ 'mysql' => array( 'mysqli', 'mysql' ),
+ 'postgres' => array(),
+ 'sqlite' => array(),
+ 'oracle' => array(),
+ 'mssql' => array(),
);
+
+ $driver = false;
$dbType = strtolower( $dbType );
- $class = 'Database' . ucfirst( $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'" );
+ }
- if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
+ $class = 'Database' . ucfirst( $driver );
+ if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
return new $class(
isset( $p['host'] ) ? $p['host'] : false,
isset( $p['user'] ) ? $p['user'] : false,
isset( $p['password'] ) ? $p['password'] : false,
isset( $p['dbname'] ) ? $p['dbname'] : false,
isset( $p['flags'] ) ? $p['flags'] : 0,
- isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'
+ isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global',
+ isset( $p['foreign'] ) ? $p['foreign'] : false
);
} else {
return null;
@@ -772,8 +831,9 @@ abstract class DatabaseBase implements DatabaseType {
/**
* @param $errno
* @param $errstr
+ * @access private
*/
- protected function connectionErrorHandler( $errno, $errstr ) {
+ public function connectionErrorHandler( $errno, $errstr ) {
$this->mPHPError = $errstr;
}
@@ -870,23 +930,8 @@ abstract class DatabaseBase implements DatabaseType {
* @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object
* for a successful read query, or false on failure if $tempIgnore set
*/
- public function query( $sql, $fname = '', $tempIgnore = false ) {
- $isMaster = !is_null( $this->getLBInfo( 'master' ) );
- if ( !Profiler::instance()->isStub() ) {
- # 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 );
- $totalProf = 'DatabaseBase::query-master';
- } else {
- $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'DatabaseBase::query';
- }
-
- wfProfileIn( $totalProf );
- wfProfileIn( $queryProf );
- }
+ public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
+ global $wgUser, $wgDebugDBTransactions;
$this->mLastQuery = $sql;
if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
@@ -896,7 +941,6 @@ abstract class DatabaseBase implements DatabaseType {
}
# Add a comment for easy SHOW PROCESSLIST interpretation
- global $wgUser;
if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
$userName = $wgUser->getName();
if ( mb_strlen( $userName ) > 15 ) {
@@ -920,7 +964,6 @@ abstract class DatabaseBase implements DatabaseType {
# is really used by application
$sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
- global $wgDebugDBTransactions;
if ( $wgDebugDBTransactions ) {
wfDebug( "Implicit transaction start.\n" );
}
@@ -932,6 +975,22 @@ abstract class DatabaseBase implements DatabaseType {
# Keep track of whether the transaction has write queries pending
if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
$this->mTrxDoneWrites = true;
+ Profiler::instance()->transactionWritingIn( $this->mServer, $this->mDBname );
+ }
+
+ $isMaster = !is_null( $this->getLBInfo( 'master' ) );
+ if ( !Profiler::instance()->isStub() ) {
+ # 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 );
+ $totalProf = 'DatabaseBase::query-master';
+ } else {
+ $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query';
+ }
+ wfProfileIn( $totalProf );
+ wfProfileIn( $queryProf );
}
if ( $this->debug() ) {
@@ -945,10 +1004,6 @@ abstract class DatabaseBase implements DatabaseType {
wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
}
- if ( istainted( $sql ) & TC_MYSQL ) {
- throw new MWException( 'Tainted query found' );
- }
-
$queryId = MWDebug::query( $sql, $fname, $isMaster );
# Do the query and handle errors
@@ -961,6 +1016,7 @@ abstract class DatabaseBase implements DatabaseType {
# Transaction is gone, like it or not
$this->mTrxLevel = 0;
$this->mTrxIdleCallbacks = array(); // cancel
+ $this->mTrxPreCommitCallbacks = array(); // cancel
wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->ping() ) {
@@ -1091,17 +1147,22 @@ abstract class DatabaseBase implements DatabaseType {
* @return String
*/
protected function fillPreparedArg( $matches ) {
- switch( $matches[1] ) {
- case '\\?': return '?';
- case '\\!': return '!';
- case '\\&': return '&';
+ switch ( $matches[1] ) {
+ case '\\?':
+ return '?';
+ case '\\!':
+ return '!';
+ case '\\&':
+ return '&';
}
list( /* $n */, $arg ) = each( $this->preparedArgs );
- switch( $matches[1] ) {
- case '?': return $this->addQuotes( $arg );
- case '!': return $arg;
+ switch ( $matches[1] ) {
+ case '?':
+ return $this->addQuotes( $arg );
+ case '!':
+ return $arg;
case '&':
# return $this->addQuotes( file_get_contents( $arg ) );
throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
@@ -1117,7 +1178,8 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $res Mixed: A SQL result
*/
- public function freeResult( $res ) {}
+ public function freeResult( $res ) {
+ }
/**
* A SELECT wrapper which returns a single field from a single result row.
@@ -1136,9 +1198,9 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool|mixed The value from the field, or false on failure.
*/
- public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
- $options = array() )
- {
+ public function selectField( $table, $var, $cond = '', $fname = __METHOD__,
+ $options = array()
+ ) {
if ( !is_array( $options ) ) {
$options = array( $options );
}
@@ -1236,7 +1298,7 @@ abstract class DatabaseBase implements DatabaseType {
$startOpts .= ' SQL_NO_CACHE';
}
- if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
+ if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
$useIndex = $this->useIndexClause( $options['USE INDEX'] );
} else {
$useIndex = '';
@@ -1427,7 +1489,7 @@ abstract class DatabaseBase implements DatabaseType {
* DBQueryError exception will be thrown, except if the "ignore errors"
* option was set, in which case false will be returned.
*/
- public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
+ public function select( $table, $vars, $conds = '', $fname = __METHOD__,
$options = array(), $join_conds = array() ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -1450,7 +1512,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return string SQL query string.
* @see DatabaseBase::select()
*/
- public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
+ public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
$options = array(), $join_conds = array() )
{
if ( is_array( $vars ) ) {
@@ -1458,28 +1520,26 @@ abstract class DatabaseBase implements DatabaseType {
}
$options = (array)$options;
+ $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
+ ? $options['USE INDEX']
+ : array();
if ( is_array( $table ) ) {
- $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
- ? $options['USE INDEX']
- : array();
- if ( count( $join_conds ) || count( $useIndex ) ) {
- $from = ' FROM ' .
- $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds );
- } else {
- $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
- }
+ $from = ' FROM ' .
+ $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
} elseif ( $table != '' ) {
if ( $table[0] == ' ' ) {
$from = ' FROM ' . $table;
} else {
- $from = ' FROM ' . $this->tableName( $table );
+ $from = ' FROM ' .
+ $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() );
}
} else {
$from = '';
}
- list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
+ list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
+ $this->makeSelectOptions( $options );
if ( !empty( $conds ) ) {
if ( is_array( $conds ) ) {
@@ -1517,7 +1577,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return object|bool
*/
- public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
+ public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
$options = array(), $join_conds = array() )
{
$options = (array)$options;
@@ -1558,7 +1618,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return Integer: row count
*/
public function estimateRowCount( $table, $vars = '*', $conds = '',
- $fname = 'DatabaseBase::estimateRowCount', $options = array() )
+ $fname = __METHOD__, $options = array() )
{
$rows = 0;
$res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
@@ -1582,19 +1642,20 @@ abstract class DatabaseBase implements DatabaseType {
static function generalizeSQL( $sql ) {
# This does the same as the regexp below would do, but in such a way
# as to avoid crashing php on some large strings.
- # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
+ # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
- $sql = str_replace ( "\\\\", '', $sql );
- $sql = str_replace ( "\\'", '', $sql );
- $sql = str_replace ( "\\\"", '', $sql );
- $sql = preg_replace ( "/'.*'/s", "'X'", $sql );
- $sql = preg_replace ( '/".*"/s', "'X'", $sql );
+ $sql = str_replace( "\\\\", '', $sql );
+ $sql = str_replace( "\\'", '', $sql );
+ $sql = str_replace( "\\\"", '', $sql );
+ $sql = preg_replace( "/'.*'/s", "'X'", $sql );
+ $sql = preg_replace( '/".*"/s', "'X'", $sql );
# All newlines, tabs, etc replaced by single space
- $sql = preg_replace ( '/\s+/', ' ', $sql );
+ $sql = preg_replace( '/\s+/', ' ', $sql );
# All numbers => N
- $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql );
+ $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
+ $sql = preg_replace( '/-?\d+/s', 'N', $sql );
return $sql;
}
@@ -1607,7 +1668,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param string $fname calling function name (optional)
* @return Boolean: whether $table has filed $field
*/
- public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
+ public function fieldExists( $table, $field, $fname = __METHOD__ ) {
$info = $this->fieldInfo( $table, $field );
return (bool)$info;
@@ -1624,8 +1685,8 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool|null
*/
- public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
- if( !$this->tableExists( $table ) ) {
+ public function indexExists( $table, $index, $fname = __METHOD__ ) {
+ if ( !$this->tableExists( $table ) ) {
return null;
}
@@ -1729,7 +1790,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
+ public function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
# No rows to insert, easy just return now
if ( !count( $a ) ) {
return true;
@@ -1828,7 +1889,7 @@ abstract class DatabaseBase implements DatabaseType {
* - LOW_PRIORITY: MySQL-specific, see MySQL manual.
* @return Boolean
*/
- function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) {
+ function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
$table = $this->tableName( $table );
$opts = $this->makeUpdateOptions( $options );
$sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
@@ -2065,6 +2126,7 @@ abstract class DatabaseBase implements DatabaseType {
} 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 ) # Paranoia check to prevent shared tables listing '`table`'
&& in_array( $table, $wgSharedTables ) # A shared table is selected
) {
@@ -2257,11 +2319,11 @@ abstract class DatabaseBase implements DatabaseType {
}
// We can't separate explicit JOIN clauses with ',', use ' ' for those
- $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
- $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
+ $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
+ $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
// Compile our final table clause
- return implode( ' ', array( $straightJoins, $otherJoins ) );
+ return implode( ' ', array( $implicitJoins, $explicitJoins ) );
}
/**
@@ -2274,9 +2336,9 @@ abstract class DatabaseBase implements DatabaseType {
protected function indexName( $index ) {
// Backwards-compatibility hack
$renamed = array(
- 'ar_usertext_timestamp' => 'usertext_timestamp',
- 'un_user_id' => 'user_id',
- 'un_user_ip' => 'user_ip',
+ 'ar_usertext_timestamp' => 'usertext_timestamp',
+ 'un_user_id' => 'user_id',
+ 'un_user_ip' => 'user_ip',
);
if ( isset( $renamed[$index] ) ) {
@@ -2287,8 +2349,7 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * If it's a string, adds quotes and backslashes
- * Otherwise returns as-is
+ * Adds quotes and backslashes.
*
* @param $s string
*
@@ -2445,7 +2506,7 @@ abstract class DatabaseBase implements DatabaseType {
* a field name or an array of field names
* @param string $fname Calling function name (use __METHOD__) for logs/profiling
*/
- public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
+ public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
$quotedTable = $this->tableName( $table );
if ( count( $rows ) == 0 ) {
@@ -2457,7 +2518,7 @@ abstract class DatabaseBase implements DatabaseType {
$rows = array( $rows );
}
- foreach( $rows as $row ) {
+ foreach ( $rows as $row ) {
# Delete rows which collide
if ( $uniqueIndexes ) {
$sql = "DELETE FROM $quotedTable WHERE ";
@@ -2488,7 +2549,7 @@ abstract class DatabaseBase implements DatabaseType {
}
# Now insert the row
- $this->insert( $table, $row );
+ $this->insert( $table, $row, $fname );
}
}
@@ -2527,6 +2588,92 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
+ *
+ * This updates any conflicting rows (according to the unique indexes) using
+ * the provided SET clause and inserts any remaining (non-conflicted) rows.
+ *
+ * $rows may be either:
+ * - A single associative array. The array keys are the field names, and
+ * the values are the values to insert. The values are treated as data
+ * and will be quoted appropriately. If NULL is inserted, this will be
+ * converted to a database NULL.
+ * - An array with numeric keys, holding a list of associative arrays.
+ * 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.
+ *
+ * It may be more efficient to leave off unique indexes which are unlikely
+ * to collide. However if you do this, you run the risk of encountering
+ * errors which wouldn't have occurred in MySQL.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
+ * returns success.
+ *
+ * @param string $table Table name. This will be passed through DatabaseBase::tableName().
+ * @param array $rows A single row or list of rows to insert
+ * @param array $uniqueIndexes List of single field names or field name tuples
+ * @param array $set An array of values to SET. For each array element,
+ * the key gives the field name, and the value gives the data
+ * to set that field to. The data will be quoted by
+ * DatabaseBase::addQuotes().
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options of options
+ *
+ * @return bool
+ * @since 1.22
+ */
+ public function upsert(
+ $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ ) {
+ if ( !count( $rows ) ) {
+ return true; // nothing to do
+ }
+ $rows = is_array( reset( $rows ) ) ? $rows : array( $rows );
+
+ if ( count( $uniqueIndexes ) ) {
+ $clauses = array(); // list WHERE clauses that each identify a single row
+ foreach ( $rows as $row ) {
+ foreach ( $uniqueIndexes as $index ) {
+ $index = is_array( $index ) ? $index : array( $index ); // columns
+ $rowKey = array(); // unique key to this row
+ foreach ( $index as $column ) {
+ $rowKey[$column] = $row[$column];
+ }
+ $clauses[] = $this->makeList( $rowKey, LIST_AND );
+ }
+ }
+ $where = array( $this->makeList( $clauses, LIST_OR ) );
+ } else {
+ $where = false;
+ }
+
+ $useTrx = !$this->mTrxLevel;
+ if ( $useTrx ) {
+ $this->begin( $fname );
+ }
+ try {
+ # Update any existing conflicting row(s)
+ if ( $where !== false ) {
+ $ok = $this->update( $table, $set, $where, $fname );
+ } else {
+ $ok = true;
+ }
+ # Now insert any non-conflicting row(s)
+ $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok;
+ } catch ( Exception $e ) {
+ if ( $useTrx ) {
+ $this->rollback( $fname );
+ }
+ throw $e;
+ }
+ if ( $useTrx ) {
+ $this->commit( $fname );
+ }
+
+ return $ok;
+ }
+
+ /**
* DELETE where the condition is a join.
*
* MySQL overrides this to use a multi-table DELETE syntax, in other databases
@@ -2548,7 +2695,7 @@ abstract class DatabaseBase implements DatabaseType {
* @throws DBUnexpectedError
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
- $fname = 'DatabaseBase::deleteJoin' )
+ $fname = __METHOD__ )
{
if ( !$conds ) {
throw new DBUnexpectedError( $this,
@@ -2614,7 +2761,7 @@ abstract class DatabaseBase implements DatabaseType {
* @throws DBUnexpectedError
* @return bool|ResultWrapper
*/
- public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
+ public function delete( $table, $conds, $fname = __METHOD__ ) {
if ( !$conds ) {
throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
}
@@ -2623,7 +2770,10 @@ abstract class DatabaseBase implements DatabaseType {
$sql = "DELETE FROM $table";
if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ if ( is_array( $conds ) ) {
+ $conds = $this->makeList( $conds, LIST_AND );
+ }
+ $sql .= ' WHERE ' . $conds;
}
return $this->query( $sql, $fname );
@@ -2656,7 +2806,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return ResultWrapper
*/
public function insertSelect( $destTable, $srcTable, $varMap, $conds,
- $fname = 'DatabaseBase::insertSelect',
+ $fname = __METHOD__,
$insertOptions = array(), $selectOptions = array() )
{
$destTable = $this->tableName( $destTable );
@@ -2848,7 +2998,7 @@ abstract class DatabaseBase implements DatabaseType {
$args = func_get_args();
$function = array_shift( $args );
$oldIgnore = $this->ignoreErrors( true );
- $tries = DEADLOCK_TRIES;
+ $tries = self::DEADLOCK_TRIES;
if ( is_array( $function ) ) {
$fname = $function[0];
@@ -2865,7 +3015,7 @@ abstract class DatabaseBase implements DatabaseType {
if ( $errno ) {
if ( $this->wasDeadlock() ) {
# Retry
- usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
+ usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
} else {
$this->reportQueryError( $error, $errno, $sql, $fname );
}
@@ -2955,24 +3105,45 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Run an anonymous function as soon as there is no transaction pending.
* If there is a transaction and it is rolled back, then the callback is cancelled.
+ * Queries in the function will run in AUTO-COMMIT mode unless there are begin() calls.
* Callbacks must commit any transactions that they begin.
*
- * This is useful for updates to different systems or separate transactions are needed.
+ * This is useful for updates to different systems or when separate transactions are needed.
+ * For example, one might want to enqueue jobs into a system outside the database, but only
+ * after the database is updated so that the jobs will see the data when they actually run.
+ * It can also be used for updates that easily cause deadlocks if locks are held too long.
*
+ * @param callable $callback
* @since 1.20
+ */
+ final public function onTransactionIdle( $callback ) {
+ $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() );
+ if ( !$this->mTrxLevel ) {
+ $this->runOnTransactionIdleCallbacks();
+ }
+ }
+
+ /**
+ * Run an anonymous function before the current transaction commits or now if there is none.
+ * If there is a transaction and it is rolled back, then the callback is cancelled.
+ * Callbacks must not start nor commit any transactions.
+ *
+ * This is useful for updates that easily cause deadlocks if locks are held too long
+ * but where atomicity is strongly desired for these updates and some related updates.
*
- * @param Closure $callback
+ * @param callable $callback
+ * @since 1.22
*/
- final public function onTransactionIdle( Closure $callback ) {
+ final public function onTransactionPreCommitOrIdle( $callback ) {
if ( $this->mTrxLevel ) {
- $this->mTrxIdleCallbacks[] = $callback;
+ $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() );
} else {
- $callback();
+ $this->onTransactionIdle( $callback ); // this will trigger immediately
}
}
/**
- * Actually run the "on transaction idle" callbacks.
+ * Actually any "on transaction idle" callbacks.
*
* @since 1.20
*/
@@ -2985,8 +3156,9 @@ abstract class DatabaseBase implements DatabaseType {
$this->mTrxIdleCallbacks = array(); // recursion guard
foreach ( $callbacks as $callback ) {
try {
+ list( $phpCallback ) = $callback;
$this->clearFlag( DBO_TRX ); // make each query its own transaction
- $callback();
+ call_user_func( $phpCallback );
$this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
} catch ( Exception $e ) {}
}
@@ -2998,6 +3170,29 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Actually any "on transaction pre-commit" callbacks.
+ *
+ * @since 1.22
+ */
+ protected function runOnTransactionPreCommitCallbacks() {
+ $e = null; // last exception
+ do { // callbacks may add callbacks :)
+ $callbacks = $this->mTrxPreCommitCallbacks;
+ $this->mTrxPreCommitCallbacks = array(); // recursion guard
+ foreach ( $callbacks as $callback ) {
+ try {
+ list( $phpCallback ) = $callback;
+ call_user_func( $phpCallback );
+ } catch ( Exception $e ) {}
+ }
+ } while ( count( $this->mTrxPreCommitCallbacks ) );
+
+ if ( $e instanceof Exception ) {
+ throw $e; // re-throw any last exception
+ }
+ }
+
+ /**
* Begin a transaction. If a transaction is already in progress, that transaction will be committed before the
* new transaction is started.
*
@@ -3009,7 +3204,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $fname string
*/
- final public function begin( $fname = 'DatabaseBase::begin' ) {
+ final public function begin( $fname = __METHOD__ ) {
global $wgDebugDBTransactions;
if ( $this->mTrxLevel ) { // implicit commit
@@ -3025,11 +3220,16 @@ abstract class DatabaseBase implements DatabaseType {
// log it if $wgDebugDBTransactions is enabled.
if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
wfDebug( "$fname: Automatic transaction with writes in progress" .
- " (from {$this->mTrxFname}), performing implicit commit!\n" );
+ " (from {$this->mTrxFname}), performing implicit commit!\n"
+ );
}
}
+ $this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
+ if ( $this->mTrxDoneWrites ) {
+ Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ }
$this->runOnTransactionIdleCallbacks();
}
@@ -3062,22 +3262,27 @@ abstract class DatabaseBase implements DatabaseType {
* This will silently break any ongoing explicit transaction. Only set the flush flag if you are sure
* that it is safe to ignore these warnings in your context.
*/
- final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) {
+ final public function commit( $fname = __METHOD__, $flush = '' ) {
if ( $flush != 'flush' ) {
if ( !$this->mTrxLevel ) {
wfWarn( "$fname: No transaction to commit, something got out of sync!" );
- } elseif( $this->mTrxAutomatic ) {
+ } elseif ( $this->mTrxAutomatic ) {
wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
}
} else {
if ( !$this->mTrxLevel ) {
return; // nothing to do
- } elseif( !$this->mTrxAutomatic ) {
+ } elseif ( !$this->mTrxAutomatic ) {
wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
}
}
+ $this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
+ if ( $this->mTrxDoneWrites ) {
+ Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ }
+ $this->mTrxDoneWrites = false;
$this->runOnTransactionIdleCallbacks();
}
@@ -3102,12 +3307,17 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $fname string
*/
- final public function rollback( $fname = 'DatabaseBase::rollback' ) {
+ final public function rollback( $fname = __METHOD__ ) {
if ( !$this->mTrxLevel ) {
wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
}
$this->doRollback( $fname );
$this->mTrxIdleCallbacks = array(); // cancel
+ $this->mTrxPreCommitCallbacks = array(); // cancel
+ if ( $this->mTrxDoneWrites ) {
+ Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ }
+ $this->mTrxDoneWrites = false;
}
/**
@@ -3139,8 +3349,8 @@ abstract class DatabaseBase implements DatabaseType {
* @return Boolean: true if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
- $fname = 'DatabaseBase::duplicateTableStructure' )
- {
+ $fname = __METHOD__
+ ) {
throw new MWException(
'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
}
@@ -3152,11 +3362,45 @@ abstract class DatabaseBase implements DatabaseType {
* @param string $fname calling function name
* @throws MWException
*/
- function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
+ function listTables( $prefix = null, $fname = __METHOD__ ) {
throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
}
/**
+ * Reset the views process cache set by listViews()
+ * @since 1.22
+ */
+ final public function clearViewsCache() {
+ $this->allViews = null;
+ }
+
+ /**
+ * Lists all the VIEWs in the database
+ *
+ * For caching purposes the list of all views should be stored in
+ * $this->allViews. The process cache can be cleared with clearViewsCache()
+ *
+ * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
+ * @param string $fname Name of calling function
+ * @throws MWException
+ * @since 1.22
+ */
+ public function listViews( $prefix = null, $fname = __METHOD__ ) {
+ throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
+ }
+
+ /**
+ * Differentiates between a TABLE and a VIEW
+ *
+ * @param $name string: Name of the database-structure to test.
+ * @throws MWException
+ * @since 1.22
+ */
+ public function isView( $name ) {
+ throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
+ }
+
+ /**
* Convert a timestamp in one of the formats accepted by wfTimestamp()
* to the format used for inserting into timestamp fields in this DBMS.
*
@@ -3285,7 +3529,8 @@ abstract class DatabaseBase implements DatabaseType {
* @param $options Array
* @return void
*/
- public function setSessionOptions( array $options ) {}
+ public function setSessionOptions( array $options ) {
+ }
/**
* Read and execute SQL commands from a file.
@@ -3375,7 +3620,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return bool|string
*/
public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = 'DatabaseBase::sourceStream', $inputCallback = false )
+ $fname = __METHOD__, $inputCallback = false )
{
$cmd = '';
@@ -3614,12 +3859,12 @@ abstract class DatabaseBase implements DatabaseType {
* @return bool|ResultWrapper
* @since 1.18
*/
- public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) {
- if( !$this->tableExists( $tableName, $fName ) ) {
+ public function dropTable( $tableName, $fName = __METHOD__ ) {
+ if ( !$this->tableExists( $tableName, $fName ) ) {
return false;
}
$sql = "DROP TABLE " . $this->tableName( $tableName );
- if( $this->cascadingDeletes() ) {
+ if ( $this->cascadingDeletes() ) {
$sql .= " CASCADE";
}
return $this->query( $sql, $fName );
@@ -3691,9 +3936,21 @@ abstract class DatabaseBase implements DatabaseType {
return (string)$this->mConn;
}
+ /**
+ * Run a few simple sanity checks
+ */
public function __destruct() {
- if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
- trigger_error( "Transaction idle callbacks still pending." );
+ if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
+ trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
+ }
+ if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) {
+ $callers = array();
+ foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
+ $callers[] = $callbackInfo[1];
+
+ }
+ $callers = implode( ', ', $callers );
+ trigger_error( "DB transaction callbacks still pending (from $callers)." );
}
}
}