summaryrefslogtreecommitdiff
path: root/includes/db
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2014-12-27 15:41:37 +0100
committerPierre Schmitz <pierre@archlinux.de>2014-12-31 11:43:28 +0100
commitc1f9b1f7b1b77776192048005dcc66dcf3df2bfb (patch)
tree2b38796e738dd74cb42ecd9bfd151803108386bc /includes/db
parentb88ab0086858470dd1f644e64cb4e4f62bb2be9b (diff)
Update to MediaWiki 1.24.1
Diffstat (limited to 'includes/db')
-rw-r--r--includes/db/ChronologyProtector.php13
-rw-r--r--includes/db/CloneDatabase.php65
-rw-r--r--includes/db/Database.php1325
-rw-r--r--includes/db/DatabaseError.php181
-rw-r--r--includes/db/DatabaseMssql.php1502
-rw-r--r--includes/db/DatabaseMysql.php35
-rw-r--r--includes/db/DatabaseMysqlBase.php451
-rw-r--r--includes/db/DatabaseMysqli.php138
-rw-r--r--includes/db/DatabaseOracle.php376
-rw-r--r--includes/db/DatabasePostgres.php426
-rw-r--r--includes/db/DatabaseSqlite.php289
-rw-r--r--includes/db/DatabaseUtility.php71
-rw-r--r--includes/db/IORMRow.php32
-rw-r--r--includes/db/IORMTable.php65
-rw-r--r--includes/db/LBFactory.php152
-rw-r--r--includes/db/LBFactoryMulti.php (renamed from includes/db/LBFactory_Multi.php)175
-rw-r--r--includes/db/LBFactorySingle.php (renamed from includes/db/LBFactory_Single.php)50
-rw-r--r--includes/db/LoadBalancer.php458
-rw-r--r--includes/db/LoadMonitor.php129
-rw-r--r--includes/db/ORMIterator.php1
-rw-r--r--includes/db/ORMResult.php14
-rw-r--r--includes/db/ORMRow.php55
-rw-r--r--includes/db/ORMTable.php123
23 files changed, 3849 insertions, 2277 deletions
diff --git a/includes/db/ChronologyProtector.php b/includes/db/ChronologyProtector.php
index de5e72c3..0c7b612e 100644
--- a/includes/db/ChronologyProtector.php
+++ b/includes/db/ChronologyProtector.php
@@ -26,12 +26,14 @@
* Kind of like Hawking's [[Chronology Protection Agency]].
*/
class ChronologyProtector {
- /** @var Array (DB master name => position) */
+ /** @var array (DB master name => position) */
protected $startupPositions = array();
- /** @var Array (DB master name => position) */
+
+ /** @var array (DB master name => position) */
protected $shutdownPositions = array();
- protected $initialized = false; // bool; whether the session data was loaded
+ /** @var bool Whether the session data was loaded */
+ protected $initialized = false;
/**
* Initialise a LoadBalancer to give it appropriate chronology protection.
@@ -41,7 +43,7 @@ class ChronologyProtector {
* to that position by delaying execution. The delay may timeout and allow stale
* data if no non-lagged slaves are available.
*
- * @param $lb LoadBalancer
+ * @param LoadBalancer $lb
* @return void
*/
public function initLB( LoadBalancer $lb ) {
@@ -67,7 +69,7 @@ class ChronologyProtector {
* Notify the ChronologyProtector that the LoadBalancer is about to shut
* down. Saves replication positions.
*
- * @param $lb LoadBalancer
+ * @param LoadBalancer $lb
* @return void
*/
public function shutdownLB( LoadBalancer $lb ) {
@@ -83,6 +85,7 @@ class ChronologyProtector {
$info = $lb->parentInfo();
if ( !$db || !$db->doneWrites() ) {
wfDebug( __METHOD__ . ": LB {$info['id']}, no writes done\n" );
+
return;
}
$pos = $db->getMasterPos();
diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
index 819925cb..9eb3e2fa 100644
--- a/includes/db/CloneDatabase.php
+++ b/includes/db/CloneDatabase.php
@@ -3,7 +3,7 @@
* Helper class for making a copy of the database, mostly for unit testing.
*
* Copyright © 2010 Chad Horohoe <chad@anyonecanedit.org>
- * http://www.mediawiki.org/
+ * https://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,49 +25,33 @@
*/
class CloneDatabase {
-
- /**
- * Table prefix for cloning
- * @var String
- */
+ /** @var string Table prefix for cloning */
private $newTablePrefix = '';
- /**
- * Current table prefix
- * @var String
- */
+ /** @var string Current table prefix */
private $oldTablePrefix = '';
- /**
- * List of tables to be cloned
- * @var Array
- */
+ /** @var array List of tables to be cloned */
private $tablesToClone = array();
- /**
- * Should we DROP tables containing the new names?
- * @var Bool
- */
+ /** @var bool Should we DROP tables containing the new names? */
private $dropCurrentTables = true;
- /**
- * Whether to use temporary tables or not
- * @var Bool
- */
+ /** @var bool Whether to use temporary tables or not */
private $useTemporaryTables = true;
/**
* Constructor
*
- * @param $db DatabaseBase A database subclass
+ * @param DatabaseBase $db A database subclass
* @param array $tablesToClone An array of tables to clone, unprefixed
* @param string $newTablePrefix Prefix to assign to the tables
* @param string $oldTablePrefix Prefix on current tables, if not $wgDBprefix
- * @param $dropCurrentTables bool
+ * @param bool $dropCurrentTables
*/
public function __construct( DatabaseBase $db, array $tablesToClone,
- $newTablePrefix, $oldTablePrefix = '', $dropCurrentTables = true )
- {
+ $newTablePrefix, $oldTablePrefix = '', $dropCurrentTables = true
+ ) {
$this->db = $db;
$this->tablesToClone = $tablesToClone;
$this->newTablePrefix = $newTablePrefix;
@@ -87,7 +71,13 @@ class CloneDatabase {
* Clone the table structure
*/
public function cloneTableStructure() {
+ global $wgSharedTables, $wgSharedDB;
foreach ( $this->tablesToClone as $tbl ) {
+ if ( $wgSharedDB && in_array( $tbl, $wgSharedTables, true ) ) {
+ // Shared tables don't work properly when cloning due to
+ // how prefixes are handled (bug 65654)
+ throw new MWException( "Cannot clone shared table $tbl." );
+ }
# Clean up from previous aborted run. So that table escaping
# works correctly across DB engines, we need to change the pre-
# fix back and forth so tableName() works right.
@@ -98,14 +88,21 @@ class CloneDatabase {
self::changePrefix( $this->newTablePrefix );
$newTableName = $this->db->tableName( $tbl, 'raw' );
- if ( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) ) ) {
+ if ( $this->dropCurrentTables
+ && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) )
+ ) {
+ if ( $oldTableName === $newTableName ) {
+ // Last ditch check to avoid data loss
+ throw new MWException( "Not dropping new table, as '$newTableName'"
+ . " is name of both the old and the new table." );
+ }
$this->db->dropTable( $tbl, __METHOD__ );
- wfDebug( __METHOD__ . " dropping {$newTableName}\n", true );
+ wfDebug( __METHOD__ . " dropping {$newTableName}\n" );
//Dropping the oldTable because the prefix was changed
}
# Create new table
- wfDebug( __METHOD__ . " duplicating $oldTableName to $newTableName\n", true );
+ wfDebug( __METHOD__ . " duplicating $oldTableName to $newTableName\n" );
$this->db->duplicateTableStructure( $oldTableName, $newTableName, $this->useTemporaryTables );
}
}
@@ -127,7 +124,7 @@ class CloneDatabase {
/**
* Change the table prefix on all open DB connections/
*
- * @param $prefix
+ * @param string $prefix
* @return void
*/
public static function changePrefix( $prefix ) {
@@ -137,8 +134,8 @@ class CloneDatabase {
}
/**
- * @param $lb LoadBalancer
- * @param $prefix
+ * @param LoadBalancer $lb
+ * @param string $prefix
* @return void
*/
public static function changeLBPrefix( $lb, $prefix ) {
@@ -146,8 +143,8 @@ class CloneDatabase {
}
/**
- * @param $db DatabaseBase
- * @param $prefix
+ * @param DatabaseBase $db
+ * @param string $prefix
* @return void
*/
public static function changeDBPrefix( $db, $prefix ) {
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 10645608..9b783a99 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -1,4 +1,5 @@
<?php
+
/**
* @defgroup Database Database
*
@@ -42,10 +43,10 @@ interface DatabaseType {
/**
* 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
+ * @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
*/
@@ -57,18 +58,18 @@ interface DatabaseType {
* member variables.
* If no more rows are available, false is returned.
*
- * @param $res ResultWrapper|object as returned from DatabaseBase::query(), etc.
- * @return object|bool
+ * @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'].
+ * form. Fields are retrieved with $row['fieldname'].
* If no more rows are available, false is returned.
*
- * @param $res ResultWrapper result object as returned from DatabaseBase::query(), etc.
+ * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc.
* @return array|bool
* @throws DBUnexpectedError Thrown if the database returns an error
*/
@@ -77,7 +78,7 @@ interface DatabaseType {
/**
* Get the number of rows in a result object
*
- * @param $res Mixed: A SQL result
+ * @param mixed $res A SQL result
* @return int
*/
function numRows( $res );
@@ -86,7 +87,7 @@ interface DatabaseType {
* Get the number of fields in a result object
* @see http://www.php.net/mysql_num_fields
*
- * @param $res Mixed: A SQL result
+ * @param mixed $res A SQL result
* @return int
*/
function numFields( $res );
@@ -95,8 +96,8 @@ interface DatabaseType {
* Get a field name in a result object
* @see http://www.php.net/mysql_field_name
*
- * @param $res Mixed: A SQL result
- * @param $n Integer
+ * @param mixed $res A SQL result
+ * @param int $n
* @return string
*/
function fieldName( $res, $n );
@@ -119,8 +120,8 @@ interface DatabaseType {
* Change the position of the cursor in a result object
* @see http://www.php.net/mysql_data_seek
*
- * @param $res Mixed: A SQL result
- * @param $row Mixed: Either MySQL row or ResultWrapper
+ * @param mixed $res A SQL result
+ * @param int $row
*/
function dataSeek( $res, $row );
@@ -144,8 +145,8 @@ interface DatabaseType {
* mysql_fetch_field() wrapper
* Returns false if the field doesn't exist
*
- * @param string $table table name
- * @param string $field field name
+ * @param string $table Table name
+ * @param string $field Field name
*
* @return Field
*/
@@ -156,7 +157,7 @@ interface DatabaseType {
* @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
+ * @return mixed Database-specific index description class or false if the index does not exist
*/
function indexInfo( $table, $index, $fname = __METHOD__ );
@@ -171,18 +172,18 @@ interface DatabaseType {
/**
* Wrapper for addslashes()
*
- * @param string $s to be slashed.
- * @return string: slashed string.
+ * @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]";
+ * 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
+ * @return string Wikitext of a link to the server software's web site
*/
function getSoftwareLink();
@@ -190,16 +191,16 @@ interface DatabaseType {
* A string describing the current software version, like from
* mysql_get_server_info().
*
- * @return string: Version information from the database server.
+ * @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.
+ * 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
+ * @return string Version information from the database server
*/
function getServerInfo();
}
@@ -208,7 +209,8 @@ interface DatabaseType {
* Interface for classes that implement or wrap DatabaseBase
* @ingroup Database
*/
-interface IDatabase {}
+interface IDatabase {
+}
/**
* Database abstraction object
@@ -217,8 +219,10 @@ interface IDatabase {}
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;
@@ -232,6 +236,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
protected $mServer, $mUser, $mPassword, $mDBname;
+ /** @var resource Database connection */
protected $mConn = null;
protected $mOpened = false;
@@ -241,12 +246,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
protected $mTrxPreCommitCallbacks = array();
protected $mTablePrefix;
+ protected $mSchema;
protected $mFlags;
protected $mForeign;
- protected $mTrxLevel = 0;
protected $mErrorCount = 0;
protected $mLBInfo = array();
- protected $mFakeSlaveLag = null, $mFakeMaster = false;
protected $mDefaultBigSelects = null;
protected $mSchemaVars = false;
@@ -257,10 +261,25 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
protected $delimiter = ';';
/**
+ * Either 1 if a transaction is active or 0 otherwise.
+ * The other Trx fields may not be meaningfull if this is 0.
+ *
+ * @var int
+ */
+ protected $mTrxLevel = 0;
+
+ /**
+ * Either a short hexidecimal string if a transaction is active or ""
+ *
+ * @var string
+ */
+ protected $mTrxShortId = '';
+
+ /**
* Remembers the function name given for starting the most recent transaction via begin().
* Used to provide additional context for error reporting.
*
- * @var String
+ * @var string
* @see DatabaseBase::mTrxLevel
*/
private $mTrxFname = null;
@@ -268,7 +287,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Record if possible write queries were done in the last transaction started
*
- * @var Bool
+ * @var bool
* @see DatabaseBase::mTrxLevel
*/
private $mTrxDoneWrites = false;
@@ -276,20 +295,34 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Record if the current transaction was started implicitly due to DBO_TRX being set.
*
- * @var Bool
+ * @var bool
* @see DatabaseBase::mTrxLevel
*/
private $mTrxAutomatic = false;
/**
+ * Array of levels of atomicity within transactions
+ *
+ * @var SplStack
+ */
+ private $mTrxAtomicLevels;
+
+ /**
+ * Record if the current transaction was started implicitly by DatabaseBase::startAtomic
+ *
+ * @var bool
+ */
+ private $mTrxAutomaticAtomic = false;
+
+ /**
* @since 1.21
- * @var file handle for upgrade
+ * @var resource File handle for upgrade
*/
protected $fileHandle = null;
/**
* @since 1.22
- * @var Process cache of VIEWs names in the database
+ * @var string[] Process cache of VIEWs names in the database
*/
protected $allViews = null;
@@ -300,17 +333,17 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* A string describing the current software version, and possibly
- * other details in a user-friendly way. Will be listed on Special:Version, etc.
+ * 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
+ * @return string Version information from the database server
*/
public function getServerInfo() {
return $this->getServerVersion();
}
/**
- * @return string: command delimiter used by this database engine
+ * @return string Command delimiter used by this database engine
*/
public function getDelimiter() {
return $this->delimiter;
@@ -318,12 +351,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Boolean, controls output of large amounts of debug information.
- * @param $debug bool|null
+ * @param bool|null $debug
* - true to enable debugging
* - false to disable debugging
* - omitted or null to do nothing
*
- * @return bool|null previous value of the flag
+ * @return bool|null Previous value of the flag
*/
public function debug( $debug = null ) {
return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
@@ -347,8 +380,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* split up queries into batches using a LIMIT clause than to switch off
* buffering.
*
- * @param $buffer null|bool
- *
+ * @param null|bool $buffer
* @return null|bool The previous value of the flag
*/
public function bufferResults( $buffer = null ) {
@@ -368,8 +400,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* Do not use this function outside of the Database classes.
*
- * @param $ignoreErrors bool|null
- *
+ * @param null|bool $ignoreErrors
* @return bool The previous value of the flag.
*/
public function ignoreErrors( $ignoreErrors = null ) {
@@ -377,16 +408,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Gets or sets the current transaction level.
+ * Gets the current transaction level.
*
* Historically, transactions were allowed to be "nested". This is no
* longer supported, so this function really only returns a boolean.
*
- * @param int $level An integer (0 or 1), or omitted to leave it unchanged.
* @return int The previous value
*/
- public function trxLevel( $level = null ) {
- return wfSetVar( $this->mTrxLevel, $level );
+ public function trxLevel() {
+ return $this->mTrxLevel;
}
/**
@@ -408,9 +438,18 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Get/set the db schema.
+ * @param string $schema The database schema to set, or omitted to leave it unchanged.
+ * @return string The previous db schema.
+ */
+ public function dbSchema( $schema = null ) {
+ return wfSetVar( $this->mSchema, $schema );
+ }
+
+ /**
* Set the filehandle to copy write statements to.
*
- * @param $fh filehandle
+ * @param resource $fh File handle
*/
public function setFileHandle( $fh ) {
$this->fileHandle = $fh;
@@ -423,7 +462,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param string $name The entry of the info array to get, or null to get the
* whole array
*
- * @return LoadBalancer|null
+ * @return array|mixed|null
*/
public function getLBInfo( $name = null ) {
if ( is_null( $name ) ) {
@@ -442,8 +481,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* the LB info array is set to that parameter. If it is called with two
* parameters, the member with the given name is set to the given value.
*
- * @param $name
- * @param $value
+ * @param string $name
+ * @param array $value
*/
public function setLBInfo( $name, $value = null ) {
if ( is_null( $value ) ) {
@@ -456,19 +495,19 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Set lag time in seconds for a fake slave
*
- * @param $lag int
+ * @param mixed $lag Valid values for this parameter are determined by the
+ * subclass, but should be a PHP scalar or array that would be sensible
+ * as part of $wgLBFactoryConf.
*/
public function setFakeSlaveLag( $lag ) {
- $this->mFakeSlaveLag = $lag;
}
/**
* Make this connection a fake master
*
- * @param $enabled bool
+ * @param bool $enabled
*/
public function setFakeMaster( $enabled = true ) {
- $this->mFakeMaster = $enabled;
}
/**
@@ -548,7 +587,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Return the last query that went through DatabaseBase::query()
- * @return String
+ * @return string
*/
public function lastQuery() {
return $this->mLastQuery;
@@ -561,7 +600,18 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @return bool
*/
public function doneWrites() {
- return $this->mDoneWrites;
+ return (bool)$this->mDoneWrites;
+ }
+
+ /**
+ * Returns the last time the connection may have been used for write queries.
+ * Should return a timestamp if unsure.
+ *
+ * @return int|float UNIX timestamp or false
+ * @since 1.24
+ */
+ public function lastDoneWrites() {
+ return $this->mDoneWrites ?: false;
}
/**
@@ -578,7 +628,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Is a connection to the database open?
- * @return Boolean
+ * @return bool
*/
public function isOpen() {
return $this->mOpened;
@@ -587,7 +637,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Set a flag for this connection
*
- * @param $flag Integer: DBO_* constants from Defines.php:
+ * @param int $flag DBO_* constants from Defines.php:
* - DBO_DEBUG: output some debug info (same as debug())
* - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
* - DBO_TRX: automatically start transactions
@@ -598,15 +648,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
public function setFlag( $flag ) {
global $wgDebugDBTransactions;
$this->mFlags |= $flag;
- if ( ( $flag & DBO_TRX ) & $wgDebugDBTransactions ) {
- wfDebug( "Implicit transactions are now disabled.\n" );
+ if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
+ wfDebug( "Implicit transactions are now enabled.\n" );
}
}
/**
* Clear a flag for this connection
*
- * @param $flag: same as setFlag()'s $flag param
+ * @param int $flag DBO_* constants from Defines.php:
+ * - DBO_DEBUG: output some debug info (same as debug())
+ * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+ * - DBO_TRX: automatically start transactions
+ * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
+ * and removes it in command line mode
+ * - DBO_PERSISTENT: use persistant database connection
*/
public function clearFlag( $flag ) {
global $wgDebugDBTransactions;
@@ -619,8 +675,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Returns a boolean whether the flag $flag is set for this connection
*
- * @param $flag: same as setFlag()'s $flag param
- * @return Boolean
+ * @param int $flag DBO_* constants from Defines.php:
+ * - DBO_DEBUG: output some debug info (same as debug())
+ * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+ * - DBO_TRX: automatically start transactions
+ * - DBO_PERSISTENT: use persistant database connection
+ * @return bool
*/
public function getFlag( $flag ) {
return !!( $this->mFlags & $flag );
@@ -629,8 +689,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* General read-only accessor
*
- * @param $name string
- *
+ * @param string $name
* @return string
*/
public function getProperty( $name ) {
@@ -649,19 +708,42 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Return a path to the DBMS-specific schema file, otherwise default to tables.sql
+ * Return a path to the DBMS-specific SQL file if it exists,
+ * otherwise default SQL file
*
+ * @param string $filename
* @return string
*/
- public function getSchemaPath() {
+ private function getSqlFilePath( $filename ) {
global $IP;
- if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
- return "$IP/maintenance/" . $this->getType() . "/tables.sql";
+ $dbmsSpecificFilePath = "$IP/maintenance/" . $this->getType() . "/$filename";
+ if ( file_exists( $dbmsSpecificFilePath ) ) {
+ return $dbmsSpecificFilePath;
} else {
- return "$IP/maintenance/tables.sql";
+ 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' );
+ }
+
# ------------------------------------------------------------------------------
# Other functions
# ------------------------------------------------------------------------------
@@ -673,28 +755,39 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* 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
+ * @param array $params Parameters passed from DatabaseBase::factory()
*/
- function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $flags = 0, $tablePrefix = 'get from global', $foreign = false
- ) {
- global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions;
+ function __construct( $params = null ) {
+ global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions;
- $this->mFlags = $flags;
+ $this->mTrxAtomicLevels = new SplStack;
+
+ if ( is_array( $params ) ) { // MW 1.22
+ $server = $params['host'];
+ $user = $params['user'];
+ $password = $params['password'];
+ $dbName = $params['dbname'];
+ $flags = $params['flags'];
+ $tablePrefix = $params['tablePrefix'];
+ $schema = $params['schema'];
+ $foreign = $params['foreign'];
+ } else { // legacy calling pattern
+ wfDeprecated( __METHOD__ . " method called without parameter array.", "1.23" );
+ $args = func_get_args();
+ $server = isset( $args[0] ) ? $args[0] : false;
+ $user = isset( $args[1] ) ? $args[1] : false;
+ $password = isset( $args[2] ) ? $args[2] : false;
+ $dbName = isset( $args[3] ) ? $args[3] : false;
+ $flags = isset( $args[4] ) ? $args[4] : 0;
+ $tablePrefix = isset( $args[5] ) ? $args[5] : 'get from global';
+ $schema = 'get from global';
+ $foreign = isset( $args[6] ) ? $args[6] : false;
+ }
+ $this->mFlags = $flags;
if ( $this->mFlags & DBO_DEFAULT ) {
if ( $wgCommandLineMode ) {
$this->mFlags &= ~DBO_TRX;
@@ -716,6 +809,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->mTablePrefix = $tablePrefix;
}
+ /** Get the database schema*/
+ if ( $schema == 'get from global' ) {
+ $this->mSchema = $wgDBmwschema;
+ } else {
+ $this->mSchema = $schema;
+ }
+
$this->mForeign = $foreign;
if ( $user ) {
@@ -729,13 +829,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* not restored on unserialize.
*/
public function __sleep() {
- throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
+ throw new MWException( '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 ) );
+ * $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
@@ -744,22 +845,23 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* database. Example uses in core:
* @see LoadBalancer::reallyOpenConnection()
* @see ForeignDBRepo::getMasterDB()
- * @see WebInstaller_DBConnect::execute()
+ * @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, driver
- * @return DatabaseBase subclass or null
+ * 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 = array() ) {
$canonicalDBTypes = array(
- 'mysql' => array( 'mysqli', 'mysql' ),
+ 'mysql' => array( 'mysqli', 'mysql' ),
'postgres' => array(),
- 'sqlite' => array(),
- 'oracle' => array(),
- 'mssql' => array(),
+ 'sqlite' => array(),
+ 'oracle' => array(),
+ 'mssql' => array(),
);
$driver = false;
@@ -789,17 +891,32 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
" 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 = array(
+ 'mysql' => null,
+ 'postgres' => null,
+ 'sqlite' => null,
+ 'oracle' => null,
+ 'mssql' => 'get from global',
+ );
+
$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['foreign'] ) ? $p['foreign'] : false
+ $params = array(
+ 'host' => isset( $p['host'] ) ? $p['host'] : false,
+ 'user' => isset( $p['user'] ) ? $p['user'] : false,
+ 'password' => isset( $p['password'] ) ? $p['password'] : false,
+ 'dbname' => isset( $p['dbname'] ) ? $p['dbname'] : false,
+ 'flags' => isset( $p['flags'] ) ? $p['flags'] : 0,
+ 'tablePrefix' => isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global',
+ 'schema' => isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType],
+ 'foreign' => isset( $p['foreign'] ) ? $p['foreign'] : false
);
+
+ return new $class( $params );
} else {
return null;
}
@@ -822,6 +939,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $this->mPHPError ) {
$error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
$error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
+
return $error;
} else {
return false;
@@ -829,9 +947,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * @param $errno
- * @param $errstr
- * @access private
+ * @param int $errno
+ * @param string $errstr
*/
public function connectionErrorHandler( $errno, $errstr ) {
$this->mPHPError = $errstr;
@@ -842,13 +959,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* if it is open : commits any open transactions
*
* @throws MWException
- * @return Bool operation success. true if already closed.
+ * @return bool Operation success. true if already closed.
*/
public function close() {
if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
throw new MWException( "Transaction idle callbacks still pending." );
}
- $this->mOpened = false;
if ( $this->mConn ) {
if ( $this->trxLevel() ) {
if ( !$this->mTrxAutomatic ) {
@@ -859,23 +975,25 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->commit( __METHOD__, 'flush' );
}
- $ret = $this->closeConnection();
+ $closed = $this->closeConnection();
$this->mConn = false;
- return $ret;
} else {
- return true;
+ $closed = true;
}
+ $this->mOpened = false;
+
+ return $closed;
}
/**
* Closes underlying database connection
* @since 1.20
- * @return bool: Whether connection was closed successfully
+ * @return bool Whether connection was closed successfully
*/
abstract protected function closeConnection();
/**
- * @param string $error fallback error message, used if none is given by DB
+ * @param string $error Fallback error message, used if none is given by DB
* @throws DBConnectionError
*/
function reportConnectionError( $error = 'Unknown error' ) {
@@ -891,8 +1009,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* The DBMS-dependent part of query()
*
- * @param $sql String: SQL query.
- * @return ResultWrapper Result object to feed to fetchObject, fetchRow, ...; or false on failure
+ * @param string $sql SQL query.
+ * @return ResultWrapper|bool Result object to feed to fetchObject,
+ * fetchRow, ...; or false on failure
*/
abstract protected function doQuery( $sql );
@@ -900,8 +1019,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Determine whether a query writes to the DB.
* Should return true if unsure.
*
- * @param $sql string
- *
+ * @param string $sql
* @return bool
*/
public function isWriteQuery( $sql ) {
@@ -921,23 +1039,23 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* However, the query wrappers themselves should call this function.
*
- * @param $sql String: SQL query
- * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST
+ * @param string $sql SQL query
+ * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST
* comment (you can use __METHOD__ or add some extra info)
- * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
+ * @param bool $tempIgnore Whether to avoid throwing an exception on errors...
* maybe best to catch the exception instead?
* @throws MWException
- * @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object
+ * @return bool|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 = __METHOD__, $tempIgnore = false ) {
- global $wgUser, $wgDebugDBTransactions;
+ global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength;
$this->mLastQuery = $sql;
- if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
+ if ( $this->isWriteQuery( $sql ) ) {
# Set a flag indicating that writes have been done
- wfDebug( __METHOD__ . ": Writes done: $sql\n" );
- $this->mDoneWrites = true;
+ wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" );
+ $this->mDoneWrites = microtime( true );
}
# Add a comment for easy SHOW PROCESSLIST interpretation
@@ -957,8 +1075,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# If DBO_TRX is set, start a transaction
if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' )
- {
+ $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK'
+ ) {
# Avoid establishing transactions for SHOW and SET statements too -
# that would delay transaction initializations to once connection
# is really used by application
@@ -975,10 +1093,14 @@ abstract class DatabaseBase implements IDatabase, 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 );
+ Profiler::instance()->transactionWritingIn(
+ $this->mServer, $this->mDBname, $this->mTrxShortId );
}
+ $queryProf = '';
+ $totalProf = '';
$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.
@@ -989,6 +1111,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
$totalProf = 'DatabaseBase::query';
}
+ # Include query transaction state
+ $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
+
+ $trx = $this->mTrxLevel ? 'TRX=yes' : 'TRX=no';
wfProfileIn( $totalProf );
wfProfileIn( $queryProf );
}
@@ -997,7 +1123,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
static $cnt = 0;
$cnt++;
- $sqlx = substr( $commentedSql, 0, 500 );
+ $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
+ : $commentedSql;
$sqlx = strtr( $sqlx, "\t\n", ' ' );
$master = $isMaster ? 'master' : 'slave';
@@ -1006,6 +1133,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$queryId = MWDebug::query( $sql, $fname, $isMaster );
+ # Avoid fatals if close() was called
+ if ( !$this->isOpen() ) {
+ throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ }
+
# Do the query and handle errors
$ret = $this->doQuery( $commentedSql );
@@ -1014,22 +1146,33 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# Try reconnecting if the connection was lost
if ( false === $ret && $this->wasErrorReissuable() ) {
# Transaction is gone, like it or not
+ $hadTrx = $this->mTrxLevel; // possible lost transaction
$this->mTrxLevel = 0;
- $this->mTrxIdleCallbacks = array(); // cancel
- $this->mTrxPreCommitCallbacks = array(); // cancel
+ $this->mTrxIdleCallbacks = array(); // bug 65263
+ $this->mTrxPreCommitCallbacks = array(); // bug 65263
wfDebug( "Connection lost, reconnecting...\n" );
-
+ # Stash the last error values since ping() might clear them
+ $lastError = $this->lastError();
+ $lastErrno = $this->lastErrno();
if ( $this->ping() ) {
+ global $wgRequestTime;
wfDebug( "Reconnected\n" );
- $sqlx = substr( $commentedSql, 0, 500 );
+ $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
+ : $commentedSql;
$sqlx = strtr( $sqlx, "\t\n", ' ' );
- global $wgRequestTime;
$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\n" );
+ wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" );
+ }
+ 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( $lastError, $lastErrno, $sql, $fname, $tempIgnore );
+ } else {
+ # Should be safe to silently retry (no trx and thus no callbacks)
+ $ret = $this->doQuery( $commentedSql );
}
- $ret = $this->doQuery( $commentedSql );
} else {
wfDebug( "Failed\n" );
}
@@ -1051,11 +1194,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Report a query error. Log the error, and if neither the object ignore
* flag nor the $tempIgnore flag is set, throw a DBQueryError.
*
- * @param $error String
- * @param $errno Integer
- * @param $sql String
- * @param $fname String
- * @param $tempIgnore Boolean
+ * @param string $error
+ * @param int $errno
+ * @param string $sql
+ * @param string $fname
+ * @param bool $tempIgnore
* @throws DBQueryError
*/
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
@@ -1067,8 +1210,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
wfDebug( "SQL ERROR (ignored): $error\n" );
$this->ignoreErrors( $ignore );
} else {
- $sql1line = str_replace( "\n", "\\n", $sql );
- wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" );
+ $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
+ wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" );
wfDebug( "SQL ERROR: " . $error . "\n" );
throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
@@ -1083,21 +1226,22 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* & = filename; reads the file and inserts as a blob
* (we don't use this though...)
*
- * @param $sql string
- * @param $func string
+ * @param string $sql
+ * @param string $func
*
* @return array
*/
protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
/* MySQL doesn't support prepared statements (yet), so just
- pack up the query for reference. We'll manually replace
- the bits later. */
+ * pack up the query for reference. We'll manually replace
+ * the bits later.
+ */
return array( 'query' => $sql, 'func' => $func );
}
/**
* Free a prepared query, generated by prepare().
- * @param $prepared
+ * @param string $prepared
*/
protected function freePrepared( $prepared ) {
/* No-op by default */
@@ -1105,8 +1249,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Execute a prepared query with the various arguments
- * @param string $prepared the prepared sql
- * @param $args Mixed: Either an array here, or put scalars as varargs
+ * @param string $prepared The prepared sql
+ * @param mixed $args Either an array here, or put scalars as varargs
*
* @return ResultWrapper
*/
@@ -1125,9 +1269,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* For faking prepared SQL statements on DBs that don't support it directly.
*
- * @param string $preparedQuery a 'preparable' SQL statement
- * @param array $args of arguments to fill it with
- * @return string executable SQL
+ * @param string $preparedQuery A 'preparable' SQL statement
+ * @param array $args Array of Arguments to fill it with
+ * @return string Executable SQL
*/
public function fillPrepared( $preparedQuery, $args ) {
reset( $args );
@@ -1142,9 +1286,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* The arguments should be in $this->preparedArgs and must not be touched
* while we're doing this.
*
- * @param $matches Array
+ * @param array $matches
* @throws DBUnexpectedError
- * @return String
+ * @return string
*/
protected function fillPreparedArg( $matches ) {
switch ( $matches[1] ) {
@@ -1165,9 +1309,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
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.' );
+ throw new DBUnexpectedError(
+ $this,
+ '& mode is not implemented. If it\'s really needed, uncomment the line above.'
+ );
default:
- throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
+ throw new DBUnexpectedError(
+ $this,
+ 'Received invalid match. This should never happen!'
+ );
}
}
@@ -1176,7 +1326,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* necessary to call this, just use unset() or let the variable holding
* the result object go out of scope.
*
- * @param $res Mixed: A SQL result
+ * @param mixed $res A SQL result
*/
public function freeResult( $res ) {
}
@@ -1226,9 +1376,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Returns an optional USE INDEX clause to go after the table, and a
* string to go at the end of the query.
*
- * @param array $options associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return Array
+ * @param array $options Associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
+ * @return array
* @see DatabaseBase::select()
*/
public function makeSelectOptions( $options ) {
@@ -1310,7 +1460,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Returns an optional GROUP BY with an optional HAVING
*
- * @param array $options associative array of options
+ * @param array $options Associative array of options
* @return string
* @see DatabaseBase::select()
* @since 1.21
@@ -1329,13 +1479,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
: $options['HAVING'];
$sql .= ' HAVING ' . $having;
}
+
return $sql;
}
/**
* Returns an optional ORDER BY
*
- * @param array $options associative array of options
+ * @param array $options Associative array of options
* @return string
* @see DatabaseBase::select()
* @since 1.21
@@ -1345,8 +1496,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$ob = is_array( $options['ORDER BY'] )
? implode( ',', $options['ORDER BY'] )
: $options['ORDER BY'];
+
return ' ORDER BY ' . $ob;
}
+
return '';
}
@@ -1359,9 +1512,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param string|array $conds Conditions
* @param string $fname Caller function name
* @param array $options Query options
- * @param $join_conds Array Join conditions
+ * @param array $join_conds Join conditions
*
- * @param $table string|array
+ *
+ * @param string|array $table
*
* May be either an array of table names, or a single string holding a table
* name. If an array is given, table aliases can be specified, for example:
@@ -1376,7 +1530,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* added, and various other table name mappings to be performed.
*
*
- * @param $vars string|array
+ * @param string|array $vars
*
* May be either a field name or an array of field names. The field names
* can be complete fragments of SQL, for direct inclusion into the SELECT
@@ -1390,7 +1544,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* DBMS-independent.
*
*
- * @param $conds string|array
+ * @param string|array $conds
*
* May be either a string containing a single condition, or an array of
* conditions. If an array is given, the conditions constructed from each
@@ -1415,7 +1569,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* - DatabaseBase::conditional()
*
*
- * @param $options string|array
+ * @param string|array $options
*
* Optional: Array of query options. Boolean options are specified by
* including them in the array as a string value with a numeric key, for
@@ -1471,7 +1625,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* - SQL_NO_CACHE
*
*
- * @param $join_conds string|array
+ * @param string|array $join_conds
*
* Optional associative array of table-specific join conditions. In the
* most common case, this is unnecessary, since the join condition can be
@@ -1484,7 +1638,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* array( 'page' => array( 'LEFT JOIN', 'page_latest=rev_id' ) )
*
- * @return ResultWrapper. If the query returned no rows, a ResultWrapper
+ * @return ResultWrapper|bool If the query returned no rows, a ResultWrapper
* with no rows in it will be returned. If there was a query error, a
* DBQueryError exception will be thrown, except if the "ignore errors"
* option was set, in which case false will be returned.
@@ -1507,14 +1661,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param string|array $conds Conditions
* @param string $fname Caller function name
* @param string|array $options Query options
- * @param $join_conds string|array Join conditions
+ * @param string|array $join_conds Join conditions
*
* @return string SQL query string.
* @see DatabaseBase::select()
*/
public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
- $options = array(), $join_conds = array() )
- {
+ $options = array(), $join_conds = array()
+ ) {
if ( is_array( $vars ) ) {
$vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
}
@@ -1573,13 +1727,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param array $conds Conditions
* @param string $fname Caller function name
* @param string|array $options Query options
- * @param $join_conds array|string Join conditions
+ * @param array|string $join_conds Join conditions
*
- * @return object|bool
+ * @return stdClass|bool
*/
public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
- $options = array(), $join_conds = array() )
- {
+ $options = array(), $join_conds = array()
+ ) {
$options = (array)$options;
$options['LIMIT'] = 1;
$res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -1598,7 +1752,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Estimate rows in dataset.
+ * Estimate the number of rows in dataset
*
* MySQL allows you to estimate the number of rows that would be returned
* by a SELECT query, using EXPLAIN SELECT. The estimate is provided using
@@ -1610,16 +1764,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* Takes the same arguments as DatabaseBase::select().
*
- * @param string $table table name
- * @param array|string $vars : unused
- * @param array|string $conds : filters on the table
- * @param string $fname function name for profiling
- * @param array $options options for select
- * @return Integer: row count
- */
- public function estimateRowCount( $table, $vars = '*', $conds = '',
- $fname = __METHOD__, $options = array() )
- {
+ * @param string $table Table name
+ * @param string $vars Unused
+ * @param array|string $conds Filters on the table
+ * @param string $fname Function name for profiling
+ * @param array $options Options for select
+ * @return int Row count
+ */
+ public function estimateRowCount(
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array()
+ ) {
$rows = 0;
$res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
@@ -1632,6 +1786,36 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Get the number of rows in dataset
+ *
+ * This is useful when trying to do COUNT(*) but with a LIMIT for performance.
+ *
+ * Takes the same arguments as DatabaseBase::select().
+ *
+ * @param string $table Table name
+ * @param string $vars Unused
+ * @param array|string $conds Filters on the table
+ * @param string $fname Function name for profiling
+ * @param array $options Options for select
+ * @return int Row count
+ * @since 1.24
+ */
+ public function selectRowCount(
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array()
+ ) {
+ $rows = 0;
+ $sql = $this->selectSQLText( $table, '1', $conds, $fname, $options );
+ $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count" );
+
+ if ( $res ) {
+ $row = $this->fetchRow( $res );
+ $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
+ }
+
+ return $rows;
+ }
+
+ /**
* Removes most variables from an SQL query and replaces them with X or N for numbers.
* It's only slightly flawed. Don't use for anything important.
*
@@ -1653,9 +1837,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# All newlines, tabs, etc replaced by single space
$sql = preg_replace( '/\s+/', ' ', $sql );
- # All numbers => N
+ # All numbers => N,
+ # except the ones surrounded by characters, e.g. l10n
$sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
- $sql = preg_replace( '/-?\d+/s', 'N', $sql );
+ $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
return $sql;
}
@@ -1663,10 +1848,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Determines whether a field exists in a table
*
- * @param string $table table name
- * @param string $field filed to check on that table
- * @param string $fname calling function name (optional)
- * @return Boolean: whether $table has filed $field
+ * @param string $table Table name
+ * @param string $field Filed to check on that table
+ * @param string $fname Calling function name (optional)
+ * @return bool Whether $table has filed $field
*/
public function fieldExists( $table, $field, $fname = __METHOD__ ) {
$info = $this->fieldInfo( $table, $field );
@@ -1679,10 +1864,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Usually throws a DBQueryError on failure
* If errors are explicitly ignored, returns NULL on failure
*
- * @param $table
- * @param $index
- * @param $fname string
- *
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool|null
*/
public function indexExists( $table, $index, $fname = __METHOD__ ) {
@@ -1701,9 +1885,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Query whether a given table exists
*
- * @param $table string
- * @param $fname string
- *
+ * @param string $table
+ * @param string $fname
* @return bool
*/
public function tableExists( $table, $fname = __METHOD__ ) {
@@ -1716,24 +1899,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * mysql_field_type() wrapper
- * @param $res
- * @param $index
- * @return string
- */
- public function fieldType( $res, $index ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return mysql_field_type( $res, $index );
- }
-
- /**
* Determines if a given index is unique
*
- * @param $table string
- * @param $index string
+ * @param string $table
+ * @param string $index
*
* @return bool
*/
@@ -1750,7 +1919,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Helper for DatabaseBase::insert().
*
- * @param $options array
+ * @param array $options
* @return string
*/
protected function makeInsertOptions( $options ) {
@@ -1782,11 +1951,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* possible to determine how many rows were successfully inserted using
* DatabaseBase::affectedRows().
*
- * @param $table String Table name. This will be passed through
- * DatabaseBase::tableName().
- * @param $a Array of rows to insert
- * @param $fname String Calling function name (use __METHOD__) for logs/profiling
- * @param array $options of options
+ * @param string $table Table name. This will be passed through
+ * DatabaseBase::tableName().
+ * @param array $a Array of rows to insert
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options Array of options
*
* @return bool
*/
@@ -1843,12 +2012,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Make UPDATE options for the DatabaseBase::update function
+ * Make UPDATE options array for DatabaseBase::makeUpdateOptions
*
- * @param array $options The options passed to DatabaseBase::update
- * @return string
+ * @param array $options
+ * @return array
*/
- protected function makeUpdateOptions( $options ) {
+ protected function makeUpdateOptionsArray( $options ) {
if ( !is_array( $options ) ) {
$options = array( $options );
}
@@ -1863,31 +2032,38 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$opts[] = 'IGNORE';
}
+ return $opts;
+ }
+
+ /**
+ * Make UPDATE options for the DatabaseBase::update function
+ *
+ * @param array $options The options passed to DatabaseBase::update
+ * @return string
+ */
+ protected function makeUpdateOptions( $options ) {
+ $opts = $this->makeUpdateOptionsArray( $options );
+
return implode( ' ', $opts );
}
/**
* UPDATE wrapper. Takes a condition array and a SET array.
*
- * @param $table String name of the table to UPDATE. This will be passed through
- * DatabaseBase::tableName().
- *
- * @param array $values 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 $conds Array: An array of conditions (WHERE). See
- * DatabaseBase::select() for the details of the format of
- * condition arrays. Use '*' to update all rows.
- *
- * @param $fname String: The function name of the caller (from __METHOD__),
- * for logging and profiling.
- *
+ * @param string $table Name of the table to UPDATE. This will be passed through
+ * DatabaseBase::tableName().
+ * @param array $values 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 array $conds An array of conditions (WHERE). See
+ * DatabaseBase::select() for the details of the format of condition
+ * arrays. Use '*' to update all rows.
+ * @param string $fname The function name of the caller (from __METHOD__),
+ * for logging and profiling.
* @param array $options An array of UPDATE options, can be:
- * - IGNORE: Ignore unique key conflicts
- * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
- * @return Boolean
+ * - IGNORE: Ignore unique key conflicts
+ * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
+ * @return bool
*/
function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
$table = $this->tableName( $table );
@@ -1903,15 +2079,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Makes an encoded list of strings from an array
- * @param array $a containing the data
- * @param int $mode Constant
- * - LIST_COMMA: comma separated, no field names
- * - LIST_AND: ANDed WHERE clause (without the WHERE). See
- * the documentation for $conds in DatabaseBase::select().
- * - LIST_OR: ORed WHERE clause (without the WHERE)
- * - LIST_SET: comma separated with field names, like a SET clause
- * - LIST_NAMES: comma separated field names
*
+ * @param array $a Containing the data
+ * @param int $mode Constant
+ * - LIST_COMMA: Comma separated, no field names
+ * - LIST_AND: ANDed WHERE clause (without the WHERE). See the
+ * documentation for $conds in DatabaseBase::select().
+ * - LIST_OR: ORed WHERE clause (without the WHERE)
+ * - LIST_SET: Comma separated with field names, like a SET clause
+ * - LIST_NAMES: Comma separated field names
* @throws MWException|DBUnexpectedError
* @return string
*/
@@ -1974,11 +2150,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Build a partial where clause from a 2-d array such as used for LinkBatch.
* The keys on each level may be either integers or strings.
*
- * @param array $data organized as 2-d
- * array(baseKeyVal => array(subKeyVal => [ignored], ...), ...)
- * @param string $baseKey field name to match the base-level keys to (eg 'pl_namespace')
- * @param string $subKey field name to match the sub-level keys to (eg 'pl_title')
- * @return Mixed: string SQL fragment, or false if no items in array.
+ * @param array $data Organized as 2-d
+ * array(baseKeyVal => array(subKeyVal => [ignored], ...), ...)
+ * @param string $baseKey Field name to match the base-level keys to (eg 'pl_namespace')
+ * @param string $subKey Field name to match the sub-level keys to (eg 'pl_title')
+ * @return string|bool SQL fragment, or false if no items in array
*/
public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
$conds = array();
@@ -2002,8 +2178,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Return aggregated value alias
*
- * @param $valuedata
- * @param $valuename string
+ * @param array $valuedata
+ * @param string $valuename
*
* @return string
*/
@@ -2012,7 +2188,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * @param $field
+ * @param string $field
* @return string
*/
public function bitNot( $field ) {
@@ -2020,8 +2196,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * @param $fieldLeft
- * @param $fieldRight
+ * @param string $fieldLeft
+ * @param string $fieldRight
* @return string
*/
public function bitAnd( $fieldLeft, $fieldRight ) {
@@ -2029,8 +2205,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * @param $fieldLeft
- * @param $fieldRight
+ * @param string $fieldLeft
+ * @param string $fieldRight
* @return string
*/
public function bitOr( $fieldLeft, $fieldRight ) {
@@ -2039,32 +2215,59 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Build a concatenation list to feed into a SQL query
- * @param array $stringList list of raw SQL expressions; caller is responsible for any quoting
- * @return String
+ * @param array $stringList List of raw SQL expressions; caller is
+ * responsible for any quoting
+ * @return string
*/
public function buildConcat( $stringList ) {
return 'CONCAT(' . implode( ',', $stringList ) . ')';
}
/**
+ * Build a GROUP_CONCAT or equivalent statement for a query.
+ *
+ * This is useful for combining a field for several rows into a single string.
+ * NULL values will not appear in the output, duplicated values will appear,
+ * and the resulting delimiter-separated values have no defined sort order.
+ * Code using the results may need to use the PHP unique() or sort() methods.
+ *
+ * @param string $delim Glue to bind the results together
+ * @param string|array $table Table name
+ * @param string $field Field name
+ * @param string|array $conds Conditions
+ * @param string|array $join_conds Join conditions
+ * @return string SQL text
+ * @since 1.23
+ */
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = array()
+ ) {
+ $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
+
+ return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
+ }
+
+ /**
* Change the current database
*
* @todo Explain what exactly will fail if this is not overridden.
*
- * @param $db
+ * @param string $db
*
* @return bool Success or failure
*/
public function selectDB( $db ) {
- # Stub. Shouldn't cause serious problems if it's not overridden, but
+ # Stub. Shouldn't cause serious problems if it's not overridden, but
# if your database engine supports a concept similar to MySQL's
# databases you may as well.
$this->mDBname = $db;
+
return true;
}
/**
* Get the current DB name
+ * @return string
*/
public function getDBname() {
return $this->mDBname;
@@ -2072,6 +2275,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Get the server hostname or IP address
+ * @return string
*/
public function getServer() {
return $this->mServer;
@@ -2087,15 +2291,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* themselves. Pass the canonical name to such functions. This is only needed
* when calling query() directly.
*
- * @param string $name database table name
+ * @param string $name Database table name
* @param string $format One of:
* quoted - Automatically pass the table name through addIdentifierQuotes()
* so that it can be used in a query.
* raw - Do not add identifier quotes to the table name
- * @return String: full database name
+ * @return string Full database name
*/
public function tableName( $name, $format = 'quoted' ) {
- global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
+ 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
@@ -2119,31 +2323,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# We reverse the explode so that database.table and table both output
# the correct table.
$dbDetails = explode( '.', $name, 2 );
- if ( count( $dbDetails ) == 2 ) {
+ if ( count( $dbDetails ) == 3 ) {
+ list( $database, $schema, $table ) = $dbDetails;
+ # We don't want any prefix added in this case
+ $prefix = '';
+ } elseif ( count( $dbDetails ) == 2 ) {
list( $database, $table ) = $dbDetails;
# We don't want any prefix added in this case
+ # In dbs that support it, $database may actually be the schema
+ # but that doesn't affect any of the functionality here
$prefix = '';
+ $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 ) # Paranoia check to prevent shared tables listing '`table`'
+ && !$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;
} else {
$database = null;
+ $schema = $this->mSchema; # Default schema
$prefix = $this->mTablePrefix; # Default prefix
}
}
# 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 ) ) {
+ if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) {
$tableName = $this->addIdentifierQuotes( $tableName );
}
+ # Quote $schema and merge it with the table name if needed
+ if ( $schema !== null ) {
+ if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) {
+ $schema = $this->addIdentifierQuotes( $schema );
+ }
+ $tableName = $schema . '.' . $tableName;
+ }
+
# Quote $database and merge it with the table name if needed
if ( $database !== null ) {
if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
@@ -2218,8 +2440,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Gets an array of aliased table names
*
- * @param $tables array( [alias] => table )
- * @return array of strings, see tableNameWithAlias()
+ * @param array $tables Array( [alias] => table )
+ * @return string[] See tableNameWithAlias()
*/
public function tableNamesWithAlias( $tables ) {
$retval = array();
@@ -2229,6 +2451,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
$retval[] = $this->tableNameWithAlias( $table, $alias );
}
+
return $retval;
}
@@ -2251,8 +2474,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Gets an array of aliased field names
*
- * @param $fields array( [alias] => field )
- * @return array of strings, see fieldNameWithAlias()
+ * @param array $fields Array( [alias] => field )
+ * @return string[] See fieldNameWithAlias()
*/
public function fieldNamesWithAlias( $fields ) {
$retval = array();
@@ -2262,6 +2485,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
$retval[] = $this->fieldNameWithAlias( $field, $alias );
}
+
return $retval;
}
@@ -2270,8 +2494,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* which might have a JOIN and/or USE INDEX clause
*
* @param array $tables ( [alias] => table )
- * @param $use_index array Same as for select()
- * @param $join_conds array Same as for select()
+ * @param array $use_index Same as for select()
+ * @param array $join_conds Same as for select()
* @return string
*/
protected function tableNamesWithUseIndexOrJOIN(
@@ -2304,11 +2528,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
$retJOIN[] = $tableClause;
- // Is there an INDEX clause for this table?
} elseif ( isset( $use_index[$alias] ) ) {
+ // Is there an INDEX clause for this table?
$tableClause = $this->tableNameWithAlias( $table, $alias );
$tableClause .= ' ' . $this->useIndexClause(
- implode( ',', (array)$use_index[$alias] ) );
+ implode( ',', (array)$use_index[$alias] )
+ );
$ret[] = $tableClause;
} else {
@@ -2329,8 +2554,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Get the name of an index in a given table
*
- * @param $index
- *
+ * @param string $index
* @return string
*/
protected function indexName( $index ) {
@@ -2351,8 +2575,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Adds quotes and backslashes.
*
- * @param $s string
- *
+ * @param string $s
* @return string
*/
public function addQuotes( $s ) {
@@ -2373,8 +2596,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Since MySQL is the odd one out here the double quotes are our generic
* and we implement backticks in DatabaseMysql.
*
- * @param $s string
- *
+ * @param string $s
* @return string
*/
public function addIdentifierQuotes( $s ) {
@@ -2385,37 +2607,36 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Returns if the given identifier looks quoted or not according to
* the database convention for quoting identifiers .
*
- * @param $name string
- *
- * @return boolean
+ * @param string $name
+ * @return bool
*/
public function isQuotedIdentifier( $name ) {
return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
}
/**
- * @param $s string
+ * @param string $s
* @return string
*/
protected function escapeLikeInternal( $s ) {
- $s = str_replace( '\\', '\\\\', $s );
- $s = $this->strencode( $s );
- $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
-
- return $s;
+ return addcslashes( $s, '\%_' );
}
/**
- * LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match
- * containing either string literals that will be escaped or tokens returned by anyChar() or anyString().
- * Alternatively, the function could be provided with an array of aforementioned parameters.
+ * LIKE statement wrapper, receives a variable-length argument list with
+ * parts of pattern to match containing either string literals that will be
+ * escaped or tokens returned by anyChar() or anyString(). Alternatively,
+ * the function could be provided with an array of aforementioned
+ * parameters.
*
- * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns a LIKE clause that searches
- * for subpages of 'My page title'.
- * Alternatively: $pattern = array( 'My_page_title/', $dbr->anyString() ); $query .= $dbr->buildLike( $pattern );
+ * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns
+ * a LIKE clause that searches for subpages of 'My page title'.
+ * Alternatively:
+ * $pattern = array( 'My_page_title/', $dbr->anyString() );
+ * $query .= $dbr->buildLike( $pattern );
*
* @since 1.16
- * @return String: fully built LIKE statement
+ * @return string Fully built LIKE statement
*/
public function buildLike() {
$params = func_get_args();
@@ -2434,7 +2655,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
}
- return " LIKE '" . $s . "' ";
+ return " LIKE {$this->addQuotes( $s )} ";
}
/**
@@ -2463,21 +2684,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Any implementation of this function should *not* involve reusing
* sequence numbers created for rolled-back transactions.
* See http://bugs.mysql.com/bug.php?id=30767 for details.
- * @param $seqName string
- * @return null
+ * @param string $seqName
+ * @return null|int
*/
public function nextSequenceValue( $seqName ) {
return null;
}
/**
- * USE INDEX clause. Unlikely to be useful for anything but MySQL. This
+ * USE 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
+ * 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 $index
+ * @param string $index
* @return string
*/
public function useIndexClause( $index ) {
@@ -2500,10 +2721,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* errors which wouldn't have occurred in MySQL.
*
* @param string $table The table to replace the row(s) in.
+ * @param array $uniqueIndexes Is an array of indexes. Each element may be either
+ * a field name or an array of field names
* @param array $rows Can be either a single row to insert, or multiple rows,
* in the same format as for DatabaseBase::insert()
- * @param array $uniqueIndexes is an array of indexes. Each element may be either
- * 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 = __METHOD__ ) {
@@ -2558,7 +2779,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* statement.
*
* @param string $table Table name
- * @param array $rows Rows to insert
+ * @param array|string $rows Row(s) to insert
* @param string $fname Caller function name
*
* @return ResultWrapper
@@ -2609,26 +2830,28 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Usually throws a DBQueryError on failure. If errors are explicitly ignored,
* returns success.
*
+ * @since 1.22
+ *
* @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 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
- *
+ * @throws Exception
* @return bool
- * @since 1.22
*/
- public function upsert(
- $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ 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 ( !is_array( reset( $rows ) ) ) {
+ $rows = array( $rows );
+ }
if ( count( $uniqueIndexes ) ) {
$clauses = array(); // list WHERE clauses that each identify a single row
@@ -2684,19 +2907,18 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* DO NOT put the join condition in $conds.
*
- * @param $delTable String: The table to delete from.
- * @param $joinTable String: The other table.
- * @param $delVar String: The variable to join on, in the first table.
- * @param $joinVar String: The variable to join on, in the second table.
- * @param $conds Array: Condition array of field names mapped to variables,
- * ANDed together in the WHERE clause
- * @param $fname String: Calling function name (use __METHOD__) for
- * logs/profiling
+ * @param string $delTable The table to delete from.
+ * @param string $joinTable The other table.
+ * @param string $delVar The variable to join on, in the first table.
+ * @param string $joinVar The variable to join on, in the second table.
+ * @param array $conds Condition array of field names mapped to variables,
+ * ANDed together in the WHERE clause
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
* @throws DBUnexpectedError
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
- $fname = __METHOD__ )
- {
+ $fname = __METHOD__
+ ) {
if ( !$conds ) {
throw new DBUnexpectedError( $this,
'DatabaseBase::deleteJoin() called with empty $conds' );
@@ -2716,9 +2938,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Returns the size of a text field, or -1 for "unlimited"
*
- * @param $table string
- * @param $field string
- *
+ * @param string $table
+ * @param string $field
* @return int
*/
public function textFieldSize( $table, $field ) {
@@ -2740,7 +2961,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* A string to insert into queries to show that they're low-priority, like
- * MySQL's LOW_PRIORITY. If no such feature exists, return an empty
+ * MySQL's LOW_PRIORITY. If no such feature exists, return an empty
* string and nothing bad should happen.
*
* @return string Returns the text of the low priority option if it is
@@ -2754,10 +2975,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* DELETE query wrapper.
*
* @param array $table Table name
- * @param string|array $conds of conditions. See $conds in DatabaseBase::select() for
- * the format. Use $conds == "*" to delete all rows
- * @param string $fname name of the calling function
- *
+ * @param string|array $conds Array of conditions. See $conds in DatabaseBase::select()
+ * for the format. Use $conds == "*" to delete all rows
+ * @param string $fname Name of the calling function
* @throws DBUnexpectedError
* @return bool|ResultWrapper
*/
@@ -2787,7 +3007,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param string|array $srcTable May be either a table name, or an array of table names
* to include in a join.
*
- * @param array $varMap must be an associative array of the form
+ * @param array $varMap Must be an associative array of the form
* array( 'dest1' => 'source1', ...). Source items may be literals
* rather than field names, but strings should be quoted with
* DatabaseBase::addQuotes()
@@ -2807,14 +3027,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*/
public function insertSelect( $destTable, $srcTable, $varMap, $conds,
$fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array() )
- {
+ $insertOptions = array(), $selectOptions = array()
+ ) {
$destTable = $this->tableName( $destTable );
- if ( is_array( $insertOptions ) ) {
- $insertOptions = implode( ' ', $insertOptions );
+ if ( !is_array( $insertOptions ) ) {
+ $insertOptions = array( $insertOptions );
}
+ $insertOptions = $this->makeInsertOptions( $insertOptions );
+
if ( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
}
@@ -2844,22 +3066,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Construct a LIMIT query with optional offset. This is used for query
- * pages. The SQL should be adjusted so that only the first $limit rows
- * are returned. If $offset is provided as well, then the first $offset
+ * Construct a LIMIT query with optional offset. This is used for query
+ * pages. The SQL should be adjusted so that only the first $limit rows
+ * are returned. If $offset is provided as well, then the first $offset
* rows should be discarded, and the next $limit rows should be returned.
* If the result of the query is not ordered, then the rows to be returned
* are theoretically arbitrary.
*
* $sql is expected to be a SELECT, if that makes a difference.
*
- * The version provided by default works in MySQL and SQLite. It will very
+ * The version provided by default works in MySQL and SQLite. It will very
* likely need to be overridden for most other DBMSes.
*
* @param string $sql SQL query we will append the limit too
- * @param $limit Integer the SQL limit
- * @param $offset Integer|bool the SQL offset (default false)
- *
+ * @param int $limit The SQL limit
+ * @param int|bool $offset The SQL offset (default false)
* @throws DBUnexpectedError
* @return string
*/
@@ -2867,6 +3088,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( !is_numeric( $limit ) ) {
throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
}
+
return "$sql LIMIT "
. ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
. "{$limit} ";
@@ -2875,7 +3097,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
* within the UNION construct.
- * @return Boolean
+ * @return bool
*/
public function unionSupportsOrderAndLimit() {
return true; // True for almost every DB supported
@@ -2886,27 +3108,29 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* This is used for providing overload point for other DB abstractions
* not compatible with the MySQL syntax.
* @param array $sqls SQL statements to combine
- * @param $all Boolean: use UNION ALL
- * @return String: SQL fragment
+ * @param bool $all Use UNION ALL
+ * @return string SQL fragment
*/
public function unionQueries( $sqls, $all ) {
$glue = $all ? ') UNION ALL (' : ') UNION (';
+
return '(' . implode( $glue, $sqls ) . ')';
}
/**
- * Returns an SQL expression for a simple conditional. This doesn't need
+ * Returns an SQL expression for a simple conditional. This doesn't need
* to be overridden unless CASE isn't supported in your DBMS.
*
* @param string|array $cond SQL expression which will result in a boolean value
* @param string $trueVal SQL expression to return if true
* @param string $falseVal SQL expression to return if false
- * @return String: SQL fragment
+ * @return string SQL fragment
*/
public function conditional( $cond, $trueVal, $falseVal ) {
if ( is_array( $cond ) ) {
$cond = $this->makeList( $cond, LIST_AND );
}
+
return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
}
@@ -2914,9 +3138,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Returns a comand for str_replace function in SQL query.
* Uses REPLACE() in MySQL
*
- * @param string $orig column to modify
- * @param string $old column to seek
- * @param string $new column to replace with
+ * @param string $orig Column to modify
+ * @param string $old Column to seek
+ * @param string $new Column to replace with
*
* @return string
*/
@@ -3027,9 +3251,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $tries <= 0 ) {
$this->rollback( __METHOD__ );
$this->reportQueryError( $error, $errno, $sql, $fname );
+
return false;
} else {
$this->commit( __METHOD__ );
+
return $retVal;
}
}
@@ -3037,38 +3263,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Wait for the slave to catch up to a given master position.
*
- * @param $pos DBMasterPos object
- * @param $timeout Integer: the maximum number of seconds to wait for
+ * @param DBMasterPos $pos
+ * @param int $timeout The maximum number of seconds to wait for
* synchronisation
- *
- * @return integer: zero if the slave was past that position already,
+ * @return int Zero if the slave was past that position already,
* greater than zero if we waited for some period of time, less than
* zero if we timed out.
*/
public function masterPosWait( DBMasterPos $pos, $timeout ) {
- wfProfileIn( __METHOD__ );
-
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
-
- if ( $wait > $timeout * 1e6 ) {
- wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
- wfProfileOut( __METHOD__ );
- return -1;
- } elseif ( $wait > 0 ) {
- wfDebug( "Fake slave waiting $wait us\n" );
- usleep( $wait );
- wfProfileOut( __METHOD__ );
- return 1;
- } else {
- wfDebug( "Fake slave up to date ($wait us)\n" );
- wfProfileOut( __METHOD__ );
- return 0;
- }
- }
-
- wfProfileOut( __METHOD__ );
-
# Real waits are implemented in the subclass.
return 0;
}
@@ -3076,30 +3278,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Get the replication position of this slave
*
- * @return DBMasterPos, or false if this is not a slave.
+ * @return DBMasterPos|bool False if this is not a slave.
*/
public function getSlavePos() {
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
- wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
- return $pos;
- } else {
- # Stub
- return false;
- }
+ # Stub
+ return false;
}
/**
* Get the position of this master
*
- * @return DBMasterPos, or false if this is not a master
+ * @return DBMasterPos|bool False if this is not a master
*/
public function getMasterPos() {
- if ( $this->mFakeMaster ) {
- return new MySQLMasterPos( 'fake', microtime( true ) );
- } else {
- return false;
- }
+ # Stub
+ return false;
}
/**
@@ -3150,7 +3343,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
protected function runOnTransactionIdleCallbacks() {
$autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
- $e = null; // last exception
+ $e = $ePrior = null; // last exception
do { // callbacks may add callbacks :)
$callbacks = $this->mTrxIdleCallbacks;
$this->mTrxIdleCallbacks = array(); // recursion guard
@@ -3160,7 +3353,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->clearFlag( DBO_TRX ); // make each query its own transaction
call_user_func( $phpCallback );
$this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
- } catch ( Exception $e ) {}
+ } catch ( Exception $e ) {
+ if ( $ePrior ) {
+ MWExceptionHandler::logException( $ePrior );
+ }
+ $ePrior = $e;
+ }
}
} while ( count( $this->mTrxIdleCallbacks ) );
@@ -3175,7 +3373,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @since 1.22
*/
protected function runOnTransactionPreCommitCallbacks() {
- $e = null; // last exception
+ $e = $ePrior = null; // last exception
do { // callbacks may add callbacks :)
$callbacks = $this->mTrxPreCommitCallbacks;
$this->mTrxPreCommitCallbacks = array(); // recursion guard
@@ -3183,7 +3381,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
try {
list( $phpCallback ) = $callback;
call_user_func( $phpCallback );
- } catch ( Exception $e ) {}
+ } catch ( Exception $e ) {
+ if ( $ePrior ) {
+ MWExceptionHandler::logException( $ePrior );
+ }
+ $ePrior = $e;
+ }
}
} while ( count( $this->mTrxPreCommitCallbacks ) );
@@ -3193,22 +3396,91 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Begin a transaction. If a transaction is already in progress, that transaction will be committed before the
- * new transaction is started.
+ * Begin an atomic section of statements
+ *
+ * If a transaction has been started already, just keep track of the given
+ * section name to make sure the transaction is not committed pre-maturely.
+ * This function can be used in layers (with sub-sections), so use a stack
+ * to keep track of the different atomic sections. If there is no transaction,
+ * start one implicitly.
+ *
+ * The goal of this function is to create an atomic section of SQL queries
+ * without having to start a new transaction if it already exists.
*
- * Note that when the DBO_TRX flag is set (which is usually the case for web requests, but not for maintenance scripts),
- * any previous database query will have started a transaction automatically.
+ * Atomic sections are more strict than transactions. With transactions,
+ * attempting to begin a new transaction when one is already running results
+ * in MediaWiki issuing a brief warning and doing an implicit commit. All
+ * atomic levels *must* be explicitly closed using DatabaseBase::endAtomic(),
+ * and any database transactions cannot be began or committed until all atomic
+ * levels are closed. There is no such thing as implicitly opening or closing
+ * an atomic section.
+ *
+ * @since 1.23
+ * @param string $fname
+ * @throws DBError
+ */
+ final public function startAtomic( $fname = __METHOD__ ) {
+ if ( !$this->mTrxLevel ) {
+ $this->begin( $fname );
+ $this->mTrxAutomatic = true;
+ $this->mTrxAutomaticAtomic = true;
+ }
+
+ $this->mTrxAtomicLevels->push( $fname );
+ }
+
+ /**
+ * Ends an atomic section of SQL statements
*
- * Nesting of transactions is not supported. Attempts to nest transactions will cause a warning, unless the current
- * transaction was started automatically because of the DBO_TRX flag.
+ * Ends the next section of atomic SQL statements and commits the transaction
+ * if necessary.
*
- * @param $fname string
+ * @since 1.23
+ * @see DatabaseBase::startAtomic
+ * @param string $fname
+ * @throws DBError
+ */
+ final public function endAtomic( $fname = __METHOD__ ) {
+ if ( !$this->mTrxLevel ) {
+ throw new DBUnexpectedError( $this, 'No atomic transaction is open.' );
+ }
+ if ( $this->mTrxAtomicLevels->isEmpty() ||
+ $this->mTrxAtomicLevels->pop() !== $fname
+ ) {
+ throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' );
+ }
+
+ if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) {
+ $this->commit( $fname, 'flush' );
+ }
+ }
+
+ /**
+ * Begin a transaction. If a transaction is already in progress,
+ * that transaction will be committed before the new transaction is started.
+ *
+ * Note that when the DBO_TRX flag is set (which is usually the case for web
+ * requests, but not for maintenance scripts), any previous database query
+ * will have started a transaction automatically.
+ *
+ * Nesting of transactions is not supported. Attempts to nest transactions
+ * will cause a warning, unless the current transaction was started
+ * automatically because of the DBO_TRX flag.
+ *
+ * @param string $fname
+ * @throws DBError
*/
final public function begin( $fname = __METHOD__ ) {
global $wgDebugDBTransactions;
if ( $this->mTrxLevel ) { // implicit commit
- if ( !$this->mTrxAutomatic ) {
+ if ( !$this->mTrxAtomicLevels->isEmpty() ) {
+ // If the current transaction was an automatic atomic one, then we definitely have
+ // a problem. Same if there is any unclosed atomic level.
+ throw new DBUnexpectedError( $this,
+ "Attempted to start explicit transaction when atomic levels are still open."
+ );
+ } elseif ( !$this->mTrxAutomatic ) {
// We want to warn about inadvertently nested begin/commit pairs, but not about
// auto-committing implicit transactions that were started by query() via DBO_TRX
$msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
@@ -3228,22 +3500,33 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ Profiler::instance()->transactionWritingOut(
+ $this->mServer, $this->mDBname, $this->mTrxShortId );
}
$this->runOnTransactionIdleCallbacks();
}
+ # Avoid fatals if close() was called
+ if ( !$this->isOpen() ) {
+ throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ }
+
$this->doBegin( $fname );
$this->mTrxFname = $fname;
$this->mTrxDoneWrites = false;
$this->mTrxAutomatic = false;
+ $this->mTrxAutomaticAtomic = false;
+ $this->mTrxAtomicLevels = new SplStack;
+ $this->mTrxIdleCallbacks = array();
+ $this->mTrxPreCommitCallbacks = array();
+ $this->mTrxShortId = wfRandomString( 12 );
}
/**
* Issues the BEGIN command to the database server.
*
* @see DatabaseBase::begin()
- * @param type $fname
+ * @param string $fname
*/
protected function doBegin( $fname ) {
$this->query( 'BEGIN', $fname );
@@ -3256,33 +3539,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* Nesting of transactions is not supported.
*
- * @param $fname string
- * @param string $flush Flush flag, set to 'flush' to disable warnings about explicitly committing implicit
- * transactions, or calling commit when no transaction is in progress.
- * 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.
+ * @param string $fname
+ * @param string $flush Flush flag, set to 'flush' to disable warnings about
+ * explicitly committing implicit transactions, or calling commit when no
+ * transaction is in progress. 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.
+ * @throws DBUnexpectedError
*/
final public function commit( $fname = __METHOD__, $flush = '' ) {
- if ( $flush != 'flush' ) {
+ if ( !$this->mTrxAtomicLevels->isEmpty() ) {
+ // There are still atomic sections open. This cannot be ignored
+ throw new DBUnexpectedError(
+ $this,
+ "Attempted to commit transaction while atomic sections are still open"
+ );
+ }
+
+ if ( $flush === 'flush' ) {
if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to commit, something got out of sync!" );
- } elseif ( $this->mTrxAutomatic ) {
- wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
+ return; // nothing to do
+ } elseif ( !$this->mTrxAutomatic ) {
+ wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
}
} else {
if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to commit, something got out of sync!" );
return; // nothing to do
- } elseif ( !$this->mTrxAutomatic ) {
- wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
+ } elseif ( $this->mTrxAutomatic ) {
+ wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
}
}
+ # Avoid fatals if close() was called
+ if ( !$this->isOpen() ) {
+ throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ }
+
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ Profiler::instance()->transactionWritingOut(
+ $this->mServer, $this->mDBname, $this->mTrxShortId );
}
- $this->mTrxDoneWrites = false;
$this->runOnTransactionIdleCallbacks();
}
@@ -3290,7 +3589,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Issues the COMMIT command to the database server.
*
* @see DatabaseBase::commit()
- * @param type $fname
+ * @param string $fname
*/
protected function doCommit( $fname ) {
if ( $this->mTrxLevel ) {
@@ -3305,26 +3604,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* No-op on non-transactional databases.
*
- * @param $fname string
+ * @param string $fname
+ * @param string $flush Flush flag, set to 'flush' to disable warnings about
+ * calling rollback when no transaction is in progress. 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.
+ * @since 1.23 Added $flush parameter
*/
- final public function rollback( $fname = __METHOD__ ) {
- if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
+ final public function rollback( $fname = __METHOD__, $flush = '' ) {
+ if ( $flush !== 'flush' ) {
+ if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
+ return; // nothing to do
+ } elseif ( $this->mTrxAutomatic ) {
+ wfWarn( "$fname: Explicit rollback of implicit transaction. Something may be out of sync!" );
+ }
+ } else {
+ if ( !$this->mTrxLevel ) {
+ return; // nothing to do
+ } elseif ( !$this->mTrxAutomatic ) {
+ wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
+ }
}
+
+ # Avoid fatals if close() was called
+ if ( !$this->isOpen() ) {
+ throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ }
+
$this->doRollback( $fname );
$this->mTrxIdleCallbacks = array(); // cancel
$this->mTrxPreCommitCallbacks = array(); // cancel
+ $this->mTrxAtomicLevels = new SplStack;
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ Profiler::instance()->transactionWritingOut(
+ $this->mServer, $this->mDBname, $this->mTrxShortId );
}
- $this->mTrxDoneWrites = false;
}
/**
* Issues the ROLLBACK command to the database server.
*
* @see DatabaseBase::rollback()
- * @param type $fname
+ * @param string $fname
*/
protected function doRollback( $fname ) {
if ( $this->mTrxLevel ) {
@@ -3341,12 +3663,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* The table names passed to this function shall not be quoted (this
* function calls addIdentifierQuotes when needed).
*
- * @param string $oldName name of table whose structure should be copied
- * @param string $newName name of table to be created
- * @param $temporary Boolean: whether the new table should be temporary
- * @param string $fname calling function name
+ * @param string $oldName Name of table whose structure should be copied
+ * @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
- * @return Boolean: true if operation was successful
+ * @return bool True if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
$fname = __METHOD__
@@ -3359,7 +3681,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* List all tables on the database
*
* @param string $prefix Only show tables with this prefix, e.g. mw_
- * @param string $fname calling function name
+ * @param string $fname Calling function name
* @throws MWException
*/
function listTables( $prefix = null, $fname = __METHOD__ ) {
@@ -3380,8 +3702,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* 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
+ * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
+ * @param string $fname Name of calling function
* @throws MWException
* @since 1.22
*/
@@ -3392,7 +3714,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Differentiates between a TABLE and a VIEW
*
- * @param $name string: Name of the database-structure to test.
+ * @param string $name Name of the database-structure to test.
* @throws MWException
* @since 1.22
*/
@@ -3407,7 +3729,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* The result is unquoted, and needs to be passed through addQuotes()
* before it can be included in raw SQL.
*
- * @param $ts string|int
+ * @param string|int $ts
*
* @return string
*/
@@ -3424,7 +3746,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* The result is unquoted, and needs to be passed through addQuotes()
* before it can be included in raw SQL.
*
- * @param $ts string|int
+ * @param string|int $ts
*
* @return string
*/
@@ -3447,8 +3769,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* callers, so this is unnecessary in external code. For compatibility with
* old code, ResultWrapper objects are passed through unaltered.
*
- * @param $result bool|ResultWrapper
- *
+ * @param bool|ResultWrapper|resource $result
* @return bool|ResultWrapper
*/
public function resultObject( $result ) {
@@ -3470,7 +3791,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @return bool Success or failure
*/
public function ping() {
- # Stub. Not essential to override.
+ # Stub. Not essential to override.
return true;
}
@@ -3484,7 +3805,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @return int Database replication lag in seconds
*/
public function getLag() {
- return intval( $this->mFakeSlaveLag );
+ return 0;
}
/**
@@ -3501,7 +3822,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* don't allow simple quoted strings to be inserted. To insert into such
* a field, pass the data through this function before passing it to
* DatabaseBase::insert().
- * @param $b string
+ *
+ * @param string $b
* @return string
*/
public function encodeBlob( $b ) {
@@ -3512,7 +3834,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Some DBMSs return a special placeholder object representing blob fields
* in result objects. Pass the object through this function to return the
* original string.
- * @param $b string
+ *
+ * @param string $b
* @return string
*/
public function decodeBlob( $b ) {
@@ -3526,7 +3849,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* full-wiki dumps, where a single query reads out over
* hours or days.
*
- * @param $options Array
+ * @param array $options
* @return void
*/
public function setSessionOptions( array $options ) {
@@ -3542,9 +3865,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param bool|callable $lineCallback Optional function called before reading each line
* @param bool|callable $resultCallback Optional function called for each MySQL result
* @param bool|string $fname Calling function name or false if name should be
- * generated dynamically using $filename
- * @param bool|callable $inputCallback Callback: Optional function called for each complete line sent
- * @throws MWException
+ * generated dynamically using $filename
+ * @param bool|callable $inputCallback Optional function called for each
+ * complete line sent
* @throws Exception|MWException
* @return bool|string
*/
@@ -3565,8 +3888,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
try {
$error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
- }
- catch ( MWException $e ) {
+ } catch ( MWException $e ) {
fclose( $fp );
throw $e;
}
@@ -3582,7 +3904,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* 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
+ * @return string Full path to patch file
*/
public function patchPath( $patch ) {
global $IP;
@@ -3600,7 +3922,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
* all. If it's set to false, $GLOBALS will be used.
*
- * @param bool|array $vars mapping variable name to value.
+ * @param bool|array $vars Mapping variable name to value.
*/
public function setSchemaVars( $vars ) {
$this->mSchemaVars = $vars;
@@ -3612,16 +3934,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Returns true on success, error string or exception on failure (depending
* on object's error ignore settings).
*
- * @param $fp Resource: File handle
- * @param $lineCallback Callback: Optional function called before reading each query
- * @param $resultCallback Callback: Optional function called for each MySQL result
+ * @param resource $fp File handle
+ * @param bool|callable $lineCallback Optional function called before reading each query
+ * @param bool|callable $resultCallback Optional function called for each MySQL result
* @param string $fname Calling function name
- * @param $inputCallback Callback: Optional function called for each complete query sent
+ * @param bool|callable $inputCallback Optional function called for each complete query sent
* @return bool|string
*/
public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = __METHOD__, $inputCallback = false )
- {
+ $fname = __METHOD__, $inputCallback = false
+ ) {
$cmd = '';
while ( !feof( $fp ) ) {
@@ -3659,6 +3981,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( false === $res ) {
$err = $this->lastError();
+
return "Query \"{$cmd}\" failed with error code \"$err\".\n";
}
}
@@ -3674,7 +3997,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* @param string $sql SQL assembled so far
* @param string $newLine New line about to be added to $sql
- * @return Bool Whether $newLine contains end of the statement
+ * @return bool Whether $newLine contains end of the statement
*/
public function streamStatementEnd( &$sql, &$newLine ) {
if ( $this->delimiter ) {
@@ -3684,6 +4007,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
return true;
}
}
+
return false;
}
@@ -3702,7 +4026,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* table options its use should be avoided.
*
* @param string $ins SQL statement to replace variables in
- * @return String The new SQL statement with variables replaced
+ * @return string The new SQL statement with variables replaced
*/
protected function replaceSchemaVars( $ins ) {
$vars = $this->getSchemaVars();
@@ -3714,14 +4038,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
// replace /*$var*/
$ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
}
+
return $ins;
}
/**
* Replace variables in sourced SQL
*
- * @param $ins string
- *
+ * @param string $ins
* @return string
*/
protected function replaceVars( $ins ) {
@@ -3767,8 +4091,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Table name callback
*
- * @param $matches array
- *
+ * @param array $matches
* @return string
*/
protected function tableNameCallback( $matches ) {
@@ -3778,8 +4101,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Index name callback
*
- * @param $matches array
- *
+ * @param array $matches
* @return string
*/
protected function indexNameCallback( $matches ) {
@@ -3789,9 +4111,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Check to see if a named lock is available. This is non-blocking.
*
- * @param string $lockName name of lock to poll
- * @param string $method name of method calling us
- * @return Boolean
+ * @param string $lockName Name of lock to poll
+ * @param string $method Name of method calling us
+ * @return bool
* @since 1.20
*/
public function lockIsFree( $lockName, $method ) {
@@ -3804,10 +4126,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Abstracted from Filestore::lock() so child classes can implement for
* their own needs.
*
- * @param string $lockName name of lock to aquire
- * @param string $method name of method calling us
- * @param $timeout Integer: timeout
- * @return Boolean
+ * @param string $lockName Name of lock to aquire
+ * @param string $method Name of method calling us
+ * @param int $timeout
+ * @return bool
*/
public function lock( $lockName, $method, $timeout = 5 ) {
return true;
@@ -3830,11 +4152,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Lock specific tables
*
- * @param array $read of tables to lock for read access
- * @param array $write of tables to lock for write access
- * @param string $method name of caller
+ * @param array $read Array of tables to lock for read access
+ * @param array $write Array of tables to lock for write access
+ * @param string $method Name of caller
* @param bool $lowPriority Whether to indicate writes to be LOW PRIORITY
- *
* @return bool
*/
public function lockTables( $read, $write, $method, $lowPriority = true ) {
@@ -3844,8 +4165,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Unlock specific tables
*
- * @param string $method the caller
- *
+ * @param string $method The caller
* @return bool
*/
public function unlockTables( $method ) {
@@ -3854,8 +4174,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Delete a table
- * @param $tableName string
- * @param $fName string
+ * @param string $tableName
+ * @param string $fName
* @return bool|ResultWrapper
* @since 1.18
*/
@@ -3867,6 +4187,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $this->cascadingDeletes() ) {
$sql .= " CASCADE";
}
+
return $this->query( $sql, $fName );
}
@@ -3874,7 +4195,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Get search engine class. All subclasses of this need to implement this
* if they wish to use searching.
*
- * @return String
+ * @return string
*/
public function getSearchEngine() {
return 'SearchEngineDummy';
@@ -3885,7 +4206,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* keyword for timestamps in PostgreSQL, and works with CHAR(14) as well
* because "i" sorts after all numbers.
*
- * @return String
+ * @return string
*/
public function getInfinity() {
return 'infinity';
@@ -3894,8 +4215,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Encode an expiry time into the DBMS dependent format
*
- * @param string $expiry timestamp for expiry, or the 'infinity' string
- * @return String
+ * @param string $expiry Timestamp for expiry, or the 'infinity' string
+ * @return string
*/
public function encodeExpiry( $expiry ) {
return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
@@ -3907,8 +4228,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Decode an expiry time into a DBMS independent format
*
* @param string $expiry DB timestamp field value for expiry
- * @param $format integer: TS_* constant, defaults to TS_MW
- * @return String
+ * @param int $format TS_* constant, defaults to TS_MW
+ * @return string
*/
public function decodeExpiry( $expiry, $format = TS_MW ) {
return ( $expiry == '' || $expiry == $this->getInfinity() )
@@ -3922,7 +4243,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* This is a MySQL-specific feature.
*
- * @param $value Mixed: true for allow, false for deny, or "default" to
+ * @param bool|string $value True for allow, false for deny, or "default" to
* restore the initial value
*/
public function setBigSelects( $value = true ) {
@@ -3931,6 +4252,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* @since 1.19
+ * @return string
*/
public function __toString() {
return (string)$this->mConn;
@@ -3947,10 +4269,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$callers = array();
foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
$callers[] = $callbackInfo[1];
-
}
$callers = implode( ', ', $callers );
- trigger_error( "DB transaction callbacks still pending (from $callers)." );
+ trigger_error( "DB transaction callbacks still pending (from $callers)." );
}
}
}
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
index 0875695f..2dfec41d 100644
--- a/includes/db/DatabaseError.php
+++ b/includes/db/DatabaseError.php
@@ -26,22 +26,28 @@
* @ingroup Database
*/
class DBError extends MWException {
-
- /**
- * @var DatabaseBase
- */
+ /** @var DatabaseBase */
public $db;
/**
* Construct a database error
- * @param $db DatabaseBase object which threw the error
+ * @param DatabaseBase $db Object which threw the error
* @param string $error A simple error message to be used for debugging
*/
function __construct( DatabaseBase $db = null, $error ) {
$this->db = $db;
parent::__construct( $error );
}
+}
+/**
+ * Base class for the more common types of database errors. These are known to occur
+ * frequently, so we try to give friendly error messages for them.
+ *
+ * @ingroup Database
+ * @since 1.23
+ */
+class DBExpectedError extends DBError {
/**
* @return string
*/
@@ -66,8 +72,7 @@ class DBError extends MWException {
$s = $this->getHTMLContent();
if ( $wgShowDBErrorBacktrace ) {
- $s .= '<p>Backtrace:</p><p>' .
- nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . '</p>';
+ $s .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
}
return $s;
@@ -84,16 +89,21 @@ class DBError extends MWException {
* @return string
*/
protected function getHTMLContent() {
- return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) . '</p>';
+ return '<p>' . nl2br( htmlspecialchars( $this->getTextContent() ) ) . '</p>';
}
}
/**
* @ingroup Database
*/
-class DBConnectionError extends DBError {
+class DBConnectionError extends DBExpectedError {
+ /** @var string Error text */
public $error;
+ /**
+ * @param DatabaseBase $db Object throwing the error
+ * @param string $error Error text
+ */
function __construct( DatabaseBase $db = null, $error = 'unknown error' ) {
$msg = 'DB connection error';
@@ -116,25 +126,24 @@ class DBConnectionError extends DBError {
}
/**
- * @param $key
- * @param $fallback
- * @return string
+ * @param string $key
+ * @param string $fallback Unescaped alternative error text in case the
+ * message cache cannot be used. Can contain parameters as in regular
+ * messages, that should be passed as additional parameters.
+ * @return string Unprocessed plain error text with parameters replaced
*/
function msg( $key, $fallback /*[, params...] */ ) {
- global $wgLang;
-
$args = array_slice( func_get_args(), 2 );
if ( $this->useMessageCache() ) {
- $message = $wgLang->getMessage( $key );
+ return wfMessage( $key, $args )->useDatabase( false )->text();
} else {
- $message = $fallback;
+ return wfMsgReplaceArgs( $fallback, $args );
}
- return wfMsgReplaceArgs( $message, $args );
}
/**
- * @return boolean
+ * @return bool
*/
function isLoggable() {
// Don't send to the exception log, already in dberror log
@@ -142,20 +151,19 @@ class DBConnectionError extends DBError {
}
/**
- * @return string
- */
- function getPageTitle() {
- return $this->msg( 'dberr-header', 'This wiki has a problem' );
- }
-
- /**
- * @return string
+ * @return string Safe HTML
*/
function getHTML() {
global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
- $sorry = htmlspecialchars( $this->msg( 'dberr-problems', "Sorry!\nThis site is experiencing technical difficulties." ) );
- $again = htmlspecialchars( $this->msg( 'dberr-again', 'Try waiting a few minutes and reloading.' ) );
+ $sorry = htmlspecialchars( $this->msg(
+ 'dberr-problems',
+ 'Sorry! This site is experiencing technical difficulties.'
+ ) );
+ $again = htmlspecialchars( $this->msg(
+ 'dberr-again',
+ 'Try waiting a few minutes and reloading.'
+ ) );
if ( $wgShowHostnames || $wgShowSQLErrors ) {
$info = str_replace(
@@ -163,23 +171,25 @@ class DBConnectionError extends DBError {
htmlspecialchars( $this->msg( 'dberr-info', '(Cannot contact the database server: $1)' ) )
);
} else {
- $info = htmlspecialchars( $this->msg( 'dberr-info-hidden', '(Cannot contact the database server)' ) );
+ $info = htmlspecialchars( $this->msg(
+ 'dberr-info-hidden',
+ '(Cannot contact the database server)'
+ ) );
}
# No database access
MessageCache::singleton()->disable();
- $text = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
+ $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
if ( $wgShowDBErrorBacktrace ) {
- $text .= '<p>Backtrace:</p><p>' .
- nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . '</p>';
+ $html .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
}
- $text .= '<hr />';
- $text .= $this->searchForm();
+ $html .= '<hr />';
+ $html .= $this->searchForm();
- return $text;
+ return $html;
}
protected function getTextContent() {
@@ -192,25 +202,31 @@ class DBConnectionError extends DBError {
}
}
+ /**
+ * Output the exception report using HTML.
+ *
+ * @return void
+ */
public function reportHTML() {
global $wgUseFileCache;
- # Check whether we can serve a file-cached copy of the page with the error underneath
+ // Check whether we can serve a file-cached copy of the page with the error underneath
if ( $wgUseFileCache ) {
try {
$cache = $this->fileCachedPage();
- # Cached version on file system?
+ // Cached version on file system?
if ( $cache !== null ) {
- # Hack: extend the body for error messages
+ // Hack: extend the body for error messages
$cache = str_replace( array( '</html>', '</body>' ), '', $cache );
- # Add cache notice...
- $cache .= '<div style="color:red;font-size:150%;font-weight:bold;">' .
+ // Add cache notice...
+ $cache .= '<div style="border:1px solid #ffd0d0;padding:1em;">' .
htmlspecialchars( $this->msg( 'dberr-cachederror',
- 'This is a cached copy of the requested page, and may not be up to date. ' ) ) .
+ 'This is a cached copy of the requested page, and may not be up to date.' ) ) .
'</div>';
- # Output cached page with notices on bottom and re-close body
+ // Output cached page with notices on bottom and re-close body
echo "{$cache}<hr />{$this->getHTML()}</body></html>";
+
return;
}
} catch ( MWException $e ) {
@@ -218,7 +234,7 @@ class DBConnectionError extends DBError {
}
}
- # We can't, cough and die in the usual fashion
+ // We can't, cough and die in the usual fashion
parent::reportHTML();
}
@@ -228,8 +244,14 @@ class DBConnectionError extends DBError {
function searchForm() {
global $wgSitename, $wgCanonicalServer, $wgRequest;
- $usegoogle = htmlspecialchars( $this->msg( 'dberr-usegoogle', 'You can try searching via Google in the meantime.' ) );
- $outofdate = htmlspecialchars( $this->msg( 'dberr-outofdate', 'Note that their indexes of our content may be out of date.' ) );
+ $usegoogle = htmlspecialchars( $this->msg(
+ 'dberr-usegoogle',
+ 'You can try searching via Google in the meantime.'
+ ) );
+ $outofdate = htmlspecialchars( $this->msg(
+ 'dberr-outofdate',
+ 'Note that their indexes of our content may be out of date.'
+ ) );
$googlesearch = htmlspecialchars( $this->msg( 'searchbutton', 'Search' ) );
$search = htmlspecialchars( $wgRequest->getVal( 'search' ) );
@@ -239,8 +261,8 @@ class DBConnectionError extends DBError {
$trygoogle = <<<EOT
<div style="margin: 1.5em">$usegoogle<br />
-<small>$outofdate</small></div>
-<!-- SiteSearch Google -->
+<small>$outofdate</small>
+</div>
<form method="get" action="//www.google.com/search" id="googlesearch">
<input type="hidden" name="domains" value="$server" />
<input type="hidden" name="num" value="50" />
@@ -249,13 +271,13 @@ class DBConnectionError extends DBError {
<input type="text" name="q" size="31" maxlength="255" value="$search" />
<input type="submit" name="btnG" value="$googlesearch" />
- <div>
- <input type="radio" name="sitesearch" id="gwiki" value="$server" checked="checked" /><label for="gwiki">$sitename</label>
- <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
- </div>
+ <p>
+ <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label>
+ <label><input type="radio" name="sitesearch" value="" />WWW</label>
+ </p>
</form>
-<!-- SiteSearch Google -->
EOT;
+
return $trygoogle;
}
@@ -263,26 +285,28 @@ EOT;
* @return string
*/
private function fileCachedPage() {
- global $wgTitle, $wgOut, $wgRequest;
+ $context = RequestContext::getMain();
- if ( $wgOut->isDisabled() ) {
- return ''; // Done already?
+ if ( $context->getOutput()->isDisabled() ) {
+ // Done already?
+ return '';
}
- if ( $wgTitle ) { // use $wgTitle if we managed to set it
- $t = $wgTitle->getPrefixedDBkey();
+ if ( $context->getTitle() ) {
+ // Use the main context's title if we managed to set it
+ $t = $context->getTitle()->getPrefixedDBkey();
} else {
- # Fallback to the raw title URL param. We can't use the Title
- # class is it may hit the interwiki table and give a DB error.
- # We may get a cache miss due to not sanitizing the title though.
- $t = str_replace( ' ', '_', $wgRequest->getVal( 'title' ) );
+ // Fallback to the raw title URL param. We can't use the Title
+ // class is it may hit the interwiki table and give a DB error.
+ // We may get a cache miss due to not sanitizing the title though.
+ $t = str_replace( ' ', '_', $context->getRequest()->getVal( 'title' ) );
if ( $t == '' ) { // fallback to main page
$t = Title::newFromText(
$this->msg( 'mainpage', 'Main Page' ) )->getPrefixedDBkey();
}
}
- $cache = HTMLFileCache::newFromTitle( $t, 'view' );
+ $cache = new HTMLFileCache( $t, 'view' );
if ( $cache->isCached() ) {
return $cache->fetchText();
} else {
@@ -294,18 +318,20 @@ EOT;
/**
* @ingroup Database
*/
-class DBQueryError extends DBError {
+class DBQueryError extends DBExpectedError {
public $error, $errno, $sql, $fname;
/**
- * @param $db DatabaseBase
- * @param $error string
- * @param $errno int|string
- * @param $sql string
- * @param $fname string
+ * @param DatabaseBase $db
+ * @param string $error
+ * @param int|string $errno
+ * @param string $sql
+ * @param string $fname
*/
function __construct( DatabaseBase $db, $error, $errno, $sql, $fname ) {
- $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
+ $message = "A database error has occurred. Did you forget to run " .
+ "maintenance/update.php after upgrading? See: " .
+ "https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
"Query: $sql\n" .
"Function: $fname\n" .
"Error: $errno $error\n";
@@ -318,14 +344,6 @@ class DBQueryError extends DBError {
}
/**
- * @return boolean
- */
- function isLoggable() {
- // Don't send to the exception log, already in dberror log
- return false;
- }
-
- /**
* @return string
*/
function getPageTitle() {
@@ -380,7 +398,7 @@ class DBQueryError extends DBError {
* sites using this option probably don't care much about "security by obscurity". Of course,
* if $wgShowSQLErrors is true, the SQL query *is* shown.
*
- * @return array: Keys are message keys; values are arrays of arguments for Html::element().
+ * @return array Keys are message keys; values are arrays of arguments for Html::element().
* Array will be empty if users are not allowed to see any of these details at all.
*/
protected function getTechnicalDetails() {
@@ -405,7 +423,7 @@ class DBQueryError extends DBError {
/**
* @param string $key Message key
- * @return string: English message text
+ * @return string English message text
*/
private function getFallbackMessage( $key ) {
$messages = array(
@@ -416,12 +434,13 @@ This may indicate a bug in the software.',
'databaseerror-function' => 'Function: $1',
'databaseerror-error' => 'Error: $1',
);
+
return $messages[$key];
}
-
}
/**
* @ingroup Database
*/
-class DBUnexpectedError extends DBError {}
+class DBUnexpectedError extends DBError {
+}
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 37f5372e..af3cc72d 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -22,47 +22,54 @@
* @author Joel Penner <a-joelpe at microsoft dot com>
* @author Chris Pucci <a-cpucci at microsoft dot com>
* @author Ryan Biesemeyer <v-ryanbi at microsoft dot com>
+ * @author Ryan Schmidt <skizzerz at gmail dot com>
*/
/**
* @ingroup Database
*/
class DatabaseMssql extends DatabaseBase {
- var $mInsertId = null;
- var $mLastResult = null;
- var $mAffectedRows = null;
-
- var $mPort;
-
- function cascadingDeletes() {
+ protected $mInsertId = null;
+ protected $mLastResult = null;
+ protected $mAffectedRows = null;
+ protected $mSubqueryId = 0;
+ protected $mScrollableCursor = true;
+ protected $mPrepareStatements = true;
+ protected $mBinaryColumnCache = null;
+ protected $mBitColumnCache = null;
+ protected $mIgnoreDupKeyErrors = false;
+
+ protected $mPort;
+
+ public function cascadingDeletes() {
return true;
}
- function cleanupTriggers() {
- return true;
+ public function cleanupTriggers() {
+ return false;
}
- function strictIPs() {
- return true;
+ public function strictIPs() {
+ return false;
}
- function realTimestamps() {
- return true;
+ public function realTimestamps() {
+ return false;
}
- function implicitGroupby() {
+ public function implicitGroupby() {
return false;
}
- function implicitOrderby() {
+ public function implicitOrderby() {
return false;
}
- function functionalIndexes() {
+ public function functionalIndexes() {
return true;
}
- function unionSupportsOrderAndLimit() {
+ public function unionSupportsOrderAndLimit() {
return false;
}
@@ -75,16 +82,21 @@ class DatabaseMssql extends DatabaseBase {
* @throws DBConnectionError
* @return bool|DatabaseBase|null
*/
- function open( $server, $user, $password, $dbName ) {
+ public function open( $server, $user, $password, $dbName ) {
# Test for driver support, to avoid suppressed fatal error
if ( !function_exists( 'sqlsrv_connect' ) ) {
- throw new DBConnectionError( $this, "MS Sql Server Native (sqlsrv) functions missing. You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n" );
+ throw new DBConnectionError(
+ $this,
+ "Microsoft SQL Server Native (sqlsrv) functions missing.
+ You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
+ );
}
- global $wgDBport;
+ global $wgDBport, $wgDBWindowsAuthentication;
- if ( !strlen( $user ) ) { # e.g. the class is being loaded
- return;
+ # e.g. the class is being loaded
+ if ( !strlen( $user ) ) {
+ return null;
}
$this->close();
@@ -100,35 +112,23 @@ class DatabaseMssql extends DatabaseBase {
$connectionInfo['Database'] = $dbName;
}
- // Start NT Auth Hack
- // Quick and dirty work around to provide NT Auth designation support.
- // Current solution requires installer to know to input 'ntauth' for both username and password
- // to trigger connection via NT Auth. - ugly, ugly, ugly
- // TO-DO: Make this better and add NT Auth choice to MW installer when SQL Server option is chosen.
- $ntAuthUserTest = strtolower( $user );
- $ntAuthPassTest = strtolower( $password );
-
// Decide which auth scenerio to use
- if ( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ) {
- // Don't add credentials to $connectionInfo
- } else {
+ // if we are using Windows auth, don't add credentials to $connectionInfo
+ if ( !$wgDBWindowsAuthentication ) {
$connectionInfo['UID'] = $user;
$connectionInfo['PWD'] = $password;
}
- // End NT Auth Hack
wfSuppressWarnings();
$this->mConn = sqlsrv_connect( $server, $connectionInfo );
wfRestoreWarnings();
if ( $this->mConn === false ) {
- wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- wfDebug( $this->lastError() . "\n" );
- return false;
+ throw new DBConnectionError( $this, $this->lastError() );
}
$this->mOpened = true;
+
return $this->mConn;
}
@@ -141,14 +141,39 @@ class DatabaseMssql extends DatabaseBase {
return sqlsrv_close( $this->mConn );
}
+ /**
+ * @param bool|MssqlResultWrapper|resource $result
+ * @return bool|MssqlResultWrapper
+ */
+ public function resultObject( $result ) {
+ if ( empty( $result ) ) {
+ return false;
+ } elseif ( $result instanceof MssqlResultWrapper ) {
+ return $result;
+ } elseif ( $result === true ) {
+ // Successful write query
+ return $result;
+ } else {
+ return new MssqlResultWrapper( $this, $result );
+ }
+ }
+
+ /**
+ * @param string $sql
+ * @return bool|MssqlResult
+ * @throws DBUnexpectedError
+ */
protected function doQuery( $sql ) {
- wfDebug( "SQL: [$sql]\n" );
+ if ( $this->debug() ) {
+ wfDebug( "SQL: [$sql]\n" );
+ }
$this->offset = 0;
- // several extensions seem to think that all databases support limits via LIMIT N after the WHERE clause
- // well, MSSQL uses SELECT TOP N, so to catch any of those extensions we'll do a quick check for a LIMIT
- // clause and pass $sql through $this->LimitToTopN() which parses the limit clause and passes the result to
- // $this->limitResult();
+ // several extensions seem to think that all databases support limits
+ // via LIMIT N after the WHERE clause well, MSSQL uses SELECT TOP N,
+ // so to catch any of those extensions we'll do a quick check for a
+ // LIMIT clause and pass $sql through $this->LimitToTopN() which parses
+ // the limit clause and passes the result to $this->limitResult();
if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
// massage LIMIT -> TopN
$sql = $this->LimitToTopN( $sql );
@@ -161,149 +186,235 @@ class DatabaseMssql extends DatabaseBase {
}
// perform query
- $stmt = sqlsrv_query( $this->mConn, $sql );
- if ( $stmt == false ) {
- $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
- "Query: " . htmlentities( $sql ) . "\n" .
- "Function: " . __METHOD__ . "\n";
- // process each error (our driver will give us an array of errors unlike other providers)
- foreach ( sqlsrv_errors() as $error ) {
- $message .= $message . "ERROR[" . $error['code'] . "] " . $error['message'] . "\n";
- }
- throw new DBUnexpectedError( $this, $message );
+ // SQLSRV_CURSOR_STATIC is slower than SQLSRV_CURSOR_CLIENT_BUFFERED (one of the two is
+ // needed if we want to be able to seek around the result set), however CLIENT_BUFFERED
+ // has a bug in the sqlsrv driver where wchar_t types (such as nvarchar) that are empty
+ // strings make php throw a fatal error "Severe error translating Unicode"
+ if ( $this->mScrollableCursor ) {
+ $scrollArr = array( 'Scrollable' => SQLSRV_CURSOR_STATIC );
+ } else {
+ $scrollArr = array();
}
- // remember number of rows affected
- $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
- // if it is a SELECT statement, or an insert with a request to output something we want to return a row.
- if ( ( preg_match( '#\bSELECT\s#i', $sql ) ) ||
- ( preg_match( '#\bINSERT\s#i', $sql ) && preg_match( '#\bOUTPUT\s+INSERTED\b#i', $sql ) ) ) {
- // this is essentially a rowset, but Mediawiki calls these 'result'
- // the rowset owns freeing the statement
- $res = new MssqlResult( $stmt );
+ if ( $this->mPrepareStatements ) {
+ // we do prepare + execute so we can get its field metadata for later usage if desired
+ $stmt = sqlsrv_prepare( $this->mConn, $sql, array(), $scrollArr );
+ $success = sqlsrv_execute( $stmt );
} else {
- // otherwise we simply return it was successful, failure throws an exception
- $res = true;
+ $stmt = sqlsrv_query( $this->mConn, $sql, array(), $scrollArr );
+ $success = (bool)$stmt;
}
- return $res;
- }
- function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
+ if ( $this->mIgnoreDupKeyErrors ) {
+ // ignore duplicate key errors, but nothing else
+ // this emulates INSERT IGNORE in MySQL
+ if ( $success === false ) {
+ $errors = sqlsrv_errors( SQLSRV_ERR_ERRORS );
+ $success = true;
+
+ foreach ( $errors as $err ) {
+ if ( $err['SQLSTATE'] == '23000' && $err['code'] == '2601' ) {
+ continue; // duplicate key error caused by unique index
+ } elseif ( $err['SQLSTATE'] == '23000' && $err['code'] == '2627' ) {
+ continue; // duplicate key error caused by primary key
+ } elseif ( $err['SQLSTATE'] == '01000' && $err['code'] == '3621' ) {
+ continue; // generic "the statement has been terminated" error
+ }
+
+ $success = false; // getting here means we got an error we weren't expecting
+ break;
+ }
+
+ if ( $success ) {
+ $this->mAffectedRows = 0;
+ return $stmt;
+ }
+ }
+ }
+
+ if ( $success === false ) {
+ return false;
}
- $res->free();
+ // remember number of rows affected
+ $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
+
+ return $stmt;
}
- function fetchObject( $res ) {
+ public function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- $row = $res->fetch( 'OBJECT' );
- return $row;
+
+ sqlsrv_free_stmt( $res );
}
- function getErrors() {
- $strRet = '';
- $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
- if ( $retErrors != null ) {
- foreach ( $retErrors as $arrError ) {
- $strRet .= "SQLState: " . $arrError['SQLSTATE'] . "\n";
- $strRet .= "Error Code: " . $arrError['code'] . "\n";
- $strRet .= "Message: " . $arrError['message'] . "\n";
- }
- } else {
- $strRet = "No errors found";
- }
- return $strRet;
+ /**
+ * @param MssqlResultWrapper $res
+ * @return stdClass
+ */
+ public function fetchObject( $res ) {
+ // $res is expected to be an instance of MssqlResultWrapper here
+ return $res->fetchObject();
}
- function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- $row = $res->fetch( SQLSRV_FETCH_BOTH );
- return $row;
+ /**
+ * @param MssqlResultWrapper $res
+ * @return array
+ */
+ public function fetchRow( $res ) {
+ return $res->fetchRow();
}
- function numRows( $res ) {
+ /**
+ * @param mixed $res
+ * @return int
+ */
+ public function numRows( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return ( $res ) ? $res->numrows() : 0;
+
+ return sqlsrv_num_rows( $res );
}
- function numFields( $res ) {
+ /**
+ * @param mixed $res
+ * @return int
+ */
+ public function numFields( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return ( $res ) ? $res->numfields() : 0;
+
+ return sqlsrv_num_fields( $res );
}
- function fieldName( $res, $n ) {
+ /**
+ * @param mixed $res
+ * @param int $n
+ * @return int
+ */
+ public function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return ( $res ) ? $res->fieldname( $n ) : 0;
+
+ $metadata = sqlsrv_field_metadata( $res );
+ return $metadata[$n]['Name'];
}
/**
* This must be called after nextSequenceVal
- * @return null
+ * @return int|null
*/
- function insertId() {
+ public function insertId() {
return $this->mInsertId;
}
- function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return ( $res ) ? $res->seek( $row ) : false;
+ /**
+ * @param MssqlResultWrapper $res
+ * @param int $row
+ * @return bool
+ */
+ public function dataSeek( $res, $row ) {
+ return $res->seek( $row );
}
- function lastError() {
- if ( $this->mConn ) {
- return $this->getErrors();
+ /**
+ * @return string
+ */
+ public function lastError() {
+ $strRet = '';
+ $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
+ if ( $retErrors != null ) {
+ foreach ( $retErrors as $arrError ) {
+ $strRet .= $this->formatError( $arrError ) . "\n";
+ }
} else {
- return "No database connection";
+ $strRet = "No errors found";
}
+
+ return $strRet;
+ }
+
+ /**
+ * @param array $err
+ * @return string
+ */
+ private function formatError( $err ) {
+ return '[SQLSTATE ' . $err['SQLSTATE'] . '][Error Code ' . $err['code'] . ']' . $err['message'];
}
- function lastErrno() {
+ /**
+ * @return string
+ */
+ public function lastErrno() {
$err = sqlsrv_errors( SQLSRV_ERR_ALL );
- if ( $err[0] ) {
+ if ( $err !== null && isset( $err[0] ) ) {
return $err[0]['code'];
} else {
return 0;
}
}
- function affectedRows() {
+ /**
+ * @return int
+ */
+ public function affectedRows() {
return $this->mAffectedRows;
}
/**
* SELECT wrapper
*
- * @param $table Mixed: array or string, table name(s) (prefix auto-added)
- * @param $vars Mixed: array or string, field name(s) to be retrieved
- * @param $conds Mixed: array or string, condition(s) for WHERE
- * @param $fname String: calling function name (use __METHOD__) for logs/profiling
- * @param array $options associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
- * @param $join_conds Array: Associative array of table join conditions (optional)
- * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return Mixed: database result resource (feed to Database::fetchObject or whatever), or false on failure
+ * @param mixed $table Array or string, table name(s) (prefix auto-added)
+ * @param mixed $vars Array or string, field name(s) to be retrieved
+ * @param mixed $conds Array or string, condition(s) for WHERE
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options Associative array of options (e.g.
+ * array('GROUP BY' => 'page_title')), see Database::makeSelectOptions
+ * code for list of supported stuff
+ * @param array $join_conds Associative array of table join conditions
+ * (optional) (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * @return mixed Database result resource (feed to Database::fetchObject
+ * or whatever), or false on failure
*/
- function select( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() )
- {
+ public function select( $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = array(), $join_conds = array()
+ ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
if ( isset( $options['EXPLAIN'] ) ) {
- sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL ON;" );
- $ret = $this->query( $sql, $fname );
- sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL OFF;" );
+ try {
+ $this->mScrollableCursor = false;
+ $this->mPrepareStatements = false;
+ $this->query( "SET SHOWPLAN_ALL ON" );
+ $ret = $this->query( $sql, $fname );
+ $this->query( "SET SHOWPLAN_ALL OFF" );
+ } catch ( DBQueryError $dqe ) {
+ if ( isset( $options['FOR COUNT'] ) ) {
+ // likely don't have privs for SHOWPLAN, so run a select count instead
+ $this->query( "SET SHOWPLAN_ALL OFF" );
+ unset( $options['EXPLAIN'] );
+ $ret = $this->select(
+ $table,
+ 'COUNT(*) AS EstimateRows',
+ $conds,
+ $fname,
+ $options,
+ $join_conds
+ );
+ } else {
+ // someone actually wanted the query plan instead of an est row count
+ // let them know of the error
+ $this->mScrollableCursor = true;
+ $this->mPrepareStatements = true;
+ throw $dqe;
+ }
+ }
+ $this->mScrollableCursor = true;
+ $this->mPrepareStatements = true;
return $ret;
}
return $this->query( $sql, $fname );
@@ -312,21 +423,70 @@ class DatabaseMssql extends DatabaseBase {
/**
* SELECT wrapper
*
- * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
- * @param $vars Mixed: Array or string, field name(s) to be retrieved
- * @param $conds Mixed: Array or string, condition(s) for WHERE
- * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
- * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
- * @param $join_conds Array: Associative array of table join conditions (optional)
- * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return string, the SQL text
+ * @param mixed $table Array or string, table name(s) (prefix auto-added)
+ * @param mixed $vars Array or string, field name(s) to be retrieved
+ * @param mixed $conds Array or string, condition(s) for WHERE
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of supported stuff
+ * @param array $join_conds Associative array of table join conditions (optional)
+ * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * @return string The SQL text
*/
- function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() ) {
+ public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = array(), $join_conds = array()
+ ) {
if ( isset( $options['EXPLAIN'] ) ) {
unset( $options['EXPLAIN'] );
}
- return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+
+ $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+
+ // try to rewrite aggregations of bit columns (currently MAX and MIN)
+ if ( strpos( $sql, 'MAX(' ) !== false || strpos( $sql, 'MIN(' ) !== false ) {
+ $bitColumns = array();
+ if ( is_array( $table ) ) {
+ foreach ( $table as $t ) {
+ $bitColumns += $this->getBitColumns( $this->tableName( $t ) );
+ }
+ } else {
+ $bitColumns = $this->getBitColumns( $this->tableName( $table ) );
+ }
+
+ foreach ( $bitColumns as $col => $info ) {
+ $replace = array(
+ "MAX({$col})" => "MAX(CAST({$col} AS tinyint))",
+ "MIN({$col})" => "MIN(CAST({$col} AS tinyint))",
+ );
+ $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
+ }
+ }
+
+ return $sql;
+ }
+
+ public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+ $fname = __METHOD__
+ ) {
+ $this->mScrollableCursor = false;
+ try {
+ parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ throw $e;
+ }
+ $this->mScrollableCursor = true;
+ }
+
+ public function delete( $table, $conds, $fname = __METHOD__ ) {
+ $this->mScrollableCursor = false;
+ try {
+ parent::delete( $table, $conds, $fname );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ throw $e;
+ }
+ $this->mScrollableCursor = true;
}
/**
@@ -335,30 +495,45 @@ class DatabaseMssql extends DatabaseBase {
* This is not necessarily an accurate estimate, so use sparingly
* Returns -1 if count cannot be found
* Takes same arguments as Database::select()
+ * @param string $table
+ * @param string $vars
+ * @param string $conds
+ * @param string $fname
+ * @param array $options
* @return int
*/
- function estimateRowCount( $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() ) {
- $options['EXPLAIN'] = true;// http://msdn2.microsoft.com/en-us/library/aa259203.aspx
+ public function estimateRowCount( $table, $vars = '*', $conds = '',
+ $fname = __METHOD__, $options = array()
+ ) {
+ // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
+ $options['EXPLAIN'] = true;
+ $options['FOR COUNT'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
if ( $res ) {
$row = $this->fetchRow( $res );
+
if ( isset( $row['EstimateRows'] ) ) {
$rows = $row['EstimateRows'];
}
}
+
return $rows;
}
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return array|bool|null
*/
- function indexInfo( $table, $index, $fname = __METHOD__ ) {
- # This does not return the same info as MYSQL would, but that's OK because MediaWiki never uses the
- # returned value except to check for the existance of indexes.
+ public function indexInfo( $table, $index, $fname = __METHOD__ ) {
+ # This does not return the same info as MYSQL would, but that's OK
+ # because MediaWiki never uses the returned value except to check for
+ # the existance of indexes.
$sql = "sp_helpindex '" . $table . "'";
$res = $this->query( $sql, $fname );
if ( !$res ) {
@@ -383,6 +558,7 @@ class DatabaseMssql extends DatabaseBase {
}
}
}
+
return empty( $result ) ? false : $result;
}
@@ -401,7 +577,7 @@ class DatabaseMssql extends DatabaseBase {
* @throws DBQueryError
* @return bool
*/
- function insert( $table, $arrToInsert, $fname = __METHOD__, $options = array() ) {
+ public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = array() ) {
# No rows to insert, easy just return now
if ( !count( $arrToInsert ) ) {
return true;
@@ -413,24 +589,39 @@ class DatabaseMssql extends DatabaseBase {
$table = $this->tableName( $table );
- if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) {// Not multi row
- $arrToInsert = array( 0 => $arrToInsert );// make everything multi row compatible
+ if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) { // Not multi row
+ $arrToInsert = array( 0 => $arrToInsert ); // make everything multi row compatible
}
- $allOk = true;
-
// We know the table we're inserting into, get its identity column
$identity = null;
- $tableRaw = preg_replace( '#\[([^\]]*)\]#', '$1', $table ); // strip matching square brackets from table name
- $res = $this->doQuery( "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'" );
- if ( $res && $res->numrows() ) {
+ // strip matching square brackets and the db/schema from table name
+ $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
+ $tableRaw = array_pop( $tableRawArr );
+ $res = $this->doQuery(
+ "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
+ "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
+ );
+ if ( $res && sqlsrv_has_rows( $res ) ) {
// There is an identity for this table.
- $identity = array_pop( $res->fetch( SQLSRV_FETCH_ASSOC ) );
+ $identityArr = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC );
+ $identity = array_pop( $identityArr );
+ }
+ sqlsrv_free_stmt( $res );
+
+ // Determine binary/varbinary fields so we can encode data as a hex string like 0xABCDEF
+ $binaryColumns = $this->getBinaryColumns( $table );
+
+ // INSERT IGNORE is not supported by SQL Server
+ // remove IGNORE from options list and set ignore flag to true
+ if ( in_array( 'IGNORE', $options ) ) {
+ $options = array_diff( $options, array( 'IGNORE' ) );
+ $this->mIgnoreDupKeyErrors = true;
}
- unset( $res );
foreach ( $arrToInsert as $a ) {
- // start out with empty identity column, this is so we can return it as a result of the insert logic
+ // start out with empty identity column, this is so we can return
+ // it as a result of the insert logic
$sqlPre = '';
$sqlPost = '';
$identityClause = '';
@@ -441,152 +632,246 @@ class DatabaseMssql extends DatabaseBase {
foreach ( $a as $k => $v ) {
if ( $k == $identity ) {
if ( !is_null( $v ) ) {
- // there is a value being passed to us, we need to turn on and off inserted identity
+ // there is a value being passed to us,
+ // we need to turn on and off inserted identity
$sqlPre = "SET IDENTITY_INSERT $table ON;";
$sqlPost = ";SET IDENTITY_INSERT $table OFF;";
-
} else {
- // we can't insert NULL into an identity column, so remove the column from the insert.
+ // we can't insert NULL into an identity column,
+ // so remove the column from the insert.
unset( $a[$k] );
}
}
}
- $identityClause = "OUTPUT INSERTED.$identity "; // we want to output an identity column as result
- }
-
- $keys = array_keys( $a );
- // INSERT IGNORE is not supported by SQL Server
- // remove IGNORE from options list and set ignore flag to true
- $ignoreClause = false;
- foreach ( $options as $k => $v ) {
- if ( strtoupper( $v ) == "IGNORE" ) {
- unset( $options[$k] );
- $ignoreClause = true;
- }
+ // we want to output an identity column as result
+ $identityClause = "OUTPUT INSERTED.$identity ";
}
- // translate MySQL INSERT IGNORE to something SQL Server can use
- // example:
- // MySQL: INSERT IGNORE INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
- // MSSQL: IF NOT EXISTS (SELECT * FROM user_groups WHERE ug_user = '1') INSERT INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
- if ( $ignoreClause ) {
- $prival = $a[$keys[0]];
- $sqlPre .= "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival')";
- }
+ $keys = array_keys( $a );
// Build the actual query
$sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
" INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
$first = true;
- foreach ( $a as $value ) {
+ foreach ( $a as $key => $value ) {
+ if ( isset( $binaryColumns[$key] ) ) {
+ $value = new MssqlBlob( $value );
+ }
if ( $first ) {
$first = false;
} else {
$sql .= ',';
}
- if ( is_string( $value ) ) {
- $sql .= $this->addQuotes( $value );
- } elseif ( is_null( $value ) ) {
+ if ( is_null( $value ) ) {
$sql .= 'null';
} elseif ( is_array( $value ) || is_object( $value ) ) {
- if ( is_object( $value ) && strtolower( get_class( $value ) ) == 'blob' ) {
+ if ( is_object( $value ) && $value instanceof Blob ) {
$sql .= $this->addQuotes( $value );
} else {
$sql .= $this->addQuotes( serialize( $value ) );
}
} else {
- $sql .= $value;
+ $sql .= $this->addQuotes( $value );
}
}
$sql .= ')' . $sqlPost;
// Run the query
- $ret = sqlsrv_query( $this->mConn, $sql );
-
- if ( $ret === false ) {
- throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname );
- } elseif ( $ret != null ) {
- // remember number of rows affected
- $this->mAffectedRows = sqlsrv_rows_affected( $ret );
- if ( !is_null( $identity ) ) {
- // then we want to get the identity column value we were assigned and save it off
- $row = sqlsrv_fetch_object( $ret );
+ $this->mScrollableCursor = false;
+ try {
+ $ret = $this->query( $sql );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ $this->mIgnoreDupKeyErrors = false;
+ throw $e;
+ }
+ $this->mScrollableCursor = true;
+
+ if ( !is_null( $identity ) ) {
+ // then we want to get the identity column value we were assigned and save it off
+ $row = $ret->fetchObject();
+ if( is_object( $row ) ){
$this->mInsertId = $row->$identity;
}
- sqlsrv_free_stmt( $ret );
- continue;
}
- $allOk = false;
}
- return $allOk;
+ $this->mIgnoreDupKeyErrors = false;
+ return $ret;
}
/**
* INSERT SELECT wrapper
* $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
- * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
- * $conds may be "*" to copy the whole table
- * srcTable may be an array of tables.
+ * Source items may be literals rather than field names, but strings should
+ * be quoted with Database::addQuotes().
* @param string $destTable
- * @param array|string $srcTable
+ * @param array|string $srcTable May be an array of tables.
* @param array $varMap
- * @param array $conds
+ * @param array $conds May be "*" to copy the whole table.
* @param string $fname
* @param array $insertOptions
* @param array $selectOptions
* @throws DBQueryError
* @return null|ResultWrapper
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array() ) {
- $ret = parent::insertSelect( $destTable, $srcTable, $varMap, $conds, $fname, $insertOptions, $selectOptions );
-
- if ( $ret === false ) {
- throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), /*$sql*/ '', $fname );
- } elseif ( $ret != null ) {
- // remember number of rows affected
- $this->mAffectedRows = sqlsrv_rows_affected( $ret );
- return $ret;
+ public function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
+ $insertOptions = array(), $selectOptions = array()
+ ) {
+ $this->mScrollableCursor = false;
+ try {
+ $ret = parent::insertSelect(
+ $destTable,
+ $srcTable,
+ $varMap,
+ $conds,
+ $fname,
+ $insertOptions,
+ $selectOptions
+ );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ throw $e;
}
- return null;
+ $this->mScrollableCursor = true;
+
+ return $ret;
}
/**
- * Return the next in a sequence, save the value for retrieval via insertId()
- * @return
+ * UPDATE wrapper. Takes a condition array and a SET array.
+ *
+ * @param string $table Name of the table to UPDATE. This will be passed through
+ * DatabaseBase::tableName().
+ *
+ * @param array $values 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 array $conds An array of conditions (WHERE). See
+ * DatabaseBase::select() for the details of the format of
+ * condition arrays. Use '*' to update all rows.
+ *
+ * @param string $fname The function name of the caller (from __METHOD__),
+ * for logging and profiling.
+ *
+ * @param array $options An array of UPDATE options, can be:
+ * - IGNORE: Ignore unique key conflicts
+ * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
+ * @return bool
*/
- function nextSequenceValue( $seqName ) {
- if ( !$this->tableExists( 'sequence_' . $seqName ) ) {
- sqlsrv_query( $this->mConn, "CREATE TABLE [sequence_$seqName] (id INT NOT NULL IDENTITY PRIMARY KEY, junk varchar(10) NULL)" );
+ function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
+ $table = $this->tableName( $table );
+ $binaryColumns = $this->getBinaryColumns( $table );
+
+ $opts = $this->makeUpdateOptions( $options );
+ $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET, $binaryColumns );
+
+ if ( $conds !== array() && $conds !== '*' ) {
+ $sql .= " WHERE " . $this->makeList( $conds, LIST_AND, $binaryColumns );
}
- sqlsrv_query( $this->mConn, "INSERT INTO [sequence_$seqName] (junk) VALUES ('')" );
- $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" );
- $row = sqlsrv_fetch_array( $ret, SQLSRV_FETCH_ASSOC );// KEEP ASSOC THERE, weird weird bug dealing with the return value if you don't
- sqlsrv_free_stmt( $ret );
- $this->mInsertId = $row['id'];
- return $row['id'];
+ $this->mScrollableCursor = false;
+ try {
+ $ret = $this->query( $sql );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ throw $e;
+ }
+ $this->mScrollableCursor = true;
+ return true;
}
/**
- * Return the current value of a sequence. Assumes it has ben nextval'ed in this session.
- * @return
+ * Makes an encoded list of strings from an array
+ * @param array $a Containing the data
+ * @param int $mode Constant
+ * - LIST_COMMA: comma separated, no field names
+ * - LIST_AND: ANDed WHERE clause (without the WHERE). See
+ * the documentation for $conds in DatabaseBase::select().
+ * - LIST_OR: ORed WHERE clause (without the WHERE)
+ * - LIST_SET: comma separated with field names, like a SET clause
+ * - LIST_NAMES: comma separated field names
+ * @param array $binaryColumns Contains a list of column names that are binary types
+ * This is a custom parameter only present for MS SQL.
+ *
+ * @throws MWException|DBUnexpectedError
+ * @return string
*/
- function currentSequenceValue( $seqName ) {
- $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" );
- if ( $ret !== false ) {
- $row = sqlsrv_fetch_array( $ret );
- sqlsrv_free_stmt( $ret );
- return $row['id'];
- } else {
- return $this->nextSequenceValue( $seqName );
+ public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = array() ) {
+ if ( !is_array( $a ) ) {
+ throw new DBUnexpectedError( $this,
+ 'DatabaseBase::makeList called with incorrect parameters' );
}
+
+ $first = true;
+ $list = '';
+
+ foreach ( $a as $field => $value ) {
+ if ( $mode != LIST_NAMES && isset( $binaryColumns[$field] ) ) {
+ if ( is_array( $value ) ) {
+ foreach ( $value as &$v ) {
+ $v = new MssqlBlob( $v );
+ }
+ } else {
+ $value = new MssqlBlob( $value );
+ }
+ }
+
+ if ( !$first ) {
+ if ( $mode == LIST_AND ) {
+ $list .= ' AND ';
+ } elseif ( $mode == LIST_OR ) {
+ $list .= ' OR ';
+ } else {
+ $list .= ',';
+ }
+ } else {
+ $first = false;
+ }
+
+ if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
+ $list .= "($value)";
+ } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
+ $list .= "$value";
+ } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
+ if ( count( $value ) == 0 ) {
+ throw new MWException( __METHOD__ . ": empty input for field $field" );
+ } elseif ( count( $value ) == 1 ) {
+ // Special-case single values, as IN isn't terribly efficient
+ // Don't necessarily assume the single key is 0; we don't
+ // enforce linear numeric ordering on other arrays here.
+ $value = array_values( $value );
+ $list .= $field . " = " . $this->addQuotes( $value[0] );
+ } else {
+ $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+ }
+ } elseif ( $value === null ) {
+ if ( $mode == LIST_AND || $mode == LIST_OR ) {
+ $list .= "$field IS ";
+ } elseif ( $mode == LIST_SET ) {
+ $list .= "$field = ";
+ }
+ $list .= 'NULL';
+ } else {
+ if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
+ $list .= "$field = ";
+ }
+ $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
+ }
+ }
+
+ return $list;
}
- # Returns the size of a text field, or -1 for "unlimited"
- function textFieldSize( $table, $field ) {
+ /**
+ * @param string $table
+ * @param string $field
+ * @return int Returns the size of a text field, or -1 for "unlimited"
+ */
+ public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
@@ -596,40 +881,75 @@ class DatabaseMssql extends DatabaseBase {
if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
$size = $row['CHARACTER_MAXIMUM_LENGTH'];
}
+
return $size;
}
/**
* Construct a LIMIT query with optional offset
* This is used for query pages
- * $sql string SQL query we will append the limit too
- * $limit integer the SQL limit
- * $offset integer the SQL offset (default false)
- * @return mixed|string
+ *
+ * @param string $sql SQL query we will append the limit too
+ * @param int $limit The SQL limit
+ * @param bool|int $offset The SQL offset (default false)
+ * @return array|string
*/
- function limitResult( $sql, $limit, $offset = false ) {
+ public function limitResult( $sql, $limit, $offset = false ) {
if ( $offset === false || $offset == 0 ) {
if ( strpos( $sql, "SELECT" ) === false ) {
return "TOP {$limit} " . $sql;
} else {
- return preg_replace( '/\bSELECT(\s*DISTINCT)?\b/Dsi', 'SELECT$1 TOP ' . $limit, $sql, 1 );
+ return preg_replace( '/\bSELECT(\s+DISTINCT)?\b/Dsi',
+ 'SELECT$1 TOP ' . $limit, $sql, 1 );
}
} else {
- $sql = '
- SELECT * FROM (
- SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 FROM (
- SELECT 1 AS line2, sub1.* FROM (' . $sql . ') AS sub1
- ) as sub2
- ) AS sub3
- WHERE line3 BETWEEN ' . ( $offset + 1 ) . ' AND ' . ( $offset + $limit );
+ // This one is fun, we need to pull out the select list as well as any ORDER BY clause
+ $select = $orderby = array();
+ $s1 = preg_match( '#SELECT\s+(.+?)\s+FROM#Dis', $sql, $select );
+ $s2 = preg_match( '#(ORDER BY\s+.+?)(\s*FOR XML .*)?$#Dis', $sql, $orderby );
+ $overOrder = $postOrder = '';
+ $first = $offset + 1;
+ $last = $offset + $limit;
+ $sub1 = 'sub_' . $this->mSubqueryId;
+ $sub2 = 'sub_' . ( $this->mSubqueryId + 1 );
+ $this->mSubqueryId += 2;
+ if ( !$s1 ) {
+ // wat
+ throw new DBUnexpectedError( $this, "Attempting to LIMIT a non-SELECT query\n" );
+ }
+ if ( !$s2 ) {
+ // no ORDER BY
+ $overOrder = 'ORDER BY (SELECT 1)';
+ } else {
+ if ( !isset( $orderby[2] ) || !$orderby[2] ) {
+ // don't need to strip it out if we're using a FOR XML clause
+ $sql = str_replace( $orderby[1], '', $sql );
+ }
+ $overOrder = $orderby[1];
+ $postOrder = ' ' . $overOrder;
+ }
+ $sql = "SELECT {$select[1]}
+ FROM (
+ SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
+ FROM ({$sql}) {$sub1}
+ ) {$sub2}
+ WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
+
return $sql;
}
}
- // If there is a limit clause, parse it, strip it, and pass the remaining sql through limitResult()
- // with the appropriate parameters. Not the prettiest solution, but better than building a whole new parser.
- // This exists becase there are still too many extensions that don't use dynamic sql generation.
- function LimitToTopN( $sql ) {
+ /**
+ * If there is a limit clause, parse it, strip it, and pass the remaining
+ * SQL through limitResult() with the appropriate parameters. Not the
+ * prettiest solution, but better than building a whole new parser. This
+ * exists becase there are still too many extensions that don't use dynamic
+ * sql generation.
+ *
+ * @param string $sql
+ * @return array|mixed|string
+ */
+ public function LimitToTopN( $sql ) {
// Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
$pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
if ( preg_match( $pattern, $sql, $matches ) ) {
@@ -637,22 +957,20 @@ class DatabaseMssql extends DatabaseBase {
$row_count = $matches[4];
// offset = $matches[3] OR $matches[6]
$offset = $matches[3] or
- $offset = $matches[6] or
- $offset = false;
+ $offset = $matches[6] or
+ $offset = false;
// strip the matching LIMIT clause out
$sql = str_replace( $matches[0], '', $sql );
+
return $this->limitResult( $sql, $row_count, $offset );
}
- return $sql;
- }
- function timestamp( $ts = 0 ) {
- return wfTimestamp( TS_ISO_8601, $ts );
+ return $sql;
}
/**
- * @return string wikitext of a link to the server software's web site
+ * @return string Wikitext of a link to the server software's web site
*/
public function getSoftwareLink() {
return "[{{int:version-db-mssql-url}} MS SQL Server]";
@@ -661,23 +979,40 @@ class DatabaseMssql extends DatabaseBase {
/**
* @return string Version information from the database
*/
- function getServerVersion() {
+ public function getServerVersion() {
$server_info = sqlsrv_server_info( $this->mConn );
$version = 'Error';
if ( isset( $server_info['SQLServerVersion'] ) ) {
$version = $server_info['SQLServerVersion'];
}
+
return $version;
}
- function tableExists( $table, $fname = __METHOD__, $schema = false ) {
- $res = sqlsrv_query( $this->mConn, "SELECT * FROM information_schema.tables
- WHERE table_type='BASE TABLE' AND table_name = '$table'" );
- if ( $res === false ) {
- print "Error in tableExists query: " . $this->getErrors();
+ /**
+ * @param string $table
+ * @param string $fname
+ * @return bool
+ */
+ public function tableExists( $table, $fname = __METHOD__ ) {
+ list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
+
+ if ( $db !== false ) {
+ // remote database
+ wfDebug( "Attempting to call tableExists on a remote table" );
return false;
}
- if ( sqlsrv_fetch( $res ) ) {
+
+ if ( $schema === false ) {
+ global $wgDBmwschema;
+ $schema = $wgDBmwschema;
+ }
+
+ $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_TYPE = 'BASE TABLE'
+ AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
+
+ if ( $res->numRows() ) {
return true;
} else {
return false;
@@ -686,40 +1021,53 @@ class DatabaseMssql extends DatabaseBase {
/**
* Query whether a given column exists in the mediawiki schema
+ * @param string $table
+ * @param string $field
+ * @param string $fname
* @return bool
*/
- function fieldExists( $table, $field, $fname = __METHOD__ ) {
- $table = $this->tableName( $table );
- $res = sqlsrv_query( $this->mConn, "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.Columns
- WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
- if ( $res === false ) {
- print "Error in fieldExists query: " . $this->getErrors();
+ public function fieldExists( $table, $field, $fname = __METHOD__ ) {
+ list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
+
+ if ( $db !== false ) {
+ // remote database
+ wfDebug( "Attempting to call fieldExists on a remote table" );
return false;
}
- if ( sqlsrv_fetch( $res ) ) {
+
+ $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+
+ if ( $res->numRows() ) {
return true;
} else {
return false;
}
}
- function fieldInfo( $table, $field ) {
- $table = $this->tableName( $table );
- $res = sqlsrv_query( $this->mConn, "SELECT * FROM INFORMATION_SCHEMA.Columns
- WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
- if ( $res === false ) {
- print "Error in fieldInfo query: " . $this->getErrors();
+ public function fieldInfo( $table, $field ) {
+ list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
+
+ if ( $db !== false ) {
+ // remote database
+ wfDebug( "Attempting to call fieldInfo on a remote table" );
return false;
}
- $meta = $this->fetchRow( $res );
+
+ $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+
+ $meta = $res->fetchRow();
if ( $meta ) {
return new MssqlField( $meta );
}
+
return false;
}
/**
* Begin a transaction, committing any previously open transaction
+ * @param string $fname
*/
protected function doBegin( $fname = __METHOD__ ) {
sqlsrv_begin_transaction( $this->mConn );
@@ -728,6 +1076,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* End a transaction
+ * @param string $fname
*/
protected function doCommit( $fname = __METHOD__ ) {
sqlsrv_commit( $this->mConn );
@@ -737,6 +1086,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Rollback a transaction.
* No-op on non-transactional databases.
+ * @param string $fname
*/
protected function doRollback( $fname = __METHOD__ ) {
sqlsrv_rollback( $this->mConn );
@@ -747,7 +1097,7 @@ class DatabaseMssql extends DatabaseBase {
* Escapes a identifier for use inm SQL.
* Throws an exception if it is invalid.
* Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
- * @param $identifier
+ * @param string $identifier
* @throws MWException
* @return string
*/
@@ -758,153 +1108,82 @@ class DatabaseMssql extends DatabaseBase {
if ( strlen( $identifier ) > 128 ) {
throw new MWException( "The identifier '$identifier' is too long (max. 128)" );
}
- if ( ( strpos( $identifier, '[' ) !== false ) || ( strpos( $identifier, ']' ) !== false ) ) {
- // It may be allowed if you quoted with double quotation marks, but that would break if QUOTED_IDENTIFIER is OFF
- throw new MWException( "You can't use square brackers in the identifier '$identifier'" );
+ if ( ( strpos( $identifier, '[' ) !== false )
+ || ( strpos( $identifier, ']' ) !== false )
+ ) {
+ // It may be allowed if you quoted with double quotation marks, but
+ // that would break if QUOTED_IDENTIFIER is OFF
+ throw new MWException( "Square brackets are not allowed in '$identifier'" );
}
+
return "[$identifier]";
}
/**
- * Initial setup.
- * Precondition: This object is connected as the superuser.
- * Creates the database, schema, user and login.
+ * @param string $s
+ * @return string
*/
- function initial_setup( $dbName, $newUser, $loginPassword ) {
- $dbName = $this->escapeIdentifier( $dbName );
-
- // It is not clear what can be used as a login,
- // From http://msdn.microsoft.com/en-us/library/ms173463.aspx
- // a sysname may be the same as an identifier.
- $newUser = $this->escapeIdentifier( $newUser );
- $loginPassword = $this->addQuotes( $loginPassword );
-
- $this->doQuery( "CREATE DATABASE $dbName;" );
- $this->doQuery( "USE $dbName;" );
- $this->doQuery( "CREATE SCHEMA $dbName;" );
- $this->doQuery( "
- CREATE
- LOGIN $newUser
- WITH
- PASSWORD=$loginPassword
- ;
- " );
- $this->doQuery( "
- CREATE
- USER $newUser
- FOR
- LOGIN $newUser
- WITH
- DEFAULT_SCHEMA=$dbName
- ;
- " );
- $this->doQuery( "
- GRANT
- BACKUP DATABASE,
- BACKUP LOG,
- CREATE DEFAULT,
- CREATE FUNCTION,
- CREATE PROCEDURE,
- CREATE RULE,
- CREATE TABLE,
- CREATE VIEW,
- CREATE FULLTEXT CATALOG
- ON
- DATABASE::$dbName
- TO $newUser
- ;
- " );
- $this->doQuery( "
- GRANT
- CONTROL
- ON
- SCHEMA::$dbName
- TO $newUser
- ;
- " );
- }
-
- function encodeBlob( $b ) {
- // we can't have zero's and such, this is a simple encoding to make sure we don't barf
- return base64_encode( $b );
- }
-
- function decodeBlob( $b ) {
- // we can't have zero's and such, this is a simple encoding to make sure we don't barf
- return base64_decode( $b );
+ public function strencode( $s ) { # Should not be called by us
+ return str_replace( "'", "''", $s );
}
/**
- * @private
+ * @param string $s
* @return string
*/
- function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
- $ret = array();
- $retJOIN = array();
- $use_index_safe = is_array( $use_index ) ? $use_index : array();
- $join_conds_safe = is_array( $join_conds ) ? $join_conds : array();
- foreach ( $tables as $table ) {
- // Is there a JOIN and INDEX clause for this table?
- if ( isset( $join_conds_safe[$table] ) && isset( $use_index_safe[$table] ) ) {
- $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
- $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
- $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')';
- $retJOIN[] = $tableClause;
- // Is there an INDEX clause?
- } elseif ( isset( $use_index_safe[$table] ) ) {
- $tableClause = $this->tableName( $table );
- $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
- $ret[] = $tableClause;
- // Is there a JOIN clause?
- } elseif ( isset( $join_conds_safe[$table] ) ) {
- $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
- $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')';
- $retJOIN[] = $tableClause;
- } else {
- $tableClause = $this->tableName( $table );
- $ret[] = $tableClause;
- }
- }
- // We can't separate explicit JOIN clauses with ',', use ' ' for those
- $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
- $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
- // Compile our final table clause
- return implode( ' ', array( $straightJoins, $otherJoins ) );
- }
-
- function strencode( $s ) { # Should not be called by us
- return str_replace( "'", "''", $s );
- }
-
- function addQuotes( $s ) {
- if ( $s instanceof Blob ) {
- return "'" . $s->fetch( $s ) . "'";
+ public function addQuotes( $s ) {
+ if ( $s instanceof MssqlBlob ) {
+ return $s->fetch();
+ } elseif ( $s instanceof Blob ) {
+ // this shouldn't really ever be called, but it's here if needed
+ // (and will quite possibly make the SQL error out)
+ $blob = new MssqlBlob( $s->fetch() );
+ return $blob->fetch();
} else {
+ if ( is_bool( $s ) ) {
+ $s = $s ? 1 : 0;
+ }
return parent::addQuotes( $s );
}
}
+ /**
+ * @param string $s
+ * @return string
+ */
public function addIdentifierQuotes( $s ) {
// http://msdn.microsoft.com/en-us/library/aa223962.aspx
return '[' . $s . ']';
}
+ /**
+ * @param string $name
+ * @return bool
+ */
public function isQuotedIdentifier( $name ) {
- return $name[0] == '[' && substr( $name, -1, 1 ) == ']';
+ return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
}
- function selectDB( $db ) {
- return ( $this->query( "SET DATABASE $db" ) !== false );
+ /**
+ * @param string $db
+ * @return bool
+ */
+ public function selectDB( $db ) {
+ try {
+ $this->mDBname = $db;
+ $this->query( "USE $db" );
+ return true;
+ } catch ( Exception $e ) {
+ return false;
+ }
}
/**
- * @private
- *
- * @param array $options an associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return Array
+ * @param array $options An associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
+ * @return array
*/
- function makeSelectOptions( $options ) {
+ public function makeSelectOptions( $options ) {
$tailOpts = '';
$startOpts = '';
@@ -919,10 +1198,15 @@ class DatabaseMssql extends DatabaseBase {
$tailOpts .= $this->makeOrderBy( $options );
- if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) {
+ if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
$startOpts .= 'DISTINCT';
}
+ if ( isset( $noKeyOptions['FOR XML'] ) ) {
+ // used in group concat field emulation
+ $tailOpts .= " FOR XML PATH('')";
+ }
+
// we want this to be compatible with the output of parent::makeSelectOptions()
return array( $startOpts, '', $tailOpts, '' );
}
@@ -931,27 +1215,165 @@ class DatabaseMssql extends DatabaseBase {
* Get the type of the DBMS, as it appears in $wgDBtype.
* @return string
*/
- function getType() {
+ public function getType() {
return 'mssql';
}
- function buildConcat( $stringList ) {
+ /**
+ * @param array $stringList
+ * @return string
+ */
+ public function buildConcat( $stringList ) {
return implode( ' + ', $stringList );
}
+ /**
+ * Build a GROUP_CONCAT or equivalent statement for a query.
+ * MS SQL doesn't have GROUP_CONCAT so we emulate it with other stuff (and boy is it nasty)
+ *
+ * This is useful for combining a field for several rows into a single string.
+ * NULL values will not appear in the output, duplicated values will appear,
+ * and the resulting delimiter-separated values have no defined sort order.
+ * Code using the results may need to use the PHP unique() or sort() methods.
+ *
+ * @param string $delim Glue to bind the results together
+ * @param string|array $table Table name
+ * @param string $field Field name
+ * @param string|array $conds Conditions
+ * @param string|array $join_conds Join conditions
+ * @return string SQL text
+ * @since 1.23
+ */
+ public function buildGroupConcatField( $delim, $table, $field, $conds = '',
+ $join_conds = array()
+ ) {
+ $gcsq = 'gcsq_' . $this->mSubqueryId;
+ $this->mSubqueryId++;
+
+ $delimLen = strlen( $delim );
+ $fld = "{$field} + {$this->addQuotes( $delim )}";
+ $sql = "(SELECT LEFT({$field}, LEN({$field}) - {$delimLen}) FROM ("
+ . $this->selectSQLText( $table, $fld, $conds, null, array( 'FOR XML' ), $join_conds )
+ . ") {$gcsq} ({$field}))";
+
+ return $sql;
+ }
+
+ /**
+ * @return string
+ */
public function getSearchEngine() {
return "SearchMssql";
}
/**
- * Since MSSQL doesn't recognize the infinity keyword, set date manually.
- * @todo Remove magic date
+ * Returns an associative array for fields that are of type varbinary, binary, or image
+ * $table can be either a raw table name or passed through tableName() first
+ * @param string $table
+ * @return array
+ */
+ private function getBinaryColumns( $table ) {
+ $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
+ $tableRaw = array_pop( $tableRawArr );
+
+ if ( $this->mBinaryColumnCache === null ) {
+ $this->populateColumnCaches();
+ }
+
+ return isset( $this->mBinaryColumnCache[$tableRaw] )
+ ? $this->mBinaryColumnCache[$tableRaw]
+ : array();
+ }
+
+ /**
+ * @param string $table
+ * @return array
+ */
+ private function getBitColumns( $table ) {
+ $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
+ $tableRaw = array_pop( $tableRawArr );
+
+ if ( $this->mBitColumnCache === null ) {
+ $this->populateColumnCaches();
+ }
+
+ return isset( $this->mBitColumnCache[$tableRaw] )
+ ? $this->mBitColumnCache[$tableRaw]
+ : array();
+ }
+
+ private function populateColumnCaches() {
+ $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
+ array(
+ 'TABLE_CATALOG' => $this->mDBname,
+ 'TABLE_SCHEMA' => $this->mSchema,
+ 'DATA_TYPE' => array( 'varbinary', 'binary', 'image', 'bit' )
+ ) );
+
+ $this->mBinaryColumnCache = array();
+ $this->mBitColumnCache = array();
+ foreach ( $res as $row ) {
+ if ( $row->DATA_TYPE == 'bit' ) {
+ $this->mBitColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
+ } else {
+ $this->mBinaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
+ }
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param string $format
+ * @return string
+ */
+ function tableName( $name, $format = 'quoted' ) {
+ # Replace reserved words with better ones
+ switch ( $name ) {
+ case 'user':
+ return $this->realTableName( 'mwuser', $format );
+ default:
+ return $this->realTableName( $name, $format );
+ }
+ }
+
+ /**
+ * call this instead of tableName() in the updater when renaming tables
+ * @param string $name
+ * @param string $format One of quoted, raw, or split
* @return string
*/
- public function getInfinity() {
- return '3000-01-31 00:00:00.000';
+ function realTableName( $name, $format = 'quoted' ) {
+ $table = parent::tableName( $name, $format );
+ if ( $format == 'split' ) {
+ // Used internally, we want the schema split off from the table name and returned
+ // as a list with 3 elements (database, schema, table)
+ $table = explode( '.', $table );
+ while ( count( $table ) < 3 ) {
+ array_unshift( $table, false );
+ }
+ }
+ return $table;
}
+ /**
+ * Called in the installer and updater.
+ * Probably doesn't need to be called anywhere else in the codebase.
+ * @param bool|null $value
+ * @return bool|null
+ */
+ public function prepareStatements( $value = null ) {
+ return wfSetVar( $this->mPrepareStatements, $value );
+ }
+
+ /**
+ * Called in the installer and updater.
+ * Probably doesn't need to be called anywhere else in the codebase.
+ * @param bool|null $value
+ * @return bool|null
+ */
+ public function scrollableCursor( $value = null ) {
+ return wfSetVar( $this->mScrollableCursor, $value );
+ }
} // end DatabaseMssql class
/**
@@ -960,10 +1382,11 @@ class DatabaseMssql extends DatabaseBase {
* @ingroup Database
*/
class MssqlField implements Field {
- private $name, $tablename, $default, $max_length, $nullable, $type;
+ private $name, $tableName, $default, $max_length, $nullable, $type;
+
function __construct( $info ) {
$this->name = $info['COLUMN_NAME'];
- $this->tablename = $info['TABLE_NAME'];
+ $this->tableName = $info['TABLE_NAME'];
$this->default = $info['COLUMN_DEFAULT'];
$this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
$this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
@@ -995,208 +1418,105 @@ class MssqlField implements Field {
}
}
-/**
- * The MSSQL PHP driver doesn't support sqlsrv_num_rows, so we recall all rows into an array and maintain our
- * own cursor index into that array...This is similar to the way the Oracle driver handles this same issue
- *
- * @ingroup Database
- */
-class MssqlResult {
-
- public function __construct( $queryresult = false ) {
- $this->mCursor = 0;
- $this->mRows = array();
- $this->mNumFields = sqlsrv_num_fields( $queryresult );
- $this->mFieldMeta = sqlsrv_field_metadata( $queryresult );
+class MssqlBlob extends Blob {
+ public function __construct( $data ) {
+ if ( $data instanceof MssqlBlob ) {
+ return $data;
+ } elseif ( $data instanceof Blob ) {
+ $this->mData = $data->fetch();
+ } elseif ( is_array( $data ) && is_object( $data ) ) {
+ $this->mData = serialize( $data );
+ } else {
+ $this->mData = $data;
+ }
+ }
- $rows = sqlsrv_fetch_array( $queryresult, SQLSRV_FETCH_ASSOC );
+ /**
+ * Returns an unquoted hex representation of a binary string
+ * for insertion into varbinary-type fields
+ * @return string
+ */
+ public function fetch() {
+ if ( $this->mData === null ) {
+ return 'null';
+ }
- foreach ( $rows as $row ) {
- if ( $row !== null ) {
- foreach ( $row as $k => $v ) {
- if ( is_object( $v ) && method_exists( $v, 'format' ) ) {// DateTime Object
- $row[$k] = $v->format( "Y-m-d\TH:i:s\Z" );
- }
- }
- $this->mRows[] = $row;// read results into memory, cursors are not supported
- }
+ $ret = '0x';
+ $dataLength = strlen( $this->mData );
+ for ( $i = 0; $i < $dataLength; $i++ ) {
+ $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
}
- $this->mRowCount = count( $this->mRows );
- sqlsrv_free_stmt( $queryresult );
+
+ return $ret;
}
+}
- private function array_to_obj( $array, &$obj ) {
- foreach ( $array as $key => $value ) {
- if ( is_array( $value ) ) {
- $obj->$key = new stdClass();
- $this->array_to_obj( $value, $obj->$key );
- } else {
- if ( !empty( $key ) ) {
- $obj->$key = $value;
- }
- }
+class MssqlResultWrapper extends ResultWrapper {
+ private $mSeekTo = null;
+
+ /**
+ * @return stdClass|bool
+ */
+ public function fetchObject() {
+ $res = $this->result;
+
+ if ( $this->mSeekTo !== null ) {
+ $result = sqlsrv_fetch_object( $res, 'stdClass', array(),
+ SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+ $this->mSeekTo = null;
+ } else {
+ $result = sqlsrv_fetch_object( $res );
}
- return $obj;
- }
- public function fetch( $mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass' ) {
- if ( $this->mCursor >= $this->mRowCount || $this->mRowCount == 0 ) {
+ // MediaWiki expects us to return boolean false when there are no more rows instead of null
+ if ( $result === null ) {
return false;
}
- $arrNum = array();
- if ( $mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH ) {
- foreach ( $this->mRows[$this->mCursor] as $value ) {
- $arrNum[] = $value;
- }
- }
- switch ( $mode ) {
- case SQLSRV_FETCH_ASSOC:
- $ret = $this->mRows[$this->mCursor];
- break;
- case SQLSRV_FETCH_NUMERIC:
- $ret = $arrNum;
- break;
- case 'OBJECT':
- $o = new $object_class;
- $ret = $this->array_to_obj( $this->mRows[$this->mCursor], $o );
- break;
- case SQLSRV_FETCH_BOTH:
- default:
- $ret = $this->mRows[$this->mCursor] + $arrNum;
- break;
- }
- $this->mCursor++;
- return $ret;
+ return $result;
}
- public function get( $pos, $fld ) {
- return $this->mRows[$pos][$fld];
- }
+ /**
+ * @return array|bool
+ */
+ public function fetchRow() {
+ $res = $this->result;
- public function numrows() {
- return $this->mRowCount;
- }
+ if ( $this->mSeekTo !== null ) {
+ $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
+ SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+ $this->mSeekTo = null;
+ } else {
+ $result = sqlsrv_fetch_array( $res );
+ }
- public function seek( $iRow ) {
- $this->mCursor = min( $iRow, $this->mRowCount );
- }
+ // MediaWiki expects us to return boolean false when there are no more rows instead of null
+ if ( $result === null ) {
+ return false;
+ }
- public function numfields() {
- return $this->mNumFields;
+ return $result;
}
- public function fieldname( $nr ) {
- $arrKeys = array_keys( $this->mRows[0] );
- return $arrKeys[$nr];
- }
+ /**
+ * @param int $row
+ * @return bool
+ */
+ public function seek( $row ) {
+ $res = $this->result;
- public function fieldtype( $nr ) {
- $i = 0;
- $intType = -1;
- foreach ( $this->mFieldMeta as $meta ) {
- if ( $nr == $i ) {
- $intType = $meta['Type'];
- break;
- }
- $i++;
- }
- // http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table
- switch ( $intType ) {
- case SQLSRV_SQLTYPE_BIGINT:
- $strType = 'bigint';
- break;
- case SQLSRV_SQLTYPE_BINARY:
- $strType = 'binary';
- break;
- case SQLSRV_SQLTYPE_BIT:
- $strType = 'bit';
- break;
- case SQLSRV_SQLTYPE_CHAR:
- $strType = 'char';
- break;
- case SQLSRV_SQLTYPE_DATETIME:
- $strType = 'datetime';
- break;
- case SQLSRV_SQLTYPE_DECIMAL: // ($precision, $scale)
- $strType = 'decimal';
- break;
- case SQLSRV_SQLTYPE_FLOAT:
- $strType = 'float';
- break;
- case SQLSRV_SQLTYPE_IMAGE:
- $strType = 'image';
- break;
- case SQLSRV_SQLTYPE_INT:
- $strType = 'int';
- break;
- case SQLSRV_SQLTYPE_MONEY:
- $strType = 'money';
- break;
- case SQLSRV_SQLTYPE_NCHAR: // ($charCount):
- $strType = 'nchar';
- break;
- case SQLSRV_SQLTYPE_NUMERIC: // ($precision, $scale):
- $strType = 'numeric';
- break;
- case SQLSRV_SQLTYPE_NVARCHAR: // ($charCount)
- $strType = 'nvarchar';
- break;
- // case SQLSRV_SQLTYPE_NVARCHAR('max'):
- // $strType = 'nvarchar(MAX)';
- // break;
- case SQLSRV_SQLTYPE_NTEXT:
- $strType = 'ntext';
- break;
- case SQLSRV_SQLTYPE_REAL:
- $strType = 'real';
- break;
- case SQLSRV_SQLTYPE_SMALLDATETIME:
- $strType = 'smalldatetime';
- break;
- case SQLSRV_SQLTYPE_SMALLINT:
- $strType = 'smallint';
- break;
- case SQLSRV_SQLTYPE_SMALLMONEY:
- $strType = 'smallmoney';
- break;
- case SQLSRV_SQLTYPE_TEXT:
- $strType = 'text';
- break;
- case SQLSRV_SQLTYPE_TIMESTAMP:
- $strType = 'timestamp';
- break;
- case SQLSRV_SQLTYPE_TINYINT:
- $strType = 'tinyint';
- break;
- case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER:
- $strType = 'uniqueidentifier';
- break;
- case SQLSRV_SQLTYPE_UDT:
- $strType = 'UDT';
- break;
- case SQLSRV_SQLTYPE_VARBINARY: // ($byteCount)
- $strType = 'varbinary';
- break;
- // case SQLSRV_SQLTYPE_VARBINARY('max'):
- // $strType = 'varbinary(MAX)';
- // break;
- case SQLSRV_SQLTYPE_VARCHAR: // ($charCount)
- $strType = 'varchar';
- break;
- // case SQLSRV_SQLTYPE_VARCHAR('max'):
- // $strType = 'varchar(MAX)';
- // break;
- case SQLSRV_SQLTYPE_XML:
- $strType = 'xml';
- break;
- default:
- $strType = $intType;
+ // check bounds
+ $numRows = $this->db->numRows( $res );
+ $row = intval( $row );
+
+ if ( $numRows === 0 ) {
+ return false;
+ } elseif ( $row < 0 || $row > $numRows - 1 ) {
+ return false;
}
- return $strType;
- }
- public function free() {
- unset( $this->mRows );
+ // Unlike MySQL, the seek actually happens on the next access
+ $this->mSeekTo = $row;
+ return true;
}
}
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index 956bb694..dc4a67df 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -28,10 +28,9 @@
* @see Database
*/
class DatabaseMysql extends DatabaseMysqlBase {
-
/**
- * @param $sql string
- * @return resource
+ * @param string $sql
+ * @return resource False on error
*/
protected function doQuery( $sql ) {
if ( $this->bufferResults() ) {
@@ -39,14 +38,23 @@ class DatabaseMysql extends DatabaseMysqlBase {
} else {
$ret = mysql_unbuffered_query( $sql, $this->mConn );
}
+
return $ret;
}
+ /**
+ * @param string $realServer
+ * @return bool|resource MySQL Database connection or false on failure to connect
+ * @throws DBConnectionError
+ */
protected function mysqlConnect( $realServer ) {
# Fail now
# Otherwise we get a suppressed fatal error, which is very hard to track down
if ( !extension_loaded( 'mysql' ) ) {
- throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
+ throw new DBConnectionError(
+ $this,
+ "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n"
+ );
}
$connFlags = 0;
@@ -81,6 +89,18 @@ class DatabaseMysql extends DatabaseMysqlBase {
}
/**
+ * @param string $charset
+ * @return bool
+ */
+ protected function mysqlSetCharset( $charset ) {
+ if ( function_exists( 'mysql_set_charset' ) ) {
+ return mysql_set_charset( $charset, $this->mConn );
+ } else {
+ return $this->query( 'SET NAMES ' . $charset, __METHOD__ );
+ }
+ }
+
+ /**
* @return bool
*/
protected function closeConnection() {
@@ -113,11 +133,12 @@ class DatabaseMysql extends DatabaseMysqlBase {
}
/**
- * @param $db
+ * @param string $db
* @return bool
*/
function selectDB( $db ) {
$this->mDBname = $db;
+
return mysql_select_db( $db, $this->mConn );
}
@@ -156,6 +177,10 @@ class DatabaseMysql extends DatabaseMysqlBase {
return mysql_field_name( $res, $n );
}
+ protected function mysqlFieldType( $res, $n ) {
+ return mysql_field_type( $res, $n );
+ }
+
protected function mysqlDataSeek( $res, $row ) {
return mysql_data_seek( $res, $row );
}
diff --git a/includes/db/DatabaseMysqlBase.php b/includes/db/DatabaseMysqlBase.php
index 8f12b92d..ba0f39ff 100644
--- a/includes/db/DatabaseMysqlBase.php
+++ b/includes/db/DatabaseMysqlBase.php
@@ -33,6 +33,11 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/** @var MysqlMasterPos */
protected $lastKnownSlavePos;
+ /** @var null|int */
+ protected $mFakeSlaveLag = null;
+
+ protected $mFakeMaster = false;
+
/**
* @return string
*/
@@ -41,15 +46,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @param $server string
- * @param $user string
- * @param $password string
- * @param $dbName string
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws Exception|DBConnectionError
* @return bool
- * @throws DBConnectionError
*/
function open( $server, $user, $password, $dbName ) {
- global $wgAllDBsAreLocalhost, $wgDBmysql5, $wgSQLMode;
+ global $wgAllDBsAreLocalhost, $wgSQLMode;
wfProfileIn( __METHOD__ );
# Debugging hack -- fake cluster
@@ -76,6 +81,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
} catch ( Exception $ex ) {
wfProfileOut( "dbconnect-$server" );
wfProfileOut( __METHOD__ );
+ $this->restoreErrorHandler();
throw $ex;
}
$error = $this->restoreErrorHandler();
@@ -87,13 +93,14 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
if ( !$error ) {
$error = $this->lastError();
}
- wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
+ wfLogDBError( "Error connecting to {$this->mServer}: $error" );
wfDebug( "DB connection error\n" .
"Server: $server, User: $user, Password: " .
substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
wfProfileOut( __METHOD__ );
- return $this->reportConnectionError( $error );
+
+ $this->reportConnectionError( $error );
}
if ( $dbName != '' ) {
@@ -101,44 +108,74 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$success = $this->selectDB( $dbName );
wfRestoreWarnings();
if ( !$success ) {
- wfLogDBError( "Error selecting database $dbName on server {$this->mServer}\n" );
+ wfLogDBError( "Error selecting database $dbName on server {$this->mServer}" );
wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
"from client host " . wfHostname() . "\n" );
wfProfileOut( __METHOD__ );
- return $this->reportConnectionError( "Error selecting database $dbName" );
+
+ $this->reportConnectionError( "Error selecting database $dbName" );
}
}
- // Tell the server we're communicating with it in UTF-8.
- // This may engage various charset conversions.
- if ( $wgDBmysql5 ) {
- $this->query( 'SET NAMES utf8', __METHOD__ );
- } else {
- $this->query( 'SET NAMES binary', __METHOD__ );
+ // Tell the server what we're communicating with
+ if ( !$this->connectInitCharset() ) {
+ $this->reportConnectionError( "Error setting character set" );
}
+
// Set SQL mode, default is turning them all off, can be overridden or skipped with null
if ( is_string( $wgSQLMode ) ) {
$mode = $this->addQuotes( $wgSQLMode );
- $this->query( "SET sql_mode = $mode", __METHOD__ );
+ // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
+ $success = $this->doQuery( "SET sql_mode = $mode", __METHOD__ );
+ if ( !$success ) {
+ wfLogDBError( "Error setting sql_mode to $mode on server {$this->mServer}" );
+ wfProfileOut( __METHOD__ );
+ $this->reportConnectionError( "Error setting sql_mode to $mode" );
+ }
}
$this->mOpened = true;
wfProfileOut( __METHOD__ );
+
return true;
}
/**
+ * Set the character set information right after connection
+ * @return bool
+ */
+ protected function connectInitCharset() {
+ global $wgDBmysql5;
+
+ if ( $wgDBmysql5 ) {
+ // Tell the server we're communicating with it in UTF-8.
+ // This may engage various charset conversions.
+ return $this->mysqlSetCharset( 'utf8' );
+ } else {
+ return $this->mysqlSetCharset( 'binary' );
+ }
+ }
+
+ /**
* Open a connection to a MySQL server
*
- * @param $realServer string
+ * @param string $realServer
* @return mixed Raw connection
* @throws DBConnectionError
*/
abstract protected function mysqlConnect( $realServer );
/**
- * @param $res ResultWrapper
+ * Set the character set of the MySQL link
+ *
+ * @param string $charset
+ * @return bool
+ */
+ abstract protected function mysqlSetCharset( $charset );
+
+ /**
+ * @param ResultWrapper|resource $res
* @throws DBUnexpectedError
*/
function freeResult( $res ) {
@@ -156,14 +193,14 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* Free result memory
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return bool
*/
abstract protected function mysqlFreeResult( $res );
/**
- * @param $res ResultWrapper
- * @return object|bool
+ * @param ResultWrapper|resource $res
+ * @return stdClass|bool
* @throws DBUnexpectedError
*/
function fetchObject( $res ) {
@@ -180,21 +217,25 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
// these are the only errors mysql_fetch_object can cause.
// See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
if ( $errno == 2000 || $errno == 2013 ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() )
+ );
}
+
return $row;
}
/**
* Fetch a result row as an object
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return stdClass
*/
abstract protected function mysqlFetchObject( $res );
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper|resource $res
* @return array|bool
* @throws DBUnexpectedError
*/
@@ -212,22 +253,26 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
// these are the only errors mysql_fetch_array can cause.
// See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
if ( $errno == 2000 || $errno == 2013 ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() )
+ );
}
+
return $row;
}
/**
* Fetch a result row as an associative and numeric array
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return array
*/
abstract protected function mysqlFetchArray( $res );
/**
* @throws DBUnexpectedError
- * @param $res ResultWrapper
+ * @param ResultWrapper|resource $res
* @return int
*/
function numRows( $res ) {
@@ -237,6 +282,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
wfSuppressWarnings();
$n = $this->mysqlNumRows( $res );
wfRestoreWarnings();
+
// Unfortunately, mysql_num_rows does not reset the last errno.
// We are not checking for any errors here, since
// these are no errors mysql_num_rows can cause.
@@ -248,68 +294,94 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* Get number of rows in result
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return int
*/
abstract protected function mysqlNumRows( $res );
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper|resource $res
* @return int
*/
function numFields( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return $this->mysqlNumFields( $res );
}
/**
* Get number of fields in result
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return int
*/
abstract protected function mysqlNumFields( $res );
/**
- * @param $res ResultWrapper
- * @param $n string
+ * @param ResultWrapper|resource $res
+ * @param int $n
* @return string
*/
function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return $this->mysqlFieldName( $res, $n );
}
/**
* Get the name of the specified field in a result
*
- * @param $res Raw result
- * @param $n int
+ * @param ResultWrapper|resource $res
+ * @param int $n
* @return string
*/
abstract protected function mysqlFieldName( $res, $n );
/**
- * @param $res ResultWrapper
- * @param $row
+ * mysql_field_type() wrapper
+ * @param ResultWrapper|resource $res
+ * @param int $n
+ * @return string
+ */
+ public function fieldType( $res, $n ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+
+ return $this->mysqlFieldType( $res, $n );
+ }
+
+ /**
+ * Get the type of the specified field in a result
+ *
+ * @param ResultWrapper|resource $res
+ * @param int $n
+ * @return string
+ */
+ abstract protected function mysqlFieldType( $res, $n );
+
+ /**
+ * @param ResultWrapper|resource $res
+ * @param int $row
* @return bool
*/
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return $this->mysqlDataSeek( $res, $row );
}
/**
* Move internal result pointer
*
- * @param $res Raw result
- * @param $row int
+ * @param ResultWrapper|resource $res
+ * @param int $row
* @return bool
*/
abstract protected function mysqlDataSeek( $res, $row );
@@ -332,22 +404,23 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
if ( $error ) {
$error .= ' (' . $this->mServer . ')';
}
+
return $error;
}
/**
* Returns the text of the error message from previous MySQL operation
*
- * @param $conn Raw connection
+ * @param resource $conn Raw connection
* @return string
*/
abstract protected function mysqlError( $conn = null );
/**
- * @param $table string
- * @param $uniqueIndexes
- * @param $rows array
- * @param $fname string
+ * @param string $table
+ * @param array $uniqueIndexes
+ * @param array $rows
+ * @param string $fname
* @return ResultWrapper
*/
function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
@@ -359,14 +432,16 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* Returns estimated count, based on EXPLAIN output
* Takes same arguments as Database::select()
*
- * @param $table string|array
- * @param $vars string|array
- * @param $conds string|array
- * @param $fname string
- * @param $options string|array
- * @return int
+ * @param string|array $table
+ * @param string|array $vars
+ * @param string|array $conds
+ * @param string $fname
+ * @param string|array $options
+ * @return bool|int
*/
- public function estimateRowCount( $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() ) {
+ public function estimateRowCount( $table, $vars = '*', $conds = '',
+ $fname = __METHOD__, $options = array()
+ ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
if ( $res === false ) {
@@ -380,12 +455,13 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
foreach ( $res as $plan ) {
$rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
}
+
return $rows;
}
/**
- * @param $table string
- * @param $field string
+ * @param string $table
+ * @param string $field
* @return bool|MySQLField
*/
function fieldInfo( $table, $field ) {
@@ -401,14 +477,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
return new MySQLField( $meta );
}
}
+
return false;
}
/**
* Get column information from a result
*
- * @param $res Raw result
- * @param $n int
+ * @param resource $res Raw result
+ * @param int $n
* @return stdClass
*/
abstract protected function mysqlFetchField( $res, $n );
@@ -417,9 +494,9 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* Get information about an index into an object
* Returns false if the index does not exist
*
- * @param $table string
- * @param $index string
- * @param $fname string
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool|array|null False or null on failure
*/
function indexInfo( $table, $index, $fname = __METHOD__ ) {
@@ -443,12 +520,12 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$result[] = $row;
}
}
+
return empty( $result ) ? false : $result;
}
/**
- * @param $s string
- *
+ * @param string $s
* @return string
*/
function strencode( $s ) {
@@ -458,24 +535,24 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->ping();
$sQuoted = $this->mysqlRealEscapeString( $s );
}
+
return $sQuoted;
}
/**
* MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
*
- * @param $s string
- *
+ * @param string $s
* @return string
*/
public function addIdentifierQuotes( $s ) {
// Characters in the range \u0001-\uFFFF are valid in a quoted identifier
// Remove NUL bytes and escape backticks by doubling
- return '`' . str_replace( array( "\0", '`' ), array( '', '``' ), $s ) . '`';
+ return '`' . str_replace( array( "\0", '`' ), array( '', '``' ), $s ) . '`';
}
/**
- * @param $name string
+ * @param string $name
* @return bool
*/
public function isQuotedIdentifier( $name ) {
@@ -495,6 +572,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->mOpened = false;
$this->mConn = false;
$this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+
return true;
}
@@ -506,6 +584,24 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
abstract protected function mysqlPing();
/**
+ * Set lag time in seconds for a fake slave
+ *
+ * @param int $lag
+ */
+ public function setFakeSlaveLag( $lag ) {
+ $this->mFakeSlaveLag = $lag;
+ }
+
+ /**
+ * Make this connection a fake master
+ *
+ * @param bool $enabled
+ */
+ public function setFakeMaster( $enabled = true ) {
+ $this->mFakeMaster = $enabled;
+ }
+
+ /**
* Returns slave lag.
*
* This will do a SHOW SLAVE STATUS
@@ -515,6 +611,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
function getLag() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
+
return $this->mFakeSlaveLag;
}
@@ -541,52 +638,14 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @deprecated in 1.19, use getLagFromSlaveStatus
- *
- * @return bool|int
- */
- function getLagFromProcesslist() {
- wfDeprecated( __METHOD__, '1.19' );
- $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
- if ( !$res ) {
- return false;
- }
- # Find slave SQL thread
- foreach ( $res as $row ) {
- /* This should work for most situations - when default db
- * for thread is not specified, it had no events executed,
- * and therefore it doesn't know yet how lagged it is.
- *
- * Relay log I/O thread does not select databases.
- */
- if ( $row->User == 'system user' &&
- $row->State != 'Waiting for master to send event' &&
- $row->State != 'Connecting to master' &&
- $row->State != 'Queueing master event to the relay log' &&
- $row->State != 'Waiting for master update' &&
- $row->State != 'Requesting binlog dump' &&
- $row->State != 'Waiting to reconnect after a failed master event read' &&
- $row->State != 'Reconnecting after a failed master event read' &&
- $row->State != 'Registering slave on master'
- ) {
- # This is it, return the time (except -ve)
- if ( $row->Time > 0x7fffffff ) {
- return false;
- } else {
- return $row->Time;
- }
- }
- }
- return false;
- }
-
- /**
* Wait for the slave to catch up to a given master position.
- * @TODO: return values for this and base class are rubbish
+ * @todo Return values for this and base class are rubbish
*
- * @param $pos DBMasterPos object
- * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
- * @return bool|string
+ * @param DBMasterPos|MySQLMasterPos $pos
+ * @param int $timeout The maximum number of seconds to wait for synchronisation
+ * @return int Zero if the slave was past that position already,
+ * greater than zero if we waited for some period of time, less than
+ * zero if we timed out.
*/
function masterPosWait( DBMasterPos $pos, $timeout ) {
if ( $this->lastKnownSlavePos && $this->lastKnownSlavePos->hasReached( $pos ) ) {
@@ -598,9 +657,25 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->commit( __METHOD__, 'flush' );
if ( !is_null( $this->mFakeSlaveLag ) ) {
- $status = parent::masterPosWait( $pos, $timeout );
- wfProfileOut( __METHOD__ );
- return $status;
+ $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
+
+ if ( $wait > $timeout * 1e6 ) {
+ wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
+ wfProfileOut( __METHOD__ );
+
+ return -1;
+ } elseif ( $wait > 0 ) {
+ wfDebug( "Fake slave waiting $wait us\n" );
+ usleep( $wait );
+ wfProfileOut( __METHOD__ );
+
+ return 1;
+ } else {
+ wfDebug( "Fake slave up to date ($wait us)\n" );
+ wfProfileOut( __METHOD__ );
+
+ return 0;
+ }
}
# Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
@@ -618,6 +693,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
wfProfileOut( __METHOD__ );
+
return $status;
}
@@ -628,14 +704,20 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
function getSlavePos() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
- return parent::getSlavePos();
+ $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
+ wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
+
+ return $pos;
}
$res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
$row = $this->fetchObject( $res );
if ( $row ) {
- $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
+ $pos = isset( $row->Exec_master_log_pos )
+ ? $row->Exec_master_log_pos
+ : $row->Exec_Master_Log_Pos;
+
return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
} else {
return false;
@@ -649,7 +731,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
function getMasterPos() {
if ( $this->mFakeMaster ) {
- return parent::getMasterPos();
+ return new MySQLMasterPos( 'fake', microtime( true ) );
}
$res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
@@ -663,7 +745,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @param $index
+ * @param string $index
* @return string
*/
function useIndexClause( $index ) {
@@ -681,18 +763,22 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* @return string
*/
public function getSoftwareLink() {
+ // MariaDB includes its name in its version string (sent when the connection is opened),
+ // and this is how MariaDB's version of the mysql command-line client identifies MariaDB
+ // servers (see the mariadb_connection() function in libmysql/libmysql.c).
$version = $this->getServerVersion();
- if ( strpos( $version, 'MariaDB' ) !== false ) {
+ if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
return '[{{int:version-db-mariadb-url}} MariaDB]';
- } elseif ( strpos( $version, 'percona' ) !== false ) {
- return '[{{int:version-db-percona-url}} Percona Server]';
- } else {
- return '[{{int:version-db-mysql-url}} MySQL]';
}
+
+ // Percona Server's version suffix is not very distinctive, and @@version_comment
+ // doesn't give the necessary info for source builds, so assume the server is MySQL.
+ // (Even Percona's version of mysql doesn't try to make the distinction.)
+ return '[{{int:version-db-mysql-url}} MySQL]';
}
/**
- * @param $options array
+ * @param array $options
*/
public function setSessionOptions( array $options ) {
if ( isset( $options['connTimeout'] ) ) {
@@ -702,34 +788,41 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
}
+ /**
+ * @param string $sql
+ * @param string $newLine
+ * @return bool
+ */
public function streamStatementEnd( &$sql, &$newLine ) {
if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
$this->delimiter = $m[1];
$newLine = '';
}
+
return parent::streamStatementEnd( $sql, $newLine );
}
/**
* Check to see if a named lock is available. This is non-blocking.
*
- * @param string $lockName name of lock to poll
- * @param string $method name of method calling us
- * @return Boolean
+ * @param string $lockName Name of lock to poll
+ * @param string $method Name of method calling us
+ * @return bool
* @since 1.20
*/
public function lockIsFree( $lockName, $method ) {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
$row = $this->fetchObject( $result );
+
return ( $row->lockstatus == 1 );
}
/**
- * @param $lockName string
- * @param $method string
- * @param $timeout int
+ * @param string $lockName
+ * @param string $method
+ * @param int $timeout
* @return bool
*/
public function lock( $lockName, $method, $timeout = 5 ) {
@@ -741,28 +834,31 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
return true;
} else {
wfDebug( __METHOD__ . " failed to acquire lock\n" );
+
return false;
}
}
/**
- * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
- * @param $lockName string
- * @param $method string
+ * FROM MYSQL DOCS:
+ * http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
+ * @param string $lockName
+ * @param string $method
* @return bool
*/
public function unlock( $lockName, $method ) {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
$row = $this->fetchObject( $result );
+
return ( $row->lockstatus == 1 );
}
/**
- * @param $read array
- * @param $write array
- * @param $method string
- * @param $lowPriority bool
+ * @param array $read
+ * @param array $write
+ * @param string $method
+ * @param bool $lowPriority
* @return bool
*/
public function lockTables( $read, $write, $method, $lowPriority = true ) {
@@ -770,8 +866,8 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
foreach ( $write as $table ) {
$tbl = $this->tableName( $table ) .
- ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
- ' WRITE';
+ ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
+ ' WRITE';
$items[] = $tbl;
}
foreach ( $read as $table ) {
@@ -779,15 +875,17 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
$sql = "LOCK TABLES " . implode( ',', $items );
$this->query( $sql, $method );
+
return true;
}
/**
- * @param $method string
+ * @param string $method
* @return bool
*/
public function unlockTables( $method ) {
$this->query( "UNLOCK TABLES", $method );
+
return true;
}
@@ -795,7 +893,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* Get search engine class. All subclasses of this
* need to implement this if they wish to use searching.
*
- * @return String
+ * @return string
*/
public function getSearchEngine() {
return 'SearchMySQL';
@@ -803,7 +901,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* @param bool $value
- * @return mixed
*/
public function setBigSelects( $value = true ) {
if ( $value === 'default' ) {
@@ -822,12 +919,12 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* DELETE where the condition is a join. MySql uses multi-table deletes.
- * @param $delTable string
- * @param $joinTable string
- * @param $delVar string
- * @param $joinVar string
- * @param $conds array|string
- * @param bool|string $fname bool
+ * @param string $delTable
+ * @param string $joinTable
+ * @param string $delVar
+ * @param string $joinVar
+ * @param array|string $conds
+ * @param bool|string $fname
* @throws DBUnexpectedError
* @return bool|ResultWrapper
*/
@@ -853,16 +950,18 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* @param array $uniqueIndexes
* @param array $set
* @param string $fname
- * @param array $options
* @return bool
*/
- public function upsert(
- $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ 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 ( !is_array( reset( $rows ) ) ) {
+ $rows = array( $rows );
+ }
$table = $this->tableName( $table );
$columns = array_keys( $rows[0] );
@@ -885,6 +984,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
function getServerUptime() {
$vars = $this->getMysqlStatus( 'Uptime' );
+
return (int)$vars['Uptime'];
}
@@ -927,24 +1027,26 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @param $oldName
- * @param $newName
- * @param $temporary bool
- * @param $fname string
+ * @param string $oldName
+ * @param string $newName
+ * @param bool $temporary
+ * @param string $fname
+ * @return bool
*/
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
$tmp = $temporary ? 'TEMPORARY ' : '';
$newName = $this->addIdentifierQuotes( $newName );
$oldName = $this->addIdentifierQuotes( $oldName );
$query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
- $this->query( $query, $fname );
+
+ return $this->query( $query, $fname );
}
/**
* List all tables on the database
*
* @param string $prefix Only show tables with this prefix, e.g. mw_
- * @param string $fname calling function name
+ * @param string $fname Calling function name
* @return array
*/
function listTables( $prefix = null, $fname = __METHOD__ ) {
@@ -965,14 +1067,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @param $tableName
- * @param $fName string
+ * @param string $tableName
+ * @param string $fName
* @return bool|ResultWrapper
*/
public function dropTable( $tableName, $fName = __METHOD__ ) {
if ( !$this->tableExists( $tableName, $fName ) ) {
return false;
}
+
return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
}
@@ -982,14 +1085,19 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
protected function getDefaultSchemaVars() {
$vars = parent::getDefaultSchemaVars();
$vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
- $vars['wgDBTableOptions'] = str_replace( 'CHARSET=mysql4', 'CHARSET=binary', $vars['wgDBTableOptions'] );
+ $vars['wgDBTableOptions'] = str_replace(
+ 'CHARSET=mysql4',
+ 'CHARSET=binary',
+ $vars['wgDBTableOptions']
+ );
+
return $vars;
}
/**
* Get status information from SHOW STATUS in an associative array
*
- * @param $which string
+ * @param string $which
* @return array
*/
function getMysqlStatus( $which = "%" ) {
@@ -1006,9 +1114,9 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* Lists VIEWs in the database
*
- * @param string $prefix Only show VIEWs with this prefix, eg.
+ * @param string $prefix Only show VIEWs with this prefix, eg.
* unit_test_, or $wgDBprefix. Default: null, would return all views.
- * @param string $fname Name of calling function
+ * @param string $fname Name of calling function
* @return array
* @since 1.22
*/
@@ -1022,12 +1130,12 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
// Query for the VIEWS
$result = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
$this->allViews = array();
- while ( ($row = $this->fetchRow($result)) !== false ) {
+ while ( ( $row = $this->fetchRow( $result ) ) !== false ) {
array_push( $this->allViews, $row[$propertyName] );
}
}
- if ( is_null($prefix) || $prefix === '' ) {
+ if ( is_null( $prefix ) || $prefix === '' ) {
return $this->allViews;
}
@@ -1038,24 +1146,23 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
array_push( $filteredViews, $viewName );
}
}
+
return $filteredViews;
}
/**
* Differentiates between a TABLE and a VIEW.
*
- * @param $name string: Name of the TABLE/VIEW to test
+ * @param string $name Name of the TABLE/VIEW to test
+ * @param string $prefix
* @return bool
* @since 1.22
*/
public function isView( $name, $prefix = null ) {
return in_array( $name, $this->listViews( $prefix ) );
}
-
}
-
-
/**
* Utility class.
* @ingroup Database
@@ -1130,7 +1237,11 @@ class MySQLField implements Field {
}
class MySQLMasterPos implements DBMasterPos {
- var $file, $pos;
+ /** @var string */
+ public $file;
+
+ /** @var int Timestamp */
+ public $pos;
function __construct( $file, $pos ) {
$this->file = $file;
@@ -1143,19 +1254,21 @@ class MySQLMasterPos implements DBMasterPos {
}
/**
- * @return array|false (int, int)
+ * @return array|bool (int, int)
*/
protected function getCoordinates() {
$m = array();
if ( preg_match( '!\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
return array( (int)$m[1], (int)$m[2] );
}
+
return false;
}
function hasReached( MySQLMasterPos $pos ) {
$thisPos = $this->getCoordinates();
$thatPos = $pos->getCoordinates();
+
return ( $thisPos && $thatPos && $thisPos >= $thatPos );
}
}
diff --git a/includes/db/DatabaseMysqli.php b/includes/db/DatabaseMysqli.php
index 0ec54314..a03c9aaf 100644
--- a/includes/db/DatabaseMysqli.php
+++ b/includes/db/DatabaseMysqli.php
@@ -29,9 +29,8 @@
* @see Database
*/
class DatabaseMysqli extends DatabaseMysqlBase {
-
/**
- * @param $sql string
+ * @param string $sql
* @return resource
*/
protected function doQuery( $sql ) {
@@ -40,10 +39,17 @@ class DatabaseMysqli extends DatabaseMysqlBase {
} else {
$ret = $this->mConn->query( $sql, MYSQLI_USE_RESULT );
}
+
return $ret;
}
+ /**
+ * @param string $realServer
+ * @return bool|mysqli
+ * @throws DBConnectionError
+ */
protected function mysqlConnect( $realServer ) {
+ global $wgDBmysql5;
# Fail now
# Otherwise we get a suppressed fatal error, which is very hard to track down
if ( !function_exists( 'mysqli_init' ) ) {
@@ -52,14 +58,22 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
// Other than mysql_connect, mysqli_real_connect expects an explicit port
- // parameter. So we need to parse the port out of $realServer
+ // and socket parameters. So we need to parse the port and socket out of
+ // $realServer
$port = null;
+ $socket = null;
$hostAndPort = IP::splitHostAndPort( $realServer );
if ( $hostAndPort ) {
$realServer = $hostAndPort[0];
if ( $hostAndPort[1] ) {
$port = $hostAndPort[1];
}
+ } elseif ( substr_count( $realServer, ':' ) == 1 ) {
+ // If we have a colon and something that's not a port number
+ // inside the hostname, assume it's the socket location
+ $hostAndSocket = explode( ':', $realServer );
+ $realServer = $hostAndSocket[0];
+ $socket = $hostAndSocket[1];
}
$connFlags = 0;
@@ -74,22 +88,41 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
$mysqli = mysqli_init();
- $numAttempts = 2;
+ if ( $wgDBmysql5 ) {
+ // Tell the server we're communicating with it in UTF-8.
+ // This may engage various charset conversions.
+ $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'utf8' );
+ } else {
+ $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'binary' );
+ }
+ $mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
- for ( $i = 0; $i < $numAttempts; $i++ ) {
- if ( $i > 1 ) {
- usleep( 1000 );
- }
- if ( $mysqli->real_connect( $realServer, $this->mUser,
- $this->mPassword, $this->mDBname, $port, null, $connFlags ) )
- {
- return $mysqli;
- }
+ if ( $mysqli->real_connect( $realServer, $this->mUser,
+ $this->mPassword, $this->mDBname, $port, $socket, $connFlags )
+ ) {
+ return $mysqli;
}
return false;
}
+ protected function connectInitCharset() {
+ // already done in mysqlConnect()
+ return true;
+ }
+
+ /**
+ * @param string $charset
+ * @return bool
+ */
+ protected function mysqlSetCharset( $charset ) {
+ if ( method_exists( $this->mConn, 'set_charset' ) ) {
+ return $this->mConn->set_charset( $charset );
+ } else {
+ return $this->query( 'SET NAMES ' . $charset, __METHOD__ );
+ }
+ }
+
/**
* @return bool
*/
@@ -123,11 +156,12 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
/**
- * @param $db
+ * @param string $db
* @return bool
*/
function selectDB( $db ) {
$this->mDBname = $db;
+
return $this->mConn->select_db( $db );
}
@@ -138,35 +172,63 @@ class DatabaseMysqli extends DatabaseMysqlBase {
return $this->mConn->server_info;
}
+ /**
+ * @param mysqli $res
+ * @return bool
+ */
protected function mysqlFreeResult( $res ) {
$res->free_result();
+
return true;
}
+ /**
+ * @param mysqli $res
+ * @return bool
+ */
protected function mysqlFetchObject( $res ) {
$object = $res->fetch_object();
if ( $object === null ) {
return false;
}
+
return $object;
}
+ /**
+ * @param mysqli $res
+ * @return bool
+ */
protected function mysqlFetchArray( $res ) {
$array = $res->fetch_array();
if ( $array === null ) {
return false;
}
+
return $array;
}
+ /**
+ * @param mysqli $res
+ * @return mixed
+ */
protected function mysqlNumRows( $res ) {
return $res->num_rows;
}
+ /**
+ * @param mysqli $res
+ * @return mixed
+ */
protected function mysqlNumFields( $res ) {
return $res->field_count;
}
+ /**
+ * @param mysqli $res
+ * @param int $n
+ * @return mixed
+ */
protected function mysqlFetchField( $res, $n ) {
$field = $res->fetch_field_direct( $n );
$field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
@@ -174,26 +236,58 @@ class DatabaseMysqli extends DatabaseMysqlBase {
$field->unique_key = $field->flags & MYSQLI_UNIQUE_KEY_FLAG;
$field->multiple_key = $field->flags & MYSQLI_MULTIPLE_KEY_FLAG;
$field->binary = $field->flags & MYSQLI_BINARY_FLAG;
+
return $field;
}
+ /**
+ * @param resource|ResultWrapper $res
+ * @param int $n
+ * @return mixed
+ */
protected function mysqlFieldName( $res, $n ) {
$field = $res->fetch_field_direct( $n );
+
return $field->name;
}
+ /**
+ * @param resource|ResultWrapper $res
+ * @param int $n
+ * @return mixed
+ */
+ protected function mysqlFieldType( $res, $n ) {
+ $field = $res->fetch_field_direct( $n );
+
+ return $field->type;
+ }
+
+ /**
+ * @param resource|ResultWrapper $res
+ * @param int $row
+ * @return mixed
+ */
protected function mysqlDataSeek( $res, $row ) {
return $res->data_seek( $row );
}
+ /**
+ * @param mysqli $conn Optional connection object
+ * @return string
+ */
protected function mysqlError( $conn = null ) {
- if ($conn === null) {
+ if ( $conn === null ) {
return mysqli_connect_error();
} else {
return $conn->error;
}
}
+ /**
+ * Escapes special characters in a string for use in an SQL statement
+ * @param string $s
+ * @return string
+ */
protected function mysqlRealEscapeString( $s ) {
return $this->mConn->real_escape_string( $s );
}
@@ -202,4 +296,18 @@ class DatabaseMysqli extends DatabaseMysqlBase {
return $this->mConn->ping();
}
+ /**
+ * Give an id for the connection
+ *
+ * mysql driver used resource id, but mysqli objects cannot be cast to string.
+ * @return string
+ */
+ public function __toString() {
+ if ( $this->mConn instanceof Mysqli ) {
+ return (string)$this->mConn->thread_id;
+ } else {
+ // mConn might be false or something.
+ return (string)$this->mConn;
+ }
+ }
}
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 9009b328..f031f780 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -50,17 +50,19 @@ class ORAResult {
}
/**
- * @param $db DatabaseBase
- * @param $stmt
+ * @param DatabaseBase $db
+ * @param resource $stmt A valid OCI statement identifier
* @param bool $unique
*/
function __construct( &$db, $stmt, $unique = false ) {
$this->db =& $db;
- if ( ( $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, - 1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM ) ) === false ) {
+ $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM );
+ if ( $this->nrows === false ) {
$e = oci_error( $stmt );
$db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ );
$this->free();
+
return;
}
@@ -121,6 +123,7 @@ class ORAResult {
$ret[$lc] = $v;
$ret[$k] = $v;
}
+
return $ret;
}
}
@@ -183,25 +186,49 @@ class ORAField implements Field {
* @ingroup Database
*/
class DatabaseOracle extends DatabaseBase {
- var $mInsertId = null;
- var $mLastResult = null;
- var $lastResult = null;
- var $cursor = 0;
- var $mAffectedRows;
+ /** @var resource */
+ protected $mLastResult = null;
+
+ /** @var int The number of rows affected as an integer */
+ protected $mAffectedRows;
- var $ignore_DUP_VAL_ON_INDEX = false;
- var $sequenceData = null;
+ /** @var int */
+ private $mInsertId = null;
- var $defaultCharset = 'AL32UTF8';
+ /** @var bool */
+ private $ignoreDupValOnIndex = false;
- var $mFieldInfoCache = array();
+ /** @var bool|array */
+ private $sequenceData = null;
- function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $flags = 0, $tablePrefix = 'get from global' )
- {
+ /** @var string Character set for Oracle database */
+ private $defaultCharset = 'AL32UTF8';
+
+ /** @var array */
+ private $mFieldInfoCache = array();
+
+ function __construct( $p = null ) {
global $wgDBprefix;
- $tablePrefix = $tablePrefix == 'get from global' ? strtoupper( $wgDBprefix ) : strtoupper( $tablePrefix );
- parent::__construct( $server, $user, $password, $dbName, $flags, $tablePrefix );
+
+ if ( !is_array( $p ) ) { // legacy calling pattern
+ wfDeprecated( __METHOD__ . " method called without parameter array.", "1.22" );
+ $args = func_get_args();
+ $p = array(
+ 'host' => isset( $args[0] ) ? $args[0] : false,
+ 'user' => isset( $args[1] ) ? $args[1] : false,
+ 'password' => isset( $args[2] ) ? $args[2] : false,
+ 'dbname' => isset( $args[3] ) ? $args[3] : false,
+ 'flags' => isset( $args[4] ) ? $args[4] : 0,
+ 'tablePrefix' => isset( $args[5] ) ? $args[5] : 'get from global',
+ 'schema' => 'get from global',
+ 'foreign' => isset( $args[6] ) ? $args[6] : false
+ );
+ }
+ if ( $p['tablePrefix'] == 'get from global' ) {
+ $p['tablePrefix'] = $wgDBprefix;
+ }
+ $p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
+ parent::__construct( $p );
wfRunHooks( 'DatabaseOraclePostInit', array( $this ) );
}
@@ -220,21 +247,27 @@ class DatabaseOracle extends DatabaseBase {
function cascadingDeletes() {
return true;
}
+
function cleanupTriggers() {
return true;
}
+
function strictIPs() {
return true;
}
+
function realTimestamps() {
return true;
}
+
function implicitGroupby() {
return false;
}
+
function implicitOrderby() {
return false;
}
+
function searchableIPs() {
return true;
}
@@ -251,7 +284,11 @@ class DatabaseOracle extends DatabaseBase {
function open( $server, $user, $password, $dbName ) {
global $wgDBOracleDRCP;
if ( !function_exists( 'oci_connect' ) ) {
- throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
+ throw new DBConnectionError(
+ $this,
+ "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n " .
+ "(Note: if you recently installed PHP, you may need to restart your webserver\n " .
+ "and database)\n" );
}
$this->close();
@@ -274,7 +311,7 @@ class DatabaseOracle extends DatabaseBase {
}
if ( !strlen( $user ) ) { # e.g. the class is being loaded
- return;
+ return null;
}
if ( $wgDBOracleDRCP ) {
@@ -285,11 +322,29 @@ class DatabaseOracle extends DatabaseBase {
wfSuppressWarnings();
if ( $this->mFlags & DBO_PERSISTENT ) {
- $this->mConn = oci_pconnect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_pconnect(
+ $this->mUser,
+ $this->mPassword,
+ $this->mServer,
+ $this->defaultCharset,
+ $session_mode
+ );
} elseif ( $this->mFlags & DBO_DEFAULT ) {
- $this->mConn = oci_new_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_new_connect(
+ $this->mUser,
+ $this->mPassword,
+ $this->mServer,
+ $this->defaultCharset,
+ $session_mode
+ );
} else {
- $this->mConn = oci_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_connect(
+ $this->mUser,
+ $this->mPassword,
+ $this->mServer,
+ $this->defaultCharset,
+ $session_mode
+ );
}
wfRestoreWarnings();
@@ -308,6 +363,7 @@ class DatabaseOracle extends DatabaseBase {
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
+
return $this->mConn;
}
@@ -343,20 +399,28 @@ class DatabaseOracle extends DatabaseBase {
// you have to select data from plan table after explain
$explain_id = MWTimestamp::getLocalInstance()->format( 'dmYHis' );
- $sql = preg_replace( '/^EXPLAIN /', 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR', $sql, 1, $explain_count );
+ $sql = preg_replace(
+ '/^EXPLAIN /',
+ 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR',
+ $sql,
+ 1,
+ $explain_count
+ );
wfSuppressWarnings();
if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
$e = oci_error( $this->mConn );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
if ( !oci_execute( $stmt, $this->execFlags() ) ) {
$e = oci_error( $stmt );
- if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
+ if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
}
@@ -364,11 +428,13 @@ class DatabaseOracle extends DatabaseBase {
wfRestoreWarnings();
if ( $explain_count > 0 ) {
- return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table WHERE statement_id = \'' . $explain_id . '\'' );
+ return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table ' .
+ 'WHERE statement_id = \'' . $explain_id . '\'' );
} elseif ( oci_statement_type( $stmt ) == 'SELECT' ) {
return new ORAResult( $this, $stmt, $union_unique );
} else {
$this->mAffectedRows = oci_num_rows( $stmt );
+
return true;
}
}
@@ -377,6 +443,10 @@ class DatabaseOracle extends DatabaseBase {
return $this->query( $sql, $fname, true );
}
+ /**
+ * Frees resources associated with the LOB descriptor
+ * @param ResultWrapper|resource $res
+ */
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -385,6 +455,10 @@ class DatabaseOracle extends DatabaseBase {
$res->free();
}
+ /**
+ * @param ResultWrapper|stdClass $res
+ * @return mixed
+ */
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -423,12 +497,16 @@ class DatabaseOracle extends DatabaseBase {
/**
* This must be called after nextSequenceVal
- * @return null
+ * @return null|int
*/
function insertId() {
return $this->mInsertId;
}
+ /**
+ * @param mixed $res
+ * @param int $row
+ */
function dataSeek( $res, $row ) {
if ( $res instanceof ORAResult ) {
$res->seek( $row );
@@ -443,6 +521,7 @@ class DatabaseOracle extends DatabaseBase {
} else {
$e = oci_error( $this->mConn );
}
+
return $e['message'];
}
@@ -452,6 +531,7 @@ class DatabaseOracle extends DatabaseBase {
} else {
$e = oci_error( $this->mConn );
}
+
return $e['code'];
}
@@ -462,6 +542,9 @@ class DatabaseOracle extends DatabaseBase {
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool
*/
function indexInfo( $table, $index, $fname = __METHOD__ ) {
@@ -482,7 +565,7 @@ class DatabaseOracle extends DatabaseBase {
}
if ( in_array( 'IGNORE', $options ) ) {
- $this->ignore_DUP_VAL_ON_INDEX = true;
+ $this->ignoreDupValOnIndex = true;
}
if ( !is_array( reset( $a ) ) ) {
@@ -495,7 +578,7 @@ class DatabaseOracle extends DatabaseBase {
$retVal = true;
if ( in_array( 'IGNORE', $options ) ) {
- $this->ignore_DUP_VAL_ON_INDEX = false;
+ $this->ignoreDupValOnIndex = false;
}
return $retVal;
@@ -509,6 +592,7 @@ class DatabaseOracle extends DatabaseBase {
if ( is_numeric( $col ) ) {
$bind = $val;
$val = null;
+
return $bind;
} elseif ( $includeCol ) {
$bind = "$col = ";
@@ -535,6 +619,13 @@ class DatabaseOracle extends DatabaseBase {
return $bind;
}
+ /**
+ * @param string $table
+ * @param array $row
+ * @param string $fname
+ * @return bool
+ * @throws DBUnexpectedError
+ */
private function insertOneRow( $table, $row, $fname ) {
global $wgContLang;
@@ -563,6 +654,7 @@ class DatabaseOracle extends DatabaseBase {
if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
$e = oci_error( $this->mConn );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
foreach ( $row as $col => &$val ) {
@@ -585,9 +677,11 @@ class DatabaseOracle extends DatabaseBase {
if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
$e = oci_error( $stmt );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
} else {
+ /** @var OCI_Lob[] $lob */
if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
$e = oci_error( $stmt );
throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
@@ -599,10 +693,10 @@ class DatabaseOracle extends DatabaseBase {
if ( $col_type == 'BLOB' ) {
$lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_BLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_BLOB );
} else {
$lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
}
}
}
@@ -611,8 +705,9 @@ class DatabaseOracle extends DatabaseBase {
if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
$e = oci_error( $stmt );
- if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
+ if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
} else {
$this->mAffectedRows = oci_num_rows( $stmt );
@@ -633,12 +728,12 @@ class DatabaseOracle extends DatabaseBase {
oci_commit( $this->mConn );
}
- oci_free_statement( $stmt );
+ return oci_free_statement( $stmt );
}
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array() )
- {
+ $insertOptions = array(), $selectOptions = array()
+ ) {
$destTable = $this->tableName( $destTable );
if ( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
@@ -651,8 +746,8 @@ class DatabaseOracle extends DatabaseBase {
}
if ( ( $sequenceData = $this->getSequenceData( $destTable ) ) !== false &&
- !isset( $varMap[$sequenceData['column']] ) )
- {
+ !isset( $varMap[$sequenceData['column']] )
+ ) {
$varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
}
@@ -671,13 +766,13 @@ class DatabaseOracle extends DatabaseBase {
$sql .= " $tailOpts";
if ( in_array( 'IGNORE', $insertOptions ) ) {
- $this->ignore_DUP_VAL_ON_INDEX = true;
+ $this->ignoreDupValOnIndex = true;
}
$retval = $this->query( $sql, $fname );
if ( in_array( 'IGNORE', $insertOptions ) ) {
- $this->ignore_DUP_VAL_ON_INDEX = false;
+ $this->ignoreDupValOnIndex = false;
}
return $retval;
@@ -699,7 +794,9 @@ class DatabaseOracle extends DatabaseBase {
// add sequence column to each list of columns, when not set
foreach ( $rows as &$row ) {
if ( !isset( $row[$sequenceData['column']] ) ) {
- $row[$sequenceData['column']] = $this->addIdentifierQuotes('GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')');
+ $row[$sequenceData['column']] =
+ $this->addIdentifierQuotes( 'GET_SEQUENCE_VALUE(\'' .
+ $sequenceData['sequence'] . '\')' );
}
}
}
@@ -727,33 +824,45 @@ class DatabaseOracle extends DatabaseBase {
function tableNameInternal( $name ) {
$name = $this->tableName( $name );
+
return preg_replace( '/.*\.(.*)/', '$1', $name );
}
+
/**
* Return the next in a sequence, save the value for retrieval via insertId()
- * @return null
+ *
+ * @param string $seqName
+ * @return null|int
*/
function nextSequenceValue( $seqName ) {
$res = $this->query( "SELECT $seqName.nextval FROM dual" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
+
return $this->mInsertId;
}
/**
* Return sequence_name if table has a sequence
+ *
+ * @param string $table
* @return bool
*/
private function getSequenceData( $table ) {
if ( $this->sequenceData == null ) {
$result = $this->doQuery( "SELECT lower(asq.sequence_name),
- lower(atc.table_name),
- lower(atc.column_name)
- FROM all_sequences asq, all_tab_columns atc
- WHERE decode(atc.table_name, '{$this->mTablePrefix}MWUSER', '{$this->mTablePrefix}USER', atc.table_name) || '_' ||
- atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
- AND asq.sequence_owner = upper('{$this->mDBname}')
- AND atc.owner = upper('{$this->mDBname}')" );
+ lower(atc.table_name),
+ lower(atc.column_name)
+ FROM all_sequences asq, all_tab_columns atc
+ WHERE decode(
+ atc.table_name,
+ '{$this->mTablePrefix}MWUSER',
+ '{$this->mTablePrefix}USER',
+ atc.table_name
+ ) || '_' ||
+ atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
+ AND asq.sequence_owner = upper('{$this->mDBname}')
+ AND atc.owner = upper('{$this->mDBname}')" );
while ( ( $row = $result->fetchRow() ) !== false ) {
$this->sequenceData[$row[1]] = array(
@@ -763,12 +872,20 @@ class DatabaseOracle extends DatabaseBase {
}
}
$table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) );
+
return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
}
- # Returns the size of a text field, or -1 for "unlimited"
+ /**
+ * Returns the size of a text field, or -1 for "unlimited"
+ *
+ * @param string $table
+ * @param string $field
+ * @return mixed
+ */
function textFieldSize( $table, $field ) {
$fieldInfoData = $this->fieldInfo( $table, $field );
+
return $fieldInfoData->maxLength();
}
@@ -776,6 +893,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $offset === false ) {
$offset = 0;
}
+
return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
}
@@ -787,19 +905,24 @@ class DatabaseOracle extends DatabaseBase {
if ( $b instanceof Blob ) {
$b = $b->fetch();
}
+
return $b;
}
function unionQueries( $sqls, $all ) {
$glue = ' UNION ALL ';
- return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')';
+
+ return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) .
+ 'FROM (' . implode( $glue, $sqls ) . ')';
}
function wasDeadlock() {
return $this->lastErrno() == 'OCI-00060';
}
- function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
+ function duplicateTableStructure( $oldName, $newName, $temporary = false,
+ $fname = __METHOD__
+ ) {
$temporary = $temporary ? 'TRUE' : 'FALSE';
$newName = strtoupper( $newName );
@@ -809,7 +932,8 @@ class DatabaseOracle extends DatabaseBase {
$oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
$newPrefix = strtoupper( $this->mTablePrefix );
- return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', '$oldPrefix', '$newPrefix', $temporary ); END;" );
+ return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
+ "'$oldPrefix', '$newPrefix', $temporary ); END;" );
}
function listTables( $prefix = null, $fname = __METHOD__ ) {
@@ -819,7 +943,8 @@ class DatabaseOracle extends DatabaseBase {
}
$owner = strtoupper( $this->mDBname );
- $result = $this->doQuery( "SELECT table_name FROM all_tables WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
+ $result = $this->doQuery( "SELECT table_name FROM all_tables " .
+ "WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
// dirty code ... i know
$endArray = array();
@@ -851,6 +976,10 @@ class DatabaseOracle extends DatabaseBase {
/**
* Return aggregated value function call
+ *
+ * @param array $valuedata
+ * @param string $valuename
+ * @return mixed
*/
public function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
@@ -871,7 +1000,7 @@ class DatabaseOracle extends DatabaseBase {
}
/**
- * @return string wikitext of a link to the server software's web site
+ * @return string Wikitext of a link to the server software's web site
*/
public function getSoftwareLink() {
return '[{{int:version-db-oracle-url}} Oracle]';
@@ -882,15 +1011,22 @@ class DatabaseOracle extends DatabaseBase {
*/
function getServerVersion() {
//better version number, fallback on driver
- $rset = $this->doQuery( 'SELECT version FROM product_component_version WHERE UPPER(product) LIKE \'ORACLE DATABASE%\'' );
+ $rset = $this->doQuery(
+ 'SELECT version FROM product_component_version ' .
+ 'WHERE UPPER(product) LIKE \'ORACLE DATABASE%\''
+ );
if ( !( $row = $rset->fetchRow() ) ) {
return oci_server_version( $this->mConn );
}
+
return $row['version'];
}
/**
* Query whether a given index exists
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool
*/
function indexExists( $table, $index, $fname = __METHOD__ ) {
@@ -898,27 +1034,30 @@ class DatabaseOracle extends DatabaseBase {
$table = strtoupper( $this->removeIdentifierQuotes( $table ) );
$index = strtoupper( $index );
$owner = strtoupper( $this->mDBname );
- $SQL = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
- $res = $this->doQuery( $SQL );
+ $sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
+ $res = $this->doQuery( $sql );
if ( $res ) {
$count = $res->numRows();
$res->free();
} else {
$count = 0;
}
+
return $count != 0;
}
/**
* Query whether a given table exists (in the given schema, or the default mw one if not given)
+ * @param string $table
+ * @param string $fname
* @return bool
*/
function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
$owner = $this->addQuotes( strtoupper( $this->mDBname ) );
- $SQL = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
- $res = $this->doQuery( $SQL );
+ $sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
+ $res = $this->doQuery( $sql );
if ( $res && $res->numRows() > 0 ) {
$exists = true;
} else {
@@ -926,6 +1065,7 @@ class DatabaseOracle extends DatabaseBase {
}
$res->free();
+
return $exists;
}
@@ -935,8 +1075,8 @@ class DatabaseOracle extends DatabaseBase {
* For internal calls. Use fieldInfo for normal usage.
* Returns false if the field doesn't exist
*
- * @param $table Array
- * @param $field String
+ * @param array|string $table
+ * @param string $field
* @return ORAField|ORAResult
*/
private function fieldInfoMulti( $table, $field ) {
@@ -960,10 +1100,15 @@ class DatabaseOracle extends DatabaseBase {
$tableWhere = '= \'' . $table . '\'';
}
- $fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name ' . $tableWhere . ' and column_name = \'' . $field . '\'' );
+ $fieldInfoStmt = oci_parse(
+ $this->mConn,
+ 'SELECT * FROM wiki_field_info_full WHERE table_name ' .
+ $tableWhere . ' and column_name = \'' . $field . '\''
+ );
if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
$e = oci_error( $fieldInfoStmt );
$this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
+
return false;
}
$res = new ORAResult( $this, $fieldInfoStmt );
@@ -982,19 +1127,21 @@ class DatabaseOracle extends DatabaseBase {
$this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
}
$res->free();
+
return $fieldInfoTemp;
}
/**
* @throws DBUnexpectedError
- * @param $table
- * @param $field
+ * @param string $table
+ * @param string $field
* @return ORAField
*/
function fieldInfo( $table, $field ) {
if ( is_array( $table ) ) {
throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
}
+
return $this->fieldInfoMulti( $table, $field );
}
@@ -1022,7 +1169,16 @@ class DatabaseOracle extends DatabaseBase {
}
}
- /* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */
+ /**
+ * defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
+ *
+ * @param resource $fp
+ * @param bool|string $lineCallback
+ * @param bool|callable $resultCallback
+ * @param string $fname
+ * @param bool|callable $inputCallback
+ * @return bool|string
+ */
function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
$fname = __METHOD__, $inputCallback = false ) {
$cmd = '';
@@ -1031,7 +1187,7 @@ class DatabaseOracle extends DatabaseBase {
$replacements = array();
- while ( ! feof( $fp ) ) {
+ while ( !feof( $fp ) ) {
if ( $lineCallback ) {
call_user_func( $lineCallback );
}
@@ -1041,7 +1197,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $sl < 0 ) {
continue;
}
- if ( '-' == $line { 0 } && '-' == $line { 1 } ) {
+ if ( '-' == $line[0] && '-' == $line[1] ) {
continue;
}
@@ -1055,7 +1211,7 @@ class DatabaseOracle extends DatabaseBase {
$dollarquote = true;
}
} elseif ( !$dollarquote ) {
- if ( ';' == $line { $sl } && ( $sl < 2 || ';' != $line { $sl - 1 } ) ) {
+ if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
$done = true;
$line = substr( $line, 0, $sl );
}
@@ -1088,6 +1244,7 @@ class DatabaseOracle extends DatabaseBase {
if ( false === $res ) {
$err = $this->lastError();
+
return "Query \"{$cmd}\" failed with error code \"$err\".\n";
}
}
@@ -1096,6 +1253,7 @@ class DatabaseOracle extends DatabaseBase {
$done = false;
}
}
+
return true;
}
@@ -1114,8 +1272,10 @@ class DatabaseOracle extends DatabaseBase {
if ( $e['code'] != '1435' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
}
+
return false;
}
+
return true;
}
@@ -1128,6 +1288,7 @@ class DatabaseOracle extends DatabaseBase {
if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
$s = $wgContLang->checkTitleEncoding( $s );
}
+
return "'" . $this->strencode( $s ) . "'";
}
@@ -1135,6 +1296,7 @@ class DatabaseOracle extends DatabaseBase {
if ( !$this->getFlag( DBO_DDLMODE ) ) {
$s = '/*Q*/' . $s;
}
+
return $s;
}
@@ -1173,13 +1335,17 @@ class DatabaseOracle extends DatabaseBase {
$conds2[$col] = $val;
}
}
+
return $conds2;
}
- function selectRow( $table, $vars, $conds, $fname = __METHOD__, $options = array(), $join_conds = array() ) {
+ function selectRow( $table, $vars, $conds, $fname = __METHOD__,
+ $options = array(), $join_conds = array()
+ ) {
if ( is_array( $conds ) ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
}
+
return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
}
@@ -1187,10 +1353,8 @@ class DatabaseOracle extends DatabaseBase {
* Returns an optional USE INDEX clause to go after the table, and a
* string to go at the end of the query
*
- * @private
- *
- * @param array $options an associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
+ * @param array $options An associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
* @return array
*/
function makeSelectOptions( $options ) {
@@ -1216,7 +1380,7 @@ class DatabaseOracle extends DatabaseBase {
$startOpts .= 'DISTINCT';
}
- if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
+ if ( isset( $options['USE INDEX'] ) && !is_array( $options['USE INDEX'] ) ) {
$useIndex = $this->useIndexClause( $options['USE INDEX'] );
} else {
$useIndex = '';
@@ -1233,21 +1397,41 @@ class DatabaseOracle extends DatabaseBase {
// all deletions on these tables have transactions so final failure rollbacks these updates
$table = $this->tableName( $table );
if ( $table == $this->tableName( 'user' ) ) {
- $this->update( 'archive', array( 'ar_user' => 0 ), array( 'ar_user' => $conds['user_id'] ), $fname );
- $this->update( 'ipblocks', array( 'ipb_user' => 0 ), array( 'ipb_user' => $conds['user_id'] ), $fname );
- $this->update( 'image', array( 'img_user' => 0 ), array( 'img_user' => $conds['user_id'] ), $fname );
- $this->update( 'oldimage', array( 'oi_user' => 0 ), array( 'oi_user' => $conds['user_id'] ), $fname );
- $this->update( 'filearchive', array( 'fa_deleted_user' => 0 ), array( 'fa_deleted_user' => $conds['user_id'] ), $fname );
- $this->update( 'filearchive', array( 'fa_user' => 0 ), array( 'fa_user' => $conds['user_id'] ), $fname );
- $this->update( 'uploadstash', array( 'us_user' => 0 ), array( 'us_user' => $conds['user_id'] ), $fname );
- $this->update( 'recentchanges', array( 'rc_user' => 0 ), array( 'rc_user' => $conds['user_id'] ), $fname );
- $this->update( 'logging', array( 'log_user' => 0 ), array( 'log_user' => $conds['user_id'] ), $fname );
+ $this->update( 'archive', array( 'ar_user' => 0 ),
+ array( 'ar_user' => $conds['user_id'] ), $fname );
+ $this->update( 'ipblocks', array( 'ipb_user' => 0 ),
+ array( 'ipb_user' => $conds['user_id'] ), $fname );
+ $this->update( 'image', array( 'img_user' => 0 ),
+ array( 'img_user' => $conds['user_id'] ), $fname );
+ $this->update( 'oldimage', array( 'oi_user' => 0 ),
+ array( 'oi_user' => $conds['user_id'] ), $fname );
+ $this->update( 'filearchive', array( 'fa_deleted_user' => 0 ),
+ array( 'fa_deleted_user' => $conds['user_id'] ), $fname );
+ $this->update( 'filearchive', array( 'fa_user' => 0 ),
+ array( 'fa_user' => $conds['user_id'] ), $fname );
+ $this->update( 'uploadstash', array( 'us_user' => 0 ),
+ array( 'us_user' => $conds['user_id'] ), $fname );
+ $this->update( 'recentchanges', array( 'rc_user' => 0 ),
+ array( 'rc_user' => $conds['user_id'] ), $fname );
+ $this->update( 'logging', array( 'log_user' => 0 ),
+ array( 'log_user' => $conds['user_id'] ), $fname );
} elseif ( $table == $this->tableName( 'image' ) ) {
- $this->update( 'oldimage', array( 'oi_name' => 0 ), array( 'oi_name' => $conds['img_name'] ), $fname );
+ $this->update( 'oldimage', array( 'oi_name' => 0 ),
+ array( 'oi_name' => $conds['img_name'] ), $fname );
}
+
return parent::delete( $table, $conds, $fname );
}
+ /**
+ * @param string $table
+ * @param array $values
+ * @param array $conds
+ * @param string $fname
+ * @param array $options
+ * @return bool
+ * @throws DBUnexpectedError
+ */
function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
global $wgContLang;
@@ -1275,6 +1459,7 @@ class DatabaseOracle extends DatabaseBase {
if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
$e = oci_error( $this->mConn );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
foreach ( $values as $col => &$val ) {
@@ -1296,9 +1481,11 @@ class DatabaseOracle extends DatabaseBase {
if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
$e = oci_error( $stmt );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
} else {
+ /** @var OCI_Lob[] $lob */
if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
$e = oci_error( $stmt );
throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
@@ -1310,10 +1497,10 @@ class DatabaseOracle extends DatabaseBase {
if ( $col_type == 'BLOB' ) {
$lob[$col]->writeTemporary( $val );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, SQLT_BLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, SQLT_BLOB );
} else {
$lob[$col]->writeTemporary( $val );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
}
}
}
@@ -1322,8 +1509,9 @@ class DatabaseOracle extends DatabaseBase {
if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
$e = oci_error( $stmt );
- if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
+ if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
} else {
$this->mAffectedRows = oci_num_rows( $stmt );
@@ -1344,7 +1532,7 @@ class DatabaseOracle extends DatabaseBase {
oci_commit( $this->mConn );
}
- oci_free_statement( $stmt );
+ return oci_free_statement( $stmt );
}
function bitNot( $field ) {
@@ -1360,9 +1548,6 @@ class DatabaseOracle extends DatabaseBase {
return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
}
- function setFakeMaster( $enabled = true ) {
- }
-
function getDBname() {
return $this->mDBname;
}
@@ -1371,6 +1556,14 @@ class DatabaseOracle extends DatabaseBase {
return $this->mServer;
}
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = array()
+ ) {
+ $fld = "LISTAGG($field," . $this->addQuotes( $delim ) . ") WITHIN GROUP (ORDER BY $field)";
+
+ return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
+ }
+
public function getSearchEngine() {
return 'SearchOracle';
}
@@ -1378,5 +1571,4 @@ class DatabaseOracle extends DatabaseBase {
public function getInfinity() {
return '31-12-2030 12:00:00.000000';
}
-
-} // end DatabaseOracle class
+}
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index 0bd966ba..ce14d7a9 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -26,9 +26,9 @@ class PostgresField implements Field {
$has_default, $default;
/**
- * @param $db DatabaseBase
- * @param $table
- * @param $field
+ * @param DatabaseBase $db
+ * @param string $table
+ * @param string $field
* @return null|PostgresField
*/
static function fromText( $db, $table, $field ) {
@@ -79,6 +79,7 @@ SQL;
$n->conname = $row->conname;
$n->has_default = ( $row->atthasdef === 't' );
$n->default = $row->adsrc;
+
return $n;
}
@@ -113,8 +114,10 @@ SQL;
function conname() {
return $this->conname;
}
+
/**
* @since 1.19
+ * @return bool|mixed
*/
function defaultValue() {
if ( $this->has_default ) {
@@ -123,7 +126,6 @@ SQL;
return false;
}
}
-
}
/**
@@ -134,8 +136,7 @@ SQL;
* @ingroup Database
*/
class PostgresTransactionState {
-
- static $WATCHED = array(
+ private static $WATCHED = array(
array(
"desc" => "%s: Connection state changed from %s -> %s\n",
"states" => array(
@@ -155,6 +156,12 @@ class PostgresTransactionState {
)
);
+ /** @var array */
+ private $mNewState;
+
+ /** @var array */
+ private $mCurrentState;
+
public function __construct( $conn ) {
$this->mConn = $conn;
$this->update();
@@ -181,7 +188,6 @@ class PostgresTransactionState {
}
$old = next( $this->mCurrentState );
$new = next( $this->mNewState );
-
}
}
}
@@ -211,21 +217,23 @@ class PostgresTransactionState {
* @since 1.19
*/
class SavepointPostgres {
- /**
- * Establish a savepoint within a transaction
- */
+ /** @var DatabaseBase Establish a savepoint within a transaction */
protected $dbw;
protected $id;
protected $didbegin;
+ /**
+ * @param DatabaseBase $dbw
+ * @param int $id
+ */
public function __construct( $dbw, $id ) {
$this->dbw = $dbw;
$this->id = $id;
$this->didbegin = false;
/* If we are not in a transaction, we need to be for savepoint trickery */
if ( !$dbw->trxLevel() ) {
- $dbw->begin( "FOR SAVEPOINT" );
- $this->didbegin = true;
+ $dbw->begin( "FOR SAVEPOINT" );
+ $this->didbegin = true;
}
}
@@ -247,10 +255,10 @@ class SavepointPostgres {
global $wgDebugDBTransactions;
if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
if ( $wgDebugDBTransactions ) {
- wfDebug( sprintf ( $msg_ok, $this->id ) );
+ wfDebug( sprintf( $msg_ok, $this->id ) );
}
} else {
- wfDebug( sprintf ( $msg_failed, $this->id ) );
+ wfDebug( sprintf( $msg_failed, $this->id ) );
}
}
@@ -284,10 +292,26 @@ class SavepointPostgres {
* @ingroup Database
*/
class DatabasePostgres extends DatabaseBase {
- var $mInsertId = null;
- var $mLastResult = null;
- var $numeric_version = null;
- var $mAffectedRows = null;
+ /** @var resource */
+ protected $mLastResult = null;
+
+ /** @var int The number of rows affected as an integer */
+ protected $mAffectedRows = null;
+
+ /** @var int */
+ private $mInsertId = null;
+
+ /** @var float|string */
+ private $numericVersion = null;
+
+ /** @var string Connect string to open a PostgreSQL connection */
+ private $connectString;
+
+ /** @var PostgresTransactionState */
+ private $mTransactionState;
+
+ /** @var string */
+ private $mCoreSchema;
function getType() {
return 'postgres';
@@ -296,32 +320,42 @@ class DatabasePostgres extends DatabaseBase {
function cascadingDeletes() {
return true;
}
+
function cleanupTriggers() {
return true;
}
+
function strictIPs() {
return true;
}
+
function realTimestamps() {
return true;
}
+
function implicitGroupby() {
return false;
}
+
function implicitOrderby() {
return false;
}
+
function searchableIPs() {
return true;
}
+
function functionalIndexes() {
return true;
}
function hasConstraint( $name ) {
- $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" .
- pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
- $res = $this->doQuery( $SQL );
+ $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
+ "WHERE c.connamespace = n.oid AND conname = '" .
+ pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" .
+ pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
+ $res = $this->doQuery( $sql );
+
return $this->numRows( $res );
}
@@ -331,19 +365,24 @@ class DatabasePostgres extends DatabaseBase {
* @param string $user
* @param string $password
* @param string $dbName
- * @throws DBConnectionError
+ * @throws DBConnectionError|Exception
* @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
# Test for Postgres support, to avoid suppressed fatal error
if ( !function_exists( 'pg_connect' ) ) {
- throw new DBConnectionError( $this, "Postgres functions missing, have you compiled PHP with the --with-pgsql option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
+ throw new DBConnectionError(
+ $this,
+ "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
+ "option? (Note: if you recently installed PHP, you may need to restart your\n" .
+ "webserver and database)\n"
+ );
}
global $wgDBport;
if ( !strlen( $user ) ) { # e.g. the class is being loaded
- return;
+ return null;
}
$this->mServer = $server;
@@ -370,12 +409,20 @@ class DatabasePostgres extends DatabaseBase {
$this->connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
$this->close();
$this->installErrorHandler();
- $this->mConn = pg_connect( $this->connectString );
+
+ try {
+ $this->mConn = pg_connect( $this->connectString );
+ } catch ( Exception $ex ) {
+ $this->restoreErrorHandler();
+ throw $ex;
+ }
+
$phpError = $this->restoreErrorHandler();
if ( !$this->mConn ) {
wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
+ wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " .
+ substr( $password, 0, 3 ) . "...\n" );
wfDebug( $this->lastError() . "\n" );
throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
}
@@ -406,7 +453,8 @@ class DatabasePostgres extends DatabaseBase {
/**
* Postgres doesn't support selectDB in the same way MySQL does. So if the
* DB name doesn't match the open connection, open a new one
- * @return
+ * @param string $db
+ * @return bool
*/
function selectDB( $db ) {
if ( $this->mDBname !== $db ) {
@@ -421,6 +469,7 @@ class DatabasePostgres extends DatabaseBase {
foreach ( $vars as $name => $value ) {
$s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
}
+
return $s;
}
@@ -447,38 +496,44 @@ class DatabasePostgres extends DatabaseBase {
if ( pg_result_error( $this->mLastResult ) ) {
return false;
}
+
return $this->mLastResult;
}
protected function dumpError() {
- $diags = array( PGSQL_DIAG_SEVERITY,
- PGSQL_DIAG_SQLSTATE,
- PGSQL_DIAG_MESSAGE_PRIMARY,
- PGSQL_DIAG_MESSAGE_DETAIL,
- PGSQL_DIAG_MESSAGE_HINT,
- PGSQL_DIAG_STATEMENT_POSITION,
- PGSQL_DIAG_INTERNAL_POSITION,
- PGSQL_DIAG_INTERNAL_QUERY,
- PGSQL_DIAG_CONTEXT,
- PGSQL_DIAG_SOURCE_FILE,
- PGSQL_DIAG_SOURCE_LINE,
- PGSQL_DIAG_SOURCE_FUNCTION );
+ $diags = array(
+ PGSQL_DIAG_SEVERITY,
+ PGSQL_DIAG_SQLSTATE,
+ PGSQL_DIAG_MESSAGE_PRIMARY,
+ PGSQL_DIAG_MESSAGE_DETAIL,
+ PGSQL_DIAG_MESSAGE_HINT,
+ PGSQL_DIAG_STATEMENT_POSITION,
+ PGSQL_DIAG_INTERNAL_POSITION,
+ PGSQL_DIAG_INTERNAL_QUERY,
+ PGSQL_DIAG_CONTEXT,
+ PGSQL_DIAG_SOURCE_FILE,
+ PGSQL_DIAG_SOURCE_LINE,
+ PGSQL_DIAG_SOURCE_FUNCTION
+ );
foreach ( $diags as $d ) {
- wfDebug( sprintf( "PgSQL ERROR(%d): %s\n", $d, pg_result_error_field( $this->mLastResult, $d ) ) );
+ wfDebug( sprintf( "PgSQL ERROR(%d): %s\n",
+ $d, pg_result_error_field( $this->mLastResult, $d ) ) );
}
}
function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- /* Transaction stays in the ERROR state until rolledback */
if ( $tempIgnore ) {
/* Check for constraint violation */
if ( $errno === '23505' ) {
parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
+
return;
}
}
- /* Don't ignore serious errors */
- $this->rollback( __METHOD__ );
+ /* Transaction stays in the ERROR state until rolledback */
+ if ( $this->mTrxLevel ) {
+ $this->rollback( __METHOD__ );
+ };
parent::reportQueryError( $error, $errno, $sql, $fname, false );
}
@@ -486,6 +541,10 @@ class DatabasePostgres extends DatabaseBase {
return $this->query( $sql, $fname, true );
}
+ /**
+ * @param stdClass|ResultWrapper $res
+ * @throws DBUnexpectedError
+ */
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -498,6 +557,11 @@ class DatabasePostgres extends DatabaseBase {
}
}
+ /**
+ * @param ResultWrapper|stdClass $res
+ * @return stdClass
+ * @throws DBUnexpectedError
+ */
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -510,8 +574,12 @@ class DatabasePostgres extends DatabaseBase {
# @todo hashar: not sure if the following test really trigger if the object
# fetching failed.
if ( pg_last_error( $this->mConn ) ) {
- throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+ );
}
+
return $row;
}
@@ -523,8 +591,12 @@ class DatabasePostgres extends DatabaseBase {
$row = pg_fetch_array( $res );
wfRestoreWarnings();
if ( pg_last_error( $this->mConn ) ) {
- throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+ );
}
+
return $row;
}
@@ -536,8 +608,12 @@ class DatabasePostgres extends DatabaseBase {
$n = pg_num_rows( $res );
wfRestoreWarnings();
if ( pg_last_error( $this->mConn ) ) {
- throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+ );
}
+
return $n;
}
@@ -545,6 +621,7 @@ class DatabasePostgres extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return pg_num_fields( $res );
}
@@ -552,6 +629,7 @@ class DatabasePostgres extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return pg_field_name( $res, $n );
}
@@ -559,16 +637,22 @@ class DatabasePostgres extends DatabaseBase {
* Return the result of the last call to nextSequenceValue();
* This must be called after nextSequenceValue().
*
- * @return integer|null
+ * @return int|null
*/
function insertId() {
return $this->mInsertId;
}
+ /**
+ * @param mixed $res
+ * @param int $row
+ * @return bool
+ */
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return pg_result_seek( $res, $row );
}
@@ -583,6 +667,7 @@ class DatabasePostgres extends DatabaseBase {
return 'No database connection';
}
}
+
function lastErrno() {
if ( $this->mLastResult ) {
return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
@@ -599,6 +684,7 @@ class DatabasePostgres extends DatabaseBase {
if ( empty( $this->mLastResult ) ) {
return 0;
}
+
return pg_affected_rows( $this->mLastResult );
}
@@ -608,9 +694,17 @@ class DatabasePostgres extends DatabaseBase {
* This is not necessarily an accurate estimate, so use sparingly
* Returns -1 if count cannot be found
* Takes same arguments as Database::select()
+ *
+ * @param string $table
+ * @param string $vars
+ * @param string $conds
+ * @param string $fname
+ * @param array $options
* @return int
*/
- function estimateRowCount( $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() ) {
+ function estimateRowCount( $table, $vars = '*', $conds = '',
+ $fname = __METHOD__, $options = array()
+ ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
@@ -621,12 +715,17 @@ class DatabasePostgres extends DatabaseBase {
$rows = $count[1];
}
}
+
return $rows;
}
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
+ *
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool|null
*/
function indexInfo( $table, $index, $fname = __METHOD__ ) {
@@ -640,6 +739,7 @@ class DatabasePostgres extends DatabaseBase {
return $row;
}
}
+
return false;
}
@@ -647,7 +747,9 @@ class DatabasePostgres extends DatabaseBase {
* Returns is of attributes used in index
*
* @since 1.19
- * @return Array
+ * @param string $index
+ * @param bool|string $schema
+ * @return array
*/
function indexAttributes( $index, $schema = false ) {
if ( $schema === false ) {
@@ -702,6 +804,7 @@ __INDEXATTR__;
} else {
return null;
}
+
return $a;
}
@@ -714,10 +817,8 @@ __INDEXATTR__;
if ( !$res ) {
return null;
}
- foreach ( $res as $row ) {
- return true;
- }
- return false;
+
+ return $res->numRows() > 0;
}
/**
@@ -727,10 +828,15 @@ __INDEXATTR__;
* In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
* can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to do
* so causes a DB error. This wrapper checks which tables can be locked and adjusts it accordingly.
+ *
+ * MySQL uses "ORDER BY NULL" as an optimization hint, but that syntax is illegal in PostgreSQL.
+ * @see DatabaseBase::selectSQLText
*/
- function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() ) {
+ function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = array(), $join_conds = array()
+ ) {
if ( is_array( $options ) ) {
- $forUpdateKey = array_search( 'FOR UPDATE', $options );
+ $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
if ( $forUpdateKey !== false && $join_conds ) {
unset( $options[$forUpdateKey] );
@@ -740,6 +846,10 @@ __INDEXATTR__;
}
}
}
+
+ if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
+ unset( $options['ORDER BY'] );
+ }
}
return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -751,11 +861,10 @@ __INDEXATTR__;
* $args may be a single associative array, or an array of these with numeric keys,
* for multi-row insert (Postgres version 8.2 and above only).
*
- * @param $table String: Name of the table to insert to.
- * @param $args Array: Items to insert into the table.
- * @param $fname String: Name of the function, for profiling
- * @param string $options or Array. Valid options: IGNORE
- *
+ * @param string $table Name of the table to insert to.
+ * @param array $args Items to insert into the table.
+ * @param string $fname Name of the function, for profiling
+ * @param array|string $options String or array. Valid options: IGNORE
* @return bool Success of insert operation. IGNORE always returns true.
*/
function insert( $table, $args, $fname = __METHOD__, $options = array() ) {
@@ -764,7 +873,7 @@ __INDEXATTR__;
}
$table = $this->tableName( $table );
- if ( !isset( $this->numeric_version ) ) {
+ if ( !isset( $this->numericVersion ) ) {
$this->getServerVersion();
}
@@ -793,7 +902,7 @@ __INDEXATTR__;
$sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
if ( $multi ) {
- if ( $this->numeric_version >= 8.2 && !$savepoint ) {
+ if ( $this->numericVersion >= 8.2 && !$savepoint ) {
$first = true;
foreach ( $args as $row ) {
if ( $first ) {
@@ -853,7 +962,7 @@ __INDEXATTR__;
}
}
if ( $savepoint ) {
- $olde = error_reporting( $olde );
+ error_reporting( $olde );
$savepoint->commit();
// Set the affected row count for the whole operation
@@ -869,15 +978,23 @@ __INDEXATTR__;
/**
* INSERT SELECT wrapper
* $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
- * Source items may be literals rather then field names, but strings should be quoted with Database::addQuotes()
+ * Source items may be literals rather then field names, but strings should
+ * be quoted with Database::addQuotes()
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
* @todo FIXME: Implement this a little better (seperate select/insert)?
+ *
+ * @param string $destTable
+ * @param array|string $srcTable
+ * @param array $varMap
+ * @param array $conds
+ * @param string $fname
+ * @param array $insertOptions
+ * @param array $selectOptions
* @return bool
*/
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array() )
- {
+ $insertOptions = array(), $selectOptions = array() ) {
$destTable = $this->tableName( $destTable );
if ( !is_array( $insertOptions ) ) {
@@ -907,8 +1024,8 @@ __INDEXATTR__;
}
$sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
- " SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex";
+ " SELECT $startOpts " . implode( ',', $varMap ) .
+ " FROM $srcTable $useIndex";
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
@@ -925,7 +1042,7 @@ __INDEXATTR__;
$savepoint->release();
$numrowsinserted++;
}
- $olde = error_reporting( $olde );
+ error_reporting( $olde );
$savepoint->commit();
// Set the affected row count for the whole operation
@@ -957,25 +1074,31 @@ __INDEXATTR__;
/**
* Return the next in a sequence, save the value for retrieval via insertId()
- * @return null
+ *
+ * @param string $seqName
+ * @return int|null
*/
function nextSequenceValue( $seqName ) {
$safeseq = str_replace( "'", "''", $seqName );
$res = $this->query( "SELECT nextval('$safeseq')" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
+
return $this->mInsertId;
}
/**
* Return the current value of a sequence. Assumes it has been nextval'ed in this session.
- * @return
+ *
+ * @param string $seqName
+ * @return int
*/
function currentSequenceValue( $seqName ) {
$safeseq = str_replace( "'", "''", $seqName );
$res = $this->query( "SELECT currval('$safeseq')" );
$row = $this->fetchRow( $res );
$currval = $row[0];
+
return $currval;
}
@@ -993,6 +1116,7 @@ __INDEXATTR__;
} else {
$size = $row->size;
}
+
return $size;
}
@@ -1007,7 +1131,9 @@ __INDEXATTR__;
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
$newName = $this->addIdentifierQuotes( $newName );
$oldName = $this->addIdentifierQuotes( $oldName );
- return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
+
+ return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
+ "(LIKE $oldName INCLUDING DEFAULTS)", $fname );
}
function listTables( $prefix = null, $fname = __METHOD__ ) {
@@ -1030,7 +1156,7 @@ __INDEXATTR__;
return wfTimestamp( TS_POSTGRES, $ts );
}
- /*
+ /**
* Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12
* to http://www.php.net/manual/en/ref.pgsql.php
*
@@ -1042,10 +1168,10 @@ __INDEXATTR__;
* This should really be handled by PHP PostgreSQL module
*
* @since 1.19
- * @param $text string: postgreql array returned in a text form like {a,b}
- * @param $output string
- * @param $limit int
- * @param $offset int
+ * @param string $text Postgreql array returned in a text form like {a,b}
+ * @param string $output
+ * @param int $limit
+ * @param int $offset
* @return string
*/
function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
@@ -1062,8 +1188,8 @@ __INDEXATTR__;
$text, $match, 0, $offset );
$offset += strlen( $match[0] );
$output[] = ( '"' != $match[1][0]
- ? $match[1]
- : stripcslashes( substr( $match[1], 1, -1 ) ) );
+ ? $match[1]
+ : stripcslashes( substr( $match[1], 1, -1 ) ) );
if ( '},' == $match[3] ) {
return $output;
}
@@ -1071,18 +1197,22 @@ __INDEXATTR__;
$offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
}
} while ( $limit > $offset );
+
return $output;
}
/**
* Return aggregated value function call
+ * @param array $valuedata
+ * @param string $valuename
+ * @return array
*/
public function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
}
/**
- * @return string wikitext of a link to the server software's web site
+ * @return string Wikitext of a link to the server software's web site
*/
public function getSoftwareLink() {
return '[{{int:version-db-postgres-url}} PostgreSQL]';
@@ -1093,11 +1223,12 @@ __INDEXATTR__;
* Needs transaction
*
* @since 1.19
- * @return string return default schema for the current session
+ * @return string Default schema for the current session
*/
function getCurrentSchema() {
$res = $this->query( "SELECT current_schema()", __METHOD__ );
$row = $this->fetchRow( $res );
+
return $row[0];
}
@@ -1109,13 +1240,15 @@ __INDEXATTR__;
* @see getSearchPath()
* @see setSearchPath()
* @since 1.19
- * @return array list of actual schemas for the current sesson
+ * @return array List of actual schemas for the current sesson
*/
function getSchemas() {
$res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
$row = $this->fetchRow( $res );
$schemas = array();
+
/* PHP pgsql support does not support array type, "{a,b}" string is returned */
+
return $this->pg_array_parse( $row[0], $schemas );
}
@@ -1126,12 +1259,14 @@ __INDEXATTR__;
* Needs transaction
*
* @since 1.19
- * @return array how to search for table names schemas for the current user
+ * @return array How to search for table names schemas for the current user
*/
function getSearchPath() {
$res = $this->query( "SHOW search_path", __METHOD__ );
$row = $this->fetchRow( $res );
+
/* PostgreSQL returns SHOW values as strings */
+
return explode( ",", $row[0] );
}
@@ -1140,7 +1275,7 @@ __INDEXATTR__;
* Values may contain magic keywords like "$user"
* @since 1.19
*
- * @param $search_path array list of schemas to be searched by default
+ * @param array $search_path List of schemas to be searched by default
*/
function setSearchPath( $search_path ) {
$this->query( "SET search_path = " . implode( ", ", $search_path ) );
@@ -1157,14 +1292,15 @@ __INDEXATTR__;
* This will be also called by the installer after the schema is created
*
* @since 1.19
- * @param $desired_schema string
+ *
+ * @param string $desiredSchema
*/
- function determineCoreSchema( $desired_schema ) {
+ function determineCoreSchema( $desiredSchema ) {
$this->begin( __METHOD__ );
- if ( $this->schemaExists( $desired_schema ) ) {
- if ( in_array( $desired_schema, $this->getSchemas() ) ) {
- $this->mCoreSchema = $desired_schema;
- wfDebug( "Schema \"" . $desired_schema . "\" already in the search path\n" );
+ if ( $this->schemaExists( $desiredSchema ) ) {
+ if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
+ $this->mCoreSchema = $desiredSchema;
+ wfDebug( "Schema \"" . $desiredSchema . "\" already in the search path\n" );
} else {
/**
* Prepend our schema (e.g. 'mediawiki') in front
@@ -1173,14 +1309,15 @@ __INDEXATTR__;
*/
$search_path = $this->getSearchPath();
array_unshift( $search_path,
- $this->addIdentifierQuotes( $desired_schema ));
+ $this->addIdentifierQuotes( $desiredSchema ) );
$this->setSearchPath( $search_path );
- $this->mCoreSchema = $desired_schema;
- wfDebug( "Schema \"" . $desired_schema . "\" added to the search path\n" );
+ $this->mCoreSchema = $desiredSchema;
+ wfDebug( "Schema \"" . $desiredSchema . "\" added to the search path\n" );
}
} else {
$this->mCoreSchema = $this->getCurrentSchema();
- wfDebug( "Schema \"" . $desired_schema . "\" not found, using current \"" . $this->mCoreSchema . "\"\n" );
+ wfDebug( "Schema \"" . $desiredSchema . "\" not found, using current \"" .
+ $this->mCoreSchema . "\"\n" );
}
/* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
$this->commit( __METHOD__ );
@@ -1190,7 +1327,7 @@ __INDEXATTR__;
* Return schema name fore core MediaWiki tables
*
* @since 1.19
- * @return string core schema name
+ * @return string Core schema name
*/
function getCoreSchema() {
return $this->mCoreSchema;
@@ -1200,25 +1337,29 @@ __INDEXATTR__;
* @return string Version information from the database
*/
function getServerVersion() {
- if ( !isset( $this->numeric_version ) ) {
+ if ( !isset( $this->numericVersion ) ) {
$versionInfo = pg_version( $this->mConn );
if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
// Old client, abort install
- $this->numeric_version = '7.3 or earlier';
+ $this->numericVersion = '7.3 or earlier';
} elseif ( isset( $versionInfo['server'] ) ) {
// Normal client
- $this->numeric_version = $versionInfo['server'];
+ $this->numericVersion = $versionInfo['server'];
} else {
// Bug 16937: broken pgsql extension from PHP<5.3
- $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' );
+ $this->numericVersion = pg_parameter_status( $this->mConn, 'server_version' );
}
}
- return $this->numeric_version;
+
+ return $this->numericVersion;
}
/**
* Query whether a given relation exists (in the given schema, or the
* default mw one if not given)
+ * @param string $table
+ * @param array|string $types
+ * @param bool|string $schema
* @return bool
*/
function relationExists( $table, $types, $schema = false ) {
@@ -1231,17 +1372,21 @@ __INDEXATTR__;
$table = $this->realTableName( $table, 'raw' );
$etable = $this->addQuotes( $table );
$eschema = $this->addQuotes( $schema );
- $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
+ $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
. "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
. "AND c.relkind IN ('" . implode( "','", $types ) . "')";
- $res = $this->query( $SQL );
+ $res = $this->query( $sql );
$count = $res ? $res->numRows() : 0;
+
return (bool)$count;
}
/**
* For backward compatibility, this function checks both tables and
* views.
+ * @param string $table
+ * @param string $fname
+ * @param bool|string $schema
* @return bool
*/
function tableExists( $table, $fname = __METHOD__, $schema = false ) {
@@ -1271,6 +1416,7 @@ SQL;
return null;
}
$rows = $res->numRows();
+
return $rows;
}
@@ -1282,41 +1428,47 @@ SQL;
'schemaname' => $this->getCoreSchema()
)
);
+
return $exists === $rule;
}
function constraintExists( $table, $constraint ) {
- $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
- "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
+ $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
+ "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
$this->addQuotes( $this->getCoreSchema() ),
$this->addQuotes( $table ),
$this->addQuotes( $constraint )
);
- $res = $this->query( $SQL );
+ $res = $this->query( $sql );
if ( !$res ) {
return null;
}
$rows = $res->numRows();
+
return $rows;
}
/**
* Query whether a given schema exists. Returns true if it does, false if it doesn't.
+ * @param string $schema
* @return bool
*/
function schemaExists( $schema ) {
$exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
array( 'nspname' => $schema ), __METHOD__ );
+
return (bool)$exists;
}
/**
* Returns true if a given role (i.e. user) exists, false otherwise.
+ * @param string $roleName
* @return bool
*/
function roleExists( $roleName ) {
$exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
array( 'rolname' => $roleName ), __METHOD__ );
+
return (bool)$exists;
}
@@ -1326,17 +1478,20 @@ SQL;
/**
* pg_field_type() wrapper
+ * @param ResultWrapper|resource $res ResultWrapper or PostgreSQL query result resource
+ * @param int $index Field number, starting from 0
* @return string
*/
function fieldType( $res, $index ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return pg_field_type( $res, $index );
}
/**
- * @param $b
+ * @param string $b
* @return Blob
*/
function encodeBlob( $b ) {
@@ -1347,6 +1502,7 @@ SQL;
if ( $b instanceof Blob ) {
$b = $b->fetch();
}
+
return pg_unescape_bytea( $b );
}
@@ -1355,7 +1511,7 @@ SQL;
}
/**
- * @param $s null|bool|Blob
+ * @param null|bool|Blob $s
* @return int|string
*/
function addQuotes( $s ) {
@@ -1366,6 +1522,7 @@ SQL;
} elseif ( $s instanceof Blob ) {
return "'" . $s->fetch( $s ) . "'";
}
+
return "'" . pg_escape_string( $this->mConn, $s ) . "'";
}
@@ -1373,21 +1530,18 @@ SQL;
* Postgres specific version of replaceVars.
* Calls the parent version in Database.php
*
- * @private
- *
* @param string $ins SQL string, read from a stream (usually tables.sql)
- *
* @return string SQL string
*/
protected function replaceVars( $ins ) {
$ins = parent::replaceVars( $ins );
- if ( $this->numeric_version >= 8.3 ) {
+ if ( $this->numericVersion >= 8.3 ) {
// Thanks for not providing backwards-compatibility, 8.3
$ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
}
- if ( $this->numeric_version <= 8.1 ) { // Our minimum version
+ if ( $this->numericVersion <= 8.1 ) { // Our minimum version
$ins = str_replace( 'USING gin', 'USING gist', $ins );
}
@@ -1397,10 +1551,8 @@ SQL;
/**
* Various select options
*
- * @private
- *
- * @param array $options an associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
+ * @param array $options An associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
* @return array
*/
function makeSelectOptions( $options ) {
@@ -1425,8 +1577,9 @@ SQL;
//}
if ( isset( $options['FOR UPDATE'] ) ) {
- $postLimitTail .= ' FOR UPDATE OF ' . implode( ', ', $options['FOR UPDATE'] );
- } else if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+ $postLimitTail .= ' FOR UPDATE OF ' .
+ implode( ', ', array_map( array( &$this, 'tableName' ), $options['FOR UPDATE'] ) );
+ } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
$postLimitTail .= ' FOR UPDATE';
}
@@ -1437,9 +1590,6 @@ SQL;
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
- function setFakeMaster( $enabled = true ) {
- }
-
function getDBname() {
return $this->mDBname;
}
@@ -1452,6 +1602,14 @@ SQL;
return implode( ' || ', $stringList );
}
+ public function buildGroupConcatField(
+ $delimiter, $table, $field, $conds = '', $options = array(), $join_conds = array()
+ ) {
+ $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
+
+ return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
+ }
+
public function getSearchEngine() {
return 'SearchPostgres';
}
@@ -1461,11 +1619,11 @@ SQL;
if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
if ( $this->delimiter ) {
$this->delimiter = false;
- }
- else {
+ } else {
$this->delimiter = ';';
}
}
+
return parent::streamStatementEnd( $sql, $newLine );
}
@@ -1473,9 +1631,9 @@ SQL;
* Check to see if a named lock is available. This is non-blocking.
* See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
*
- * @param string $lockName name of lock to poll
- * @param string $method name of method calling us
- * @return Boolean
+ * @param string $lockName Name of lock to poll
+ * @param string $method Name of method calling us
+ * @return bool
* @since 1.20
*/
public function lockIsFree( $lockName, $method ) {
@@ -1483,14 +1641,15 @@ SQL;
$result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
$row = $this->fetchObject( $result );
+
return ( $row->lockstatus === 't' );
}
/**
* See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
- * @param $lockName string
- * @param $method string
- * @param $timeout int
+ * @param string $lockName
+ * @param string $method
+ * @param int $timeout
* @return bool
*/
public function lock( $lockName, $method, $timeout = 5 ) {
@@ -1506,19 +1665,22 @@ SQL;
}
}
wfDebug( __METHOD__ . " failed to acquire lock\n" );
+
return false;
}
/**
- * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
- * @param $lockName string
- * @param $method string
+ * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM
+ * PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+ * @param string $lockName
+ * @param string $method
* @return bool
*/
public function unlock( $lockName, $method ) {
$key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
$result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
$row = $this->fetchObject( $result );
+
return ( $row->lockstatus === 't' );
}
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index 3e034649..dd2e813e 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -26,38 +26,53 @@
* @ingroup Database
*/
class DatabaseSqlite extends DatabaseBase {
-
+ /** @var bool Whether full text is enabled */
private static $fulltextEnabled = null;
- var $mAffectedRows;
- var $mLastResult;
- var $mDatabaseFile;
- var $mName;
+ /** @var string File name for SQLite database file */
+ public $mDatabaseFile;
- /**
- * @var PDO
- */
+ /** @var int The number of rows affected as an integer */
+ protected $mAffectedRows;
+
+ /** @var resource */
+ protected $mLastResult;
+
+ /** @var PDO */
protected $mConn;
- /**
- * Constructor.
- * Parameters $server, $user and $password are not used.
- * @param $server string
- * @param $user string
- * @param $password string
- * @param $dbName string
- * @param $flags int
- */
- function __construct( $server = false, $user = false, $password = false, $dbName = false, $flags = 0 ) {
- $this->mName = $dbName;
- parent::__construct( $server, $user, $password, $dbName, $flags );
+ /** @var FSLockManager (hopefully on the same server as the DB) */
+ protected $lockMgr;
+
+ function __construct( $p = null ) {
+ global $wgSharedDB, $wgSQLiteDataDir;
+
+ if ( !is_array( $p ) ) { // legacy calling pattern
+ wfDeprecated( __METHOD__ . " method called without parameter array.", "1.22" );
+ $args = func_get_args();
+ $p = array(
+ 'host' => isset( $args[0] ) ? $args[0] : false,
+ 'user' => isset( $args[1] ) ? $args[1] : false,
+ 'password' => isset( $args[2] ) ? $args[2] : false,
+ 'dbname' => isset( $args[3] ) ? $args[3] : false,
+ 'flags' => isset( $args[4] ) ? $args[4] : 0,
+ 'tablePrefix' => isset( $args[5] ) ? $args[5] : 'get from global',
+ 'schema' => 'get from global',
+ 'foreign' => isset( $args[6] ) ? $args[6] : false
+ );
+ }
+ $this->mDBname = $p['dbname'];
+ parent::__construct( $p );
// parent doesn't open when $user is false, but we can work with $dbName
- if ( $dbName && !$this->isOpen() ) {
- global $wgSharedDB;
- if ( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) {
- $this->attachDatabase( $wgSharedDB );
+ if ( $p['dbname'] && !$this->isOpen() ) {
+ if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
+ if ( $wgSharedDB ) {
+ $this->attachDatabase( $wgSharedDB );
+ }
}
}
+
+ $this->lockMgr = new FSLockManager( array( 'lockDirectory' => "$wgSQLiteDataDir/locks" ) );
}
/**
@@ -97,18 +112,20 @@ class DatabaseSqlite extends DatabaseBase {
throw new DBConnectionError( $this, "SQLite database not accessible" );
}
$this->openFile( $fileName );
+
return $this->mConn;
}
/**
* Opens a database file
*
- * @param $fileName string
- *
+ * @param string $fileName
* @throws DBConnectionError
* @return PDO|bool SQL connection or false if failed
*/
function openFile( $fileName ) {
+ $err = false;
+
$this->mDatabaseFile = $fileName;
try {
if ( $this->mFlags & DBO_PERSISTENT ) {
@@ -120,18 +137,23 @@ class DatabaseSqlite extends DatabaseBase {
} catch ( PDOException $e ) {
$err = $e->getMessage();
}
+
if ( !$this->mConn ) {
wfDebug( "DB connection error: $err\n" );
throw new DBConnectionError( $this, $err );
}
+
$this->mOpened = !!$this->mConn;
# set error codes only, don't raise exceptions
if ( $this->mOpened ) {
$this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
# Enforce LIKE to be case sensitive, just like MySQL
$this->query( 'PRAGMA case_sensitive_like = 1' );
- return true;
+
+ return $this->mConn;
}
+
+ return false;
}
/**
@@ -140,6 +162,7 @@ class DatabaseSqlite extends DatabaseBase {
*/
protected function closeConnection() {
$this->mConn = null;
+
return true;
}
@@ -147,7 +170,7 @@ class DatabaseSqlite extends DatabaseBase {
* Generates a database file name. Explicitly public for installer.
* @param string $dir Directory where database resides
* @param string $dbName Database name
- * @return String
+ * @return string
*/
public static function generateFileName( $dir, $dbName ) {
return "$dir/$dbName.sqlite";
@@ -167,12 +190,13 @@ class DatabaseSqlite extends DatabaseBase {
self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
}
}
+
return self::$fulltextEnabled;
}
/**
* Returns version of currently supported SQLite fulltext search module or false if none present.
- * @return String
+ * @return string
*/
static function getFulltextSearchModule() {
static $cachedResult = null;
@@ -188,6 +212,7 @@ class DatabaseSqlite extends DatabaseBase {
$cachedResult = 'FTS3';
}
$db->close();
+
return $cachedResult;
}
@@ -195,10 +220,11 @@ class DatabaseSqlite extends DatabaseBase {
* Attaches external database to our connection, see http://sqlite.org/lang_attach.html
* for details.
*
- * @param string $name database name to be used in queries like SELECT foo FROM dbname.table
- * @param string $file database file name. If omitted, will be generated using $name and $wgSQLiteDataDir
- * @param string $fname calling function name
- *
+ * @param string $name Database name to be used in queries like
+ * SELECT foo FROM dbname.table
+ * @param bool|string $file Database file name. If omitted, will be generated
+ * using $name and $wgSQLiteDataDir
+ * @param string $fname Calling function name
* @return ResultWrapper
*/
function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
@@ -207,14 +233,14 @@ class DatabaseSqlite extends DatabaseBase {
$file = self::generateFileName( $wgSQLiteDataDir, $name );
}
$file = $this->addQuotes( $file );
+
return $this->query( "ATTACH DATABASE $file AS $name", $fname );
}
/**
* @see DatabaseBase::isWriteQuery()
*
- * @param $sql string
- *
+ * @param string $sql
* @return bool
*/
function isWriteQuery( $sql ) {
@@ -224,9 +250,8 @@ class DatabaseSqlite extends DatabaseBase {
/**
* SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
*
- * @param $sql string
- *
- * @return ResultWrapper
+ * @param string $sql
+ * @return bool|ResultWrapper
*/
protected function doQuery( $sql ) {
$res = $this->mConn->query( $sql );
@@ -237,11 +262,12 @@ class DatabaseSqlite extends DatabaseBase {
$this->mAffectedRows = $r->rowCount();
$res = new ResultWrapper( $this, $r->fetchAll() );
}
+
return $res;
}
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper|mixed $res
*/
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -252,8 +278,8 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $res ResultWrapper
- * @return object|bool
+ * @param ResultWrapper|array $res
+ * @return stdClass|bool
*/
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -274,11 +300,12 @@ class DatabaseSqlite extends DatabaseBase {
return $obj;
}
+
return false;
}
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper|mixed $res
* @return array|bool
*/
function fetchRow( $res ) {
@@ -290,51 +317,61 @@ class DatabaseSqlite extends DatabaseBase {
$cur = current( $r );
if ( is_array( $cur ) ) {
next( $r );
+
return $cur;
}
+
return false;
}
/**
* The PDO::Statement class implements the array interface so count() will work
*
- * @param $res ResultWrapper
- *
+ * @param ResultWrapper|array $res
* @return int
*/
function numRows( $res ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
+
return count( $r );
}
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper $res
* @return int
*/
function numFields( $res ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
- return is_array( $r ) ? count( $r[0] ) : 0;
+ if ( is_array( $r ) && count( $r ) > 0 ) {
+ // The size of the result array is twice the number of fields. (Bug: 65578)
+ return count( $r[0] ) / 2;
+ } else {
+ // If the result is empty return 0
+ return 0;
+ }
}
/**
- * @param $res ResultWrapper
- * @param $n
+ * @param ResultWrapper $res
+ * @param int $n
* @return bool
*/
function fieldName( $res, $n ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
if ( is_array( $r ) ) {
$keys = array_keys( $r[0] );
+
return $keys[$n];
}
+
return false;
}
/**
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
*
- * @param $name
- * @param $format String
+ * @param string $name
+ * @param string $format
* @return string
*/
function tableName( $name, $format = 'quoted' ) {
@@ -342,14 +379,14 @@ class DatabaseSqlite extends DatabaseBase {
if ( strpos( $name, 'sqlite_' ) === 0 ) {
return $name;
}
+
return str_replace( '"', '', parent::tableName( $name, $format ) );
}
/**
* Index names have DB scope
*
- * @param $index string
- *
+ * @param string $index
* @return string
*/
function indexName( $index ) {
@@ -367,8 +404,8 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $res ResultWrapper
- * @param $row
+ * @param ResultWrapper|array $res
+ * @param int $row
*/
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
@@ -392,6 +429,7 @@ class DatabaseSqlite extends DatabaseBase {
return "Cannot return last error, no db connection";
}
$e = $this->mConn->errorInfo();
+
return isset( $e[2] ) ? $e[2] : '';
}
@@ -403,6 +441,7 @@ class DatabaseSqlite extends DatabaseBase {
return "Cannot return last error, no db connection";
} else {
$info = $this->mConn->errorInfo();
+
return $info[1];
}
}
@@ -419,6 +458,9 @@ class DatabaseSqlite extends DatabaseBase {
* Returns false if the index does not exist
* - if errors are explicitly ignored, returns NULL on failure
*
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return array
*/
function indexInfo( $table, $index, $fname = __METHOD__ ) {
@@ -434,13 +476,14 @@ class DatabaseSqlite extends DatabaseBase {
foreach ( $res as $row ) {
$info[] = $row->name;
}
+
return $info;
}
/**
- * @param $table
- * @param $index
- * @param $fname string
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool|null
*/
function indexUnique( $table, $index, $fname = __METHOD__ ) {
@@ -460,14 +503,14 @@ class DatabaseSqlite extends DatabaseBase {
}
$firstPart = substr( $row->sql, 0, $indexPos );
$options = explode( ' ', $firstPart );
+
return in_array( 'UNIQUE', $options );
}
/**
* Filter the options used in SELECT statements
*
- * @param $options array
- *
+ * @param array $options
* @return array
*/
function makeSelectOptions( $options ) {
@@ -476,20 +519,23 @@ class DatabaseSqlite extends DatabaseBase {
$options[$k] = '';
}
}
+
return parent::makeSelectOptions( $options );
}
/**
- * @param $options array
+ * @param array $options
* @return string
*/
- function makeUpdateOptions( $options ) {
+ protected function makeUpdateOptionsArray( $options ) {
+ $options = parent::makeUpdateOptionsArray( $options );
$options = self::fixIgnore( $options );
- return parent::makeUpdateOptions( $options );
+
+ return $options;
}
/**
- * @param $options array
+ * @param array $options
* @return array
*/
static function fixIgnore( $options ) {
@@ -499,20 +545,26 @@ class DatabaseSqlite extends DatabaseBase {
$options[$k] = 'OR IGNORE';
}
}
+
return $options;
}
/**
- * @param $options array
+ * @param array $options
* @return string
*/
function makeInsertOptions( $options ) {
$options = self::fixIgnore( $options );
+
return parent::makeInsertOptions( $options );
}
/**
* Based on generic method (parent) with some prior SQLite-sepcific adjustments
+ * @param string $table
+ * @param array $a
+ * @param string $fname
+ * @param array $options
* @return bool
*/
function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
@@ -536,10 +588,10 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $table
- * @param $uniqueIndexes
- * @param $rows
- * @param $fname string
+ * @param string $table
+ * @param array $uniqueIndexes Unused
+ * @param string|array $rows
+ * @param string $fname
* @return bool|ResultWrapper
*/
function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
@@ -566,6 +618,8 @@ class DatabaseSqlite extends DatabaseBase {
* Returns the size of a text field, or -1 for "unlimited"
* In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though.
*
+ * @param string $table
+ * @param string $field
* @return int
*/
function textFieldSize( $table, $field ) {
@@ -580,12 +634,13 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $sqls
- * @param $all
+ * @param string $sqls
+ * @param bool $all Whether to "UNION ALL" or not
* @return string
*/
function unionQueries( $sqls, $all ) {
$glue = $all ? ' UNION ALL ' : ' UNION ';
+
return implode( $glue, $sqls );
}
@@ -611,7 +666,7 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @return string wikitext of a link to the server software's web site
+ * @return string Wikitext of a link to the server software's web site
*/
public function getSoftwareLink() {
return "[{{int:version-db-sqlite-url}} SQLite]";
@@ -622,6 +677,7 @@ class DatabaseSqlite extends DatabaseBase {
*/
function getServerVersion() {
$ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
+
return $ver;
}
@@ -629,15 +685,17 @@ class DatabaseSqlite extends DatabaseBase {
* @return string User-friendly database information
*/
public function getServerInfo() {
- return wfMessage( self::getFulltextSearchModule() ? 'sqlite-has-fts' : 'sqlite-no-fts', $this->getServerVersion() )->text();
+ return wfMessage( self::getFulltextSearchModule()
+ ? 'sqlite-has-fts'
+ : 'sqlite-no-fts', $this->getServerVersion() )->text();
}
/**
* Get information about a given field
* Returns false if the field does not exist.
*
- * @param $table string
- * @param $field string
+ * @param string $table
+ * @param string $field
* @return SQLiteField|bool False on failure
*/
function fieldInfo( $table, $field ) {
@@ -649,6 +707,7 @@ class DatabaseSqlite extends DatabaseBase {
return new SQLiteField( $row, $tableName );
}
}
+
return false;
}
@@ -685,15 +744,15 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $s string
+ * @param string $s
* @return string
*/
function strencode( $s ) {
- return substr( $this->addQuotes( $s ), 1, - 1 );
+ return substr( $this->addQuotes( $s ), 1, -1 );
}
/**
- * @param $b
+ * @param string $b
* @return Blob
*/
function encodeBlob( $b ) {
@@ -701,18 +760,19 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $b Blob|string
+ * @param Blob|string $b
* @return string
*/
function decodeBlob( $b ) {
if ( $b instanceof Blob ) {
$b = $b->fetch();
}
+
return $b;
}
/**
- * @param $s Blob|string
+ * @param Blob|string $s
* @return string
*/
function addQuotes( $s ) {
@@ -741,6 +801,7 @@ class DatabaseSqlite extends DatabaseBase {
if ( count( $params ) > 0 && is_array( $params[0] ) ) {
$params = $params[0];
}
+
return parent::buildLike( $params ) . "ESCAPE '\' ";
}
@@ -753,16 +814,18 @@ class DatabaseSqlite extends DatabaseBase {
/**
* No-op version of deadlockLoop
+ *
* @return mixed
*/
public function deadlockLoop( /*...*/ ) {
$args = func_get_args();
$function = array_shift( $args );
+
return call_user_func_array( $function, $args );
}
/**
- * @param $s string
+ * @param string $s
* @return string
*/
protected function replaceVars( $s ) {
@@ -777,7 +840,11 @@ class DatabaseSqlite extends DatabaseBase {
// INT -> INTEGER
$s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
// floating point types -> REAL
- $s = preg_replace( '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i', 'REAL', $s );
+ $s = preg_replace(
+ '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i',
+ 'REAL',
+ $s
+ );
// varchar -> TEXT
$s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
// TEXT normalization
@@ -803,37 +870,73 @@ class DatabaseSqlite extends DatabaseBase {
$s = preg_replace( '/\(\d+\)/', '', $s );
// No FULLTEXT
$s = preg_replace( '/\bfulltext\b/i', '', $s );
+ } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) {
+ // DROP INDEX is database-wide, not table-specific, so no ON <table> clause.
+ $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s );
+ } elseif ( preg_match( '/^\s*INSERT IGNORE\b/i', $s ) ) {
+ // INSERT IGNORE --> INSERT OR IGNORE
+ $s = preg_replace( '/^\s*INSERT IGNORE\b/i', 'INSERT OR IGNORE', $s );
}
+
return $s;
}
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ global $wgSQLiteDataDir;
+
+ if ( !is_dir( "$wgSQLiteDataDir/locks" ) ) { // create dir as needed
+ if ( !is_writable( $wgSQLiteDataDir ) || !mkdir( "$wgSQLiteDataDir/locks" ) ) {
+ throw new DBError( "Cannot create directory \"$wgSQLiteDataDir/locks\"." );
+ }
+ }
+
+ return $this->lockMgr->lock( array( $lockName ), LockManager::LOCK_EX, $timeout )->isOK();
+ }
+
+ public function unlock( $lockName, $method ) {
+ return $this->lockMgr->unlock( array( $lockName ), LockManager::LOCK_EX )->isOK();
+ }
+
/**
* Build a concatenation list to feed into a SQL query
*
- * @param $stringList array
- *
+ * @param string[] $stringList
* @return string
*/
function buildConcat( $stringList ) {
return '(' . implode( ') || (', $stringList ) . ')';
}
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = array()
+ ) {
+ $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')';
+
+ return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
+ }
+
/**
* @throws MWException
- * @param $oldName
- * @param $newName
- * @param $temporary bool
- * @param $fname string
+ * @param string $oldName
+ * @param string $newName
+ * @param bool $temporary
+ * @param string $fname
* @return bool|ResultWrapper
*/
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
- $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" . $this->addQuotes( $oldName ) . " AND type='table'", $fname );
+ $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
+ $this->addQuotes( $oldName ) . " AND type='table'", $fname );
$obj = $this->fetchObject( $res );
if ( !$obj ) {
throw new MWException( "Couldn't retrieve structure for table $oldName" );
}
$sql = $obj->sql;
- $sql = preg_replace( '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/', $this->addIdentifierQuotes( $newName ), $sql, 1 );
+ $sql = preg_replace(
+ '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/',
+ $this->addIdentifierQuotes( $newName ),
+ $sql,
+ 1
+ );
if ( $temporary ) {
if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" );
@@ -841,6 +944,7 @@ class DatabaseSqlite extends DatabaseBase {
$sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
}
}
+
return $this->query( $sql, $fname );
}
@@ -848,7 +952,7 @@ class DatabaseSqlite extends DatabaseBase {
* List all tables on the database
*
* @param string $prefix Only show tables with this prefix, e.g. mw_
- * @param string $fname calling function name
+ * @param string $fname Calling function name
*
* @return array
*/
@@ -869,13 +973,11 @@ class DatabaseSqlite extends DatabaseBase {
if ( strpos( $table, 'sqlite_' ) !== 0 ) {
$endArray[] = $table;
}
-
}
}
return $endArray;
}
-
} // end DatabaseSqlite class
/**
@@ -895,6 +997,7 @@ class DatabaseSqliteStandalone extends DatabaseSqlite {
*/
class SQLiteField implements Field {
private $info, $tableName;
+
function __construct( $info, $tableName ) {
$this->info = $info;
$this->tableName = $tableName;
@@ -915,6 +1018,7 @@ class SQLiteField implements Field {
return str_replace( "''", "'", $this->info->dflt_value );
}
}
+
return $this->info->dflt_value;
}
@@ -928,5 +1032,4 @@ class SQLiteField implements Field {
function type() {
return $this->info->type;
}
-
} // end SQLiteField
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
index de58bab6..c1e80d33 100644
--- a/includes/db/DatabaseUtility.php
+++ b/includes/db/DatabaseUtility.php
@@ -51,7 +51,8 @@ class DBObject {
* This allows us to distinguish a blob from a normal string and an array of strings
*/
class Blob {
- private $mData;
+ /** @var string */
+ protected $mData;
function __construct( $data ) {
$this->mData = $data;
@@ -97,13 +98,23 @@ interface Field {
* @ingroup Database
*/
class ResultWrapper implements Iterator {
- var $db, $result, $pos = 0, $currentRow = null;
+ /** @var resource */
+ public $result;
+
+ /** @var DatabaseBase */
+ protected $db;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var object|null */
+ protected $currentRow = null;
/**
* Create a new result object from a result resource and a Database object
*
* @param DatabaseBase $database
- * @param resource $result
+ * @param resource|ResultWrapper $result
*/
function __construct( $database, $result ) {
$this->db = $database;
@@ -118,7 +129,7 @@ class ResultWrapper implements Iterator {
/**
* Get the number of rows in a result object
*
- * @return integer
+ * @return int
*/
function numRows() {
return $this->db->numRows( $this );
@@ -129,7 +140,7 @@ class ResultWrapper implements Iterator {
* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
*
- * @return object
+ * @return stdClass
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchObject() {
@@ -140,7 +151,7 @@ class ResultWrapper implements Iterator {
* Fetch the next row from the given result object, in associative array
* form. Fields are retrieved with $row['fieldname'].
*
- * @return Array
+ * @return array
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchRow() {
@@ -160,14 +171,14 @@ class ResultWrapper implements Iterator {
* Change the position of the cursor in a result object.
* See mysql_data_seek()
*
- * @param $row integer
+ * @param int $row
*/
function seek( $row ) {
$this->db->dataSeek( $this, $row );
}
- /*********************
- * Iterator functions
+ /*
+ * ======= Iterator functions =======
* Note that using these in combination with the non-iterator functions
* above may cause rows to be skipped or repeated.
*/
@@ -181,12 +192,13 @@ class ResultWrapper implements Iterator {
}
/**
- * @return int
+ * @return stdClass|array|bool
*/
function current() {
if ( is_null( $this->currentRow ) ) {
$this->next();
}
+
return $this->currentRow;
}
@@ -198,11 +210,12 @@ class ResultWrapper implements Iterator {
}
/**
- * @return int
+ * @return stdClass
*/
function next() {
$this->pos++;
$this->currentRow = $this->fetchObject();
+
return $this->currentRow;
}
@@ -219,10 +232,17 @@ class ResultWrapper implements Iterator {
* doesn't go anywhere near an actual database.
*/
class FakeResultWrapper extends ResultWrapper {
- var $result = array();
- var $db = null; // And it's going to stay that way :D
- var $pos = 0;
- var $currentRow = null;
+ /** @var array */
+ public $result = array();
+
+ /** @var null And it's going to stay that way :D */
+ protected $db = null;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var array|stdClass|bool */
+ protected $currentRow = null;
function __construct( $array ) {
$this->result = $array;
@@ -235,6 +255,9 @@ class FakeResultWrapper extends ResultWrapper {
return count( $this->result );
}
+ /**
+ * @return array|bool
+ */
function fetchRow() {
if ( $this->pos < count( $this->result ) ) {
$this->currentRow = $this->result[$this->pos];
@@ -256,7 +279,10 @@ class FakeResultWrapper extends ResultWrapper {
function free() {
}
- // Callers want to be able to access fields with $this->fieldName
+ /**
+ * Callers want to be able to access fields with $this->fieldName
+ * @return bool|stdClass
+ */
function fetchObject() {
$this->fetchRow();
if ( $this->currentRow ) {
@@ -271,16 +297,21 @@ class FakeResultWrapper extends ResultWrapper {
$this->currentRow = null;
}
+ /**
+ * @return bool|stdClass
+ */
function next() {
return $this->fetchObject();
}
}
/**
- * Used by DatabaseBase::buildLike() to represent characters that have special meaning in SQL LIKE clauses
- * and thus need no escaping. Don't instantiate it manually, use DatabaseBase::anyChar() and anyString() instead.
+ * Used by DatabaseBase::buildLike() to represent characters that have special
+ * meaning in SQL LIKE clauses and thus need no escaping. Don't instantiate it
+ * manually, use DatabaseBase::anyChar() and anyString() instead.
*/
class LikeMatch {
+ /** @var string */
private $str;
/**
@@ -295,7 +326,7 @@ class LikeMatch {
/**
* Return the original stored string.
*
- * @return String
+ * @return string
*/
public function toString() {
return $this->str;
@@ -304,6 +335,8 @@ class LikeMatch {
/**
* An object representing a master or slave position in a replicated setup.
+ *
+ * The implementation details of this opaque type are up to the database subclass.
*/
interface DBMasterPos {
}
diff --git a/includes/db/IORMRow.php b/includes/db/IORMRow.php
index 39411791..c66cddfd 100644
--- a/includes/db/IORMRow.php
+++ b/includes/db/IORMRow.php
@@ -32,7 +32,6 @@
*/
interface IORMRow {
-
/**
* Load the specified fields from the database.
*
@@ -40,8 +39,8 @@ interface IORMRow {
* @deprecated since 1.22
*
* @param array|null $fields
- * @param boolean $override
- * @param boolean $skipLoaded
+ * @param bool $override
+ * @param bool $skipLoaded
*
* @return bool Success indicator
*/
@@ -86,7 +85,7 @@ interface IORMRow {
*
* @since 1.20
*
- * @return integer|null
+ * @return int|null
*/
public function getId();
@@ -95,7 +94,7 @@ interface IORMRow {
*
* @since 1.20
*
- * @param integer|null $id
+ * @param int|null $id
*/
public function setId( $id );
@@ -106,7 +105,7 @@ interface IORMRow {
*
* @param string $name
*
- * @return boolean
+ * @return bool
*/
public function hasField( $name );
@@ -115,7 +114,7 @@ interface IORMRow {
*
* @since 1.20
*
- * @return boolean
+ * @return bool
*/
public function hasIdField();
@@ -125,7 +124,7 @@ interface IORMRow {
* @since 1.20
*
* @param array $fields The fields to set
- * @param boolean $override Override already set fields with the provided values?
+ * @param bool $override Override already set fields with the provided values?
*/
public function setFields( array $fields, $override = true );
@@ -136,7 +135,7 @@ interface IORMRow {
* @since 1.20
*
* @param null|array $fields
- * @param boolean $incNullId
+ * @param bool $incNullId
*
* @return array
*/
@@ -148,7 +147,7 @@ interface IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $override
+ * @param bool $override
*/
public function loadDefaults( $override = true );
@@ -161,7 +160,7 @@ interface IORMRow {
* @param string|null $functionName
* @deprecated since 1.22
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function save( $functionName = null );
@@ -171,7 +170,7 @@ interface IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function remove();
@@ -214,9 +213,9 @@ interface IORMRow {
* @deprecated since 1.22
*
* @param string $field
- * @param integer $amount
+ * @param int $amount
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function addToField( $field, $amount );
@@ -245,7 +244,7 @@ interface IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $update
+ * @param bool $update
*/
public function setUpdateSummaries( $update );
@@ -255,7 +254,7 @@ interface IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $summaryMode
+ * @param bool $summaryMode
*/
public function setSummaryMode( $summaryMode );
@@ -268,5 +267,4 @@ interface IORMRow {
* @return IORMTable
*/
public function getTable();
-
}
diff --git a/includes/db/IORMTable.php b/includes/db/IORMTable.php
index 36865655..4dc693ac 100644
--- a/includes/db/IORMTable.php
+++ b/includes/db/IORMTable.php
@@ -28,7 +28,6 @@
*/
interface IORMTable {
-
/**
* Returns the name of the database table objects of this type are stored in.
*
@@ -63,8 +62,9 @@ interface IORMTable {
* * array
* * blob
*
- * TODO: get rid of the id field. Every row instance needs to have
- * one so this is just causing hassle at various locations by requiring an extra check for field name.
+ * @todo Get rid of the id field. Every row instance needs to have one so
+ * this is just causing hassle at various locations by requiring an extra
+ * check for field name.
*
* @since 1.20
*
@@ -107,10 +107,10 @@ interface IORMTable {
* @param string|null $functionName
*
* @return ORMResult The result set
- * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode)
+ * @throws DBQueryError If the query failed (even if the database was in ignoreErrors mode)
*/
public function select( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Selects the the specified fields of the records matching the provided
@@ -123,10 +123,10 @@ interface IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return array of self
+ * @return array Array of self
*/
public function selectObjects( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Do the actual select.
@@ -139,10 +139,10 @@ interface IORMTable {
* @param null|string $functionName
*
* @return ResultWrapper
- * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode)
+ * @throws DBQueryError If the query failed (even if the database was in ignoreErrors mode)
*/
public function rawSelect( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Selects the the specified fields of the records matching the provided
@@ -161,13 +161,13 @@ interface IORMTable {
* @param array|string|null $fields
* @param array $conditions
* @param array $options
- * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param bool $collapse Set to false to always return each result row as associative array.
* @param string|null $functionName
*
- * @return array of array
+ * @return array Array of array
*/
public function selectFields( $fields = null, array $conditions = array(),
- array $options = array(), $collapse = true, $functionName = null );
+ array $options = array(), $collapse = true, $functionName = null );
/**
* Selects the the specified fields of the first matching record.
@@ -183,7 +183,7 @@ interface IORMTable {
* @return IORMRow|bool False on failure
*/
public function selectRow( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Selects the the specified fields of the records matching the provided
@@ -199,7 +199,7 @@ interface IORMTable {
* @return ResultWrapper
*/
public function rawSelectRow( array $fields, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Selects the the specified fields of the first record matching the provided
@@ -213,13 +213,13 @@ interface IORMTable {
* @param array|string|null $fields
* @param array $conditions
* @param array $options
- * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param bool $collapse Set to false to always return each result row as associative array.
* @param string|null $functionName
*
* @return mixed|array|bool False on failure
*/
public function selectFieldsRow( $fields = null, array $conditions = array(),
- array $options = array(), $collapse = true, $functionName = null );
+ array $options = array(), $collapse = true, $functionName = null );
/**
* Returns if there is at least one record matching the provided conditions.
@@ -229,7 +229,7 @@ interface IORMTable {
*
* @param array $conditions
*
- * @return boolean
+ * @return bool
*/
public function has( array $conditions = array() );
@@ -238,7 +238,7 @@ interface IORMTable {
*
* @since 1.21
*
- * @return boolean
+ * @return bool
*/
public function exists();
@@ -254,7 +254,7 @@ interface IORMTable {
* @param array $conditions
* @param array $options
*
- * @return integer
+ * @return int
*/
public function count( array $conditions = array(), array $options = array() );
@@ -266,7 +266,7 @@ interface IORMTable {
* @param array $conditions
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function delete( array $conditions, $functionName = null );
@@ -275,8 +275,8 @@ interface IORMTable {
*
* @since 1.20
*
- * @param boolean $requireParams
- * @param boolean $setDefaults
+ * @param bool $requireParams
+ * @param bool $setDefaults
*
* @return array
*/
@@ -298,14 +298,14 @@ interface IORMTable {
*
* @since 1.20
*
- * @return integer DB_ enum
+ * @return int DB_ enum
*/
public function getReadDb();
/**
* Set the database type to use for read operations.
*
- * @param integer $db
+ * @param int $db
*
* @since 1.20
*/
@@ -316,14 +316,16 @@ interface IORMTable {
*
* @since 1.20
*
- * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used)
+ * @return string|bool The target wiki, in a form that LBFactory
+ * understands (or false if the local wiki is used)
*/
public function getTargetWiki();
/**
* Set the ID of the any foreign wiki to use as a target for database operations
*
- * @param string|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used)
+ * @param string|bool $wiki The target wiki, in a form that LBFactory
+ * understands (or false if the local wiki shall be used)
*
* @since 1.20
*/
@@ -370,7 +372,7 @@ interface IORMTable {
*
* @see LoadBalancer::reuseConnection
*
- * @param DatabaseBase $db the database
+ * @param DatabaseBase $db The database
*
* @since 1.20
*/
@@ -386,7 +388,7 @@ interface IORMTable {
* @param array $values
* @param array $conditions
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function update( array $values, array $conditions = array() );
@@ -419,7 +421,7 @@ interface IORMTable {
*
* @since 1.20
*
- * @param array|string $fields
+ * @param array $fields
*
* @return array
*/
@@ -488,7 +490,7 @@ interface IORMTable {
* @since 1.20
*
* @param array $data
- * @param boolean $loadDefaults
+ * @param bool $loadDefaults
*
* @return IORMRow
*/
@@ -510,8 +512,7 @@ interface IORMTable {
*
* @param string $name
*
- * @return boolean
+ * @return bool
*/
public function canHaveField( $name );
-
}
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index 16c43a00..73456e23 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -26,11 +26,8 @@
* @ingroup Database
*/
abstract class LBFactory {
-
- /**
- * @var LBFactory
- */
- static $instance;
+ /** @var LBFactory */
+ protected static $instance;
/**
* Disables all access to the load balancer, will cause all database access
@@ -38,7 +35,7 @@ abstract class LBFactory {
*/
public static function disableBackend() {
global $wgLBFactoryConf;
- self::$instance = new LBFactory_Fake( $wgLBFactoryConf );
+ self::$instance = new LBFactoryFake( $wgLBFactoryConf );
}
/**
@@ -47,15 +44,47 @@ abstract class LBFactory {
* @return LBFactory
*/
static function &singleton() {
+ global $wgLBFactoryConf;
+
if ( is_null( self::$instance ) ) {
- global $wgLBFactoryConf;
- $class = $wgLBFactoryConf['class'];
+ $class = self::getLBFactoryClass( $wgLBFactoryConf );
+
self::$instance = new $class( $wgLBFactoryConf );
}
+
return self::$instance;
}
/**
+ * Returns the LBFactory class to use and the load balancer configuration.
+ *
+ * @param array $config (e.g. $wgLBFactoryConf)
+ * @return string Class name
+ */
+ public static function getLBFactoryClass( array $config ) {
+ // For configuration backward compatibility after removing
+ // underscores from class names in MediaWiki 1.23.
+ $bcClasses = array(
+ 'LBFactory_Simple' => 'LBFactorySimple',
+ 'LBFactory_Single' => 'LBFactorySingle',
+ 'LBFactory_Multi' => 'LBFactoryMulti',
+ 'LBFactory_Fake' => 'LBFactoryFake',
+ );
+
+ $class = $config['class'];
+
+ if ( isset( $bcClasses[$class] ) ) {
+ $class = $bcClasses[$class];
+ wfDeprecated(
+ '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details',
+ '1.23'
+ );
+ }
+
+ return $class;
+ }
+
+ /**
* Shut down, close connections and destroy the cached instance.
*/
static function destroyInstance() {
@@ -69,7 +98,7 @@ abstract class LBFactory {
/**
* Set the instance to be the given object
*
- * @param $instance LBFactory
+ * @param LBFactory $instance
*/
static function setInstance( $instance ) {
self::destroyInstance();
@@ -78,7 +107,7 @@ abstract class LBFactory {
/**
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
- * @param $conf
+ * @param array $conf
*/
abstract function __construct( $conf );
@@ -86,7 +115,7 @@ abstract class LBFactory {
* Create a new load balancer object. The resulting object will be untracked,
* not chronology-protected, and the caller is responsible for cleaning it up.
*
- * @param string $wiki wiki ID, or false for the current wiki
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function newMainLB( $wiki = false );
@@ -94,7 +123,7 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer object.
*
- * @param string $wiki wiki ID, or false for the current wiki
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function getMainLB( $wiki = false );
@@ -104,9 +133,8 @@ abstract class LBFactory {
* untracked, not chronology-protected, and the caller is responsible for
* cleaning it up.
*
- * @param string $cluster external storage cluster, or false for core
- * @param string $wiki wiki ID, or false for the current wiki
- *
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function newExternalLB( $cluster, $wiki = false );
@@ -114,9 +142,8 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer for external storage
*
- * @param string $cluster external storage cluster, or false for core
- * @param string $wiki wiki ID, or false for the current wiki
- *
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function &getExternalLB( $cluster, $wiki = false );
@@ -125,7 +152,8 @@ abstract class LBFactory {
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
- * @param $callback string|array
+ *
+ * @param callable $callback
* @param array $params
*/
abstract function forEachLB( $callback, $params = array() );
@@ -139,8 +167,9 @@ abstract class LBFactory {
/**
* Call a method of each tracked load balancer
- * @param $methodName string
- * @param $args array
+ *
+ * @param string $methodName
+ * @param array $args
*/
function forEachLBCallMethod( $methodName, $args = array() ) {
$this->forEachLB( array( $this, 'callMethod' ), array( $methodName, $args ) );
@@ -148,9 +177,9 @@ abstract class LBFactory {
/**
* Private helper for forEachLBCallMethod
- * @param $loadBalancer
- * @param $methodName string
- * @param $args
+ * @param LoadBalancer $loadBalancer
+ * @param string $methodName
+ * @param array $args
*/
function callMethod( $loadBalancer, $methodName, $args ) {
call_user_func_array( array( $loadBalancer, $methodName ), $args );
@@ -162,39 +191,62 @@ abstract class LBFactory {
function commitMasterChanges() {
$this->forEachLBCallMethod( 'commitMasterChanges' );
}
+
+ /**
+ * Rollback changes on all master connections
+ * @since 1.23
+ */
+ function rollbackMasterChanges() {
+ $this->forEachLBCallMethod( 'rollbackMasterChanges' );
+ }
+
+ /**
+ * Detemine if any master connection has pending changes.
+ * @since 1.23
+ * @return bool
+ */
+ function hasMasterChanges() {
+ $ret = false;
+ $this->forEachLB( function ( $lb ) use ( &$ret ) {
+ $ret = $ret || $lb->hasMasterChanges();
+ } );
+ return $ret;
+ }
}
/**
* A simple single-master LBFactory that gets its configuration from the b/c globals
*/
-class LBFactory_Simple extends LBFactory {
+class LBFactorySimple extends LBFactory {
+ /** @var LoadBalancer */
+ protected $mainLB;
- /**
- * @var LoadBalancer
- */
- var $mainLB;
- var $extLBs = array();
+ /** @var LoadBalancer[] */
+ protected $extLBs = array();
- # Chronology protector
- var $chronProt;
+ /** @var ChronologyProtector */
+ protected $chronProt;
function __construct( $conf ) {
$this->chronProt = new ChronologyProtector;
}
/**
- * @param $wiki
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function newMainLB( $wiki = false ) {
- global $wgDBservers, $wgMasterWaitTimeout;
+ global $wgDBservers;
if ( $wgDBservers ) {
$servers = $wgDBservers;
} else {
global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
global $wgDBssl, $wgDBcompress;
- $flags = ( $wgDebugDumpSql ? DBO_DEBUG : 0 ) | DBO_DEFAULT;
+ $flags = DBO_DEFAULT;
+ if ( $wgDebugDumpSql ) {
+ $flags |= DBO_DEBUG;
+ }
if ( $wgDBssl ) {
$flags |= DBO_SSL;
}
@@ -210,17 +262,16 @@ class LBFactory_Simple extends LBFactory {
'type' => $wgDBtype,
'load' => 1,
'flags' => $flags
- ));
+ ) );
}
return new LoadBalancer( array(
'servers' => $servers,
- 'masterWaitTimeout' => $wgMasterWaitTimeout
- ));
+ ) );
}
/**
- * @param $wiki
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function getMainLB( $wiki = false ) {
@@ -229,13 +280,14 @@ class LBFactory_Simple extends LBFactory {
$this->mainLB->parentInfo( array( 'id' => 'main' ) );
$this->chronProt->initLB( $this->mainLB );
}
+
return $this->mainLB;
}
/**
* @throws MWException
- * @param $cluster
- * @param $wiki
+ * @param string $cluster
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function newExternalLB( $cluster, $wiki = false ) {
@@ -243,14 +295,15 @@ class LBFactory_Simple extends LBFactory {
if ( !isset( $wgExternalServers[$cluster] ) ) {
throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
+
return new LoadBalancer( array(
'servers' => $wgExternalServers[$cluster]
- ));
+ ) );
}
/**
- * @param $cluster
- * @param $wiki
+ * @param string $cluster
+ * @param bool|string $wiki
* @return array
*/
function &getExternalLB( $cluster, $wiki = false ) {
@@ -259,6 +312,7 @@ class LBFactory_Simple extends LBFactory {
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
$this->chronProt->initLB( $this->extLBs[$cluster] );
}
+
return $this->extLBs[$cluster];
}
@@ -266,8 +320,9 @@ class LBFactory_Simple extends LBFactory {
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
- * @param $callback
- * @param $params array
+ *
+ * @param callable $callback
+ * @param array $params
*/
function forEachLB( $callback, $params = array() ) {
if ( isset( $this->mainLB ) ) {
@@ -296,7 +351,7 @@ class LBFactory_Simple extends LBFactory {
* Call LBFactory::disableBackend() to start using this, and
* LBFactory::enableBackend() to return to normal behavior
*/
-class LBFactory_Fake extends LBFactory {
+class LBFactoryFake extends LBFactory {
function __construct( $conf ) {
}
@@ -325,6 +380,7 @@ class LBFactory_Fake extends LBFactory {
*/
class DBAccessError extends MWException {
function __construct() {
- parent::__construct( "Mediawiki tried to access the database via wfGetDB(). This is not allowed." );
+ parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
+ "This is not allowed." );
}
}
diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactoryMulti.php
index 3043946a..bac96523 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactoryMulti.php
@@ -26,49 +26,130 @@
* Ignores the old configuration globals
*
* Configuration:
- * sectionsByDB A map of database names to section names
+ * sectionsByDB A map of database names to section names.
*
- * sectionLoads A 2-d map. For each section, gives a map of server names to load ratios.
- * For example: array( 'section1' => array( 'db1' => 100, 'db2' => 100 ) )
+ * sectionLoads A 2-d map. For each section, gives a map of server names to
+ * load ratios. For example:
+ * array(
+ * 'section1' => array(
+ * 'db1' => 100,
+ * 'db2' => 100
+ * )
+ * )
*
- * serverTemplate A server info associative array as documented for $wgDBservers. The host,
- * hostName and load entries will be overridden.
+ * serverTemplate A server info associative array as documented for $wgDBservers.
+ * The host, hostName and load entries will be overridden.
*
- * groupLoadsBySection A 3-d map giving server load ratios for each section and group. For example:
- * array( 'section1' => array( 'group1' => array( 'db1' => 100, 'db2' => 100 ) ) )
+ * groupLoadsBySection A 3-d map giving server load ratios for each section and group.
+ * For example:
+ * array(
+ * 'section1' => array(
+ * 'group1' => array(
+ * 'db1' => 100,
+ * 'db2' => 100
+ * )
+ * )
+ * )
*
* groupLoadsByDB A 3-d map giving server load ratios by DB name.
*
* hostsByName A map of hostname to IP address.
*
- * externalLoads A map of external storage cluster name to server load map
+ * externalLoads A map of external storage cluster name to server load map.
*
- * externalTemplateOverrides A set of server info keys overriding serverTemplate for external storage
+ * externalTemplateOverrides A set of server info keys overriding serverTemplate for external
+ * storage.
*
- * templateOverridesByServer A 2-d map overriding serverTemplate and externalTemplateOverrides on a
- * server-by-server basis. Applies to both core and external storage.
+ * templateOverridesByServer A 2-d map overriding serverTemplate and
+ * externalTemplateOverrides on a server-by-server basis. Applies
+ * to both core and external storage.
*
- * templateOverridesByCluster A 2-d map overriding the server info by external storage cluster
+ * templateOverridesByCluster A 2-d map overriding the server info by external storage cluster.
*
* masterTemplateOverrides An override array for all master servers.
*
- * readOnlyBySection A map of section name to read-only message. Missing or false for read/write.
+ * readOnlyBySection A map of section name to read-only message.
+ * Missing or false for read/write.
*
* @ingroup Database
*/
-class LBFactory_Multi extends LBFactory {
+class LBFactoryMulti extends LBFactory {
// Required settings
- var $sectionsByDB, $sectionLoads, $serverTemplate;
+
+ /** @var array A map of database names to section names */
+ protected $sectionsByDB;
+
+ /**
+ * @var array A 2-d map. For each section, gives a map of server names to
+ * load ratios
+ */
+ protected $sectionLoads;
+
+ /**
+ * @var array A server info associative array as documented for
+ * $wgDBservers. The host, hostName and load entries will be
+ * overridden
+ */
+ protected $serverTemplate;
+
// Optional settings
- var $groupLoadsBySection = array(), $groupLoadsByDB = array(), $hostsByName = array();
- var $externalLoads = array(), $externalTemplateOverrides, $templateOverridesByServer;
- var $templateOverridesByCluster, $masterTemplateOverrides, $readOnlyBySection = array();
+
+ /** @var array A 3-d map giving server load ratios for each section and group */
+ protected $groupLoadsBySection = array();
+
+ /** @var array A 3-d map giving server load ratios by DB name */
+ protected $groupLoadsByDB = array();
+
+ /** @var array A map of hostname to IP address */
+ protected $hostsByName = array();
+
+ /** @var array A map of external storage cluster name to server load map */
+ protected $externalLoads = array();
+
+ /**
+ * @var array A set of server info keys overriding serverTemplate for
+ * external storage
+ */
+ protected $externalTemplateOverrides;
+
+ /**
+ * @var array A 2-d map overriding serverTemplate and
+ * externalTemplateOverrides on a server-by-server basis. Applies to both
+ * core and external storage
+ */
+ protected $templateOverridesByServer;
+
+ /** @var array A 2-d map overriding the server info by external storage cluster */
+ protected $templateOverridesByCluster;
+
+ /** @var array An override array for all master servers */
+ protected $masterTemplateOverrides;
+
+ /**
+ * @var array|bool A map of section name to read-only message. Missing or
+ * false for read/write
+ */
+ protected $readOnlyBySection = array();
+
// Other stuff
- var $conf, $mainLBs = array(), $extLBs = array();
- var $lastWiki, $lastSection;
+
+ /** @var array Load balancer factory configuration */
+ protected $conf;
+
+ /** @var LoadBalancer[] */
+ protected $mainLBs = array();
+
+ /** @var LoadBalancer[] */
+ protected $extLBs = array();
+
+ /** @var string */
+ protected $lastWiki;
+
+ /** @var string */
+ protected $lastSection;
/**
- * @param $conf array
+ * @param array $conf
* @throws MWException
*/
function __construct( $conf ) {
@@ -102,7 +183,7 @@ class LBFactory_Multi extends LBFactory {
}
/**
- * @param $wiki bool|string
+ * @param bool|string $wiki
* @return string
*/
function getSectionForWiki( $wiki = false ) {
@@ -117,11 +198,12 @@ class LBFactory_Multi extends LBFactory {
}
$this->lastSection = $section;
$this->lastWiki = $wiki;
+
return $section;
}
/**
- * @param $wiki bool|string
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function newMainLB( $wiki = false ) {
@@ -131,14 +213,20 @@ class LBFactory_Multi extends LBFactory {
if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
$groupLoads = $this->groupLoadsByDB[$dbName];
}
+
if ( isset( $this->groupLoadsBySection[$section] ) ) {
$groupLoads = array_merge_recursive( $groupLoads, $this->groupLoadsBySection[$section] );
}
- return $this->newLoadBalancer( $this->serverTemplate, $this->sectionLoads[$section], $groupLoads );
+
+ return $this->newLoadBalancer(
+ $this->serverTemplate,
+ $this->sectionLoads[$section],
+ $groupLoads
+ );
}
/**
- * @param $wiki bool|string
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function getMainLB( $wiki = false ) {
@@ -149,12 +237,13 @@ class LBFactory_Multi extends LBFactory {
$this->chronProt->initLB( $lb );
$this->mainLBs[$section] = $lb;
}
+
return $this->mainLBs[$section];
}
/**
* @param string $cluster
- * @param bool $wiki
+ * @param bool|string $wiki
* @throws MWException
* @return LoadBalancer
*/
@@ -169,12 +258,13 @@ class LBFactory_Multi extends LBFactory {
if ( isset( $this->templateOverridesByCluster[$cluster] ) ) {
$template = $this->templateOverridesByCluster[$cluster] + $template;
}
+
return $this->newLoadBalancer( $template, $this->externalLoads[$cluster], array() );
}
/**
- * @param $cluster
- * @param $wiki
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
function &getExternalLB( $cluster, $wiki = false ) {
@@ -183,33 +273,33 @@ class LBFactory_Multi extends LBFactory {
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
$this->chronProt->initLB( $this->extLBs[$cluster] );
}
+
return $this->extLBs[$cluster];
}
/**
* Make a new load balancer object based on template and load array
*
- * @param $template
- * @param $loads array
- * @param $groupLoads
+ * @param array $template
+ * @param array $loads
+ * @param array $groupLoads
* @return LoadBalancer
*/
function newLoadBalancer( $template, $loads, $groupLoads ) {
- global $wgMasterWaitTimeout;
$servers = $this->makeServerArray( $template, $loads, $groupLoads );
$lb = new LoadBalancer( array(
'servers' => $servers,
- 'masterWaitTimeout' => $wgMasterWaitTimeout
- ));
+ ) );
+
return $lb;
}
/**
* Make a server array as expected by LoadBalancer::__construct, using a template and load array
*
- * @param $template
- * @param $loads array
- * @param $groupLoads
+ * @param array $template
+ * @param array $loads
+ * @param array $groupLoads
* @return array
*/
function makeServerArray( $template, $loads, $groupLoads ) {
@@ -245,12 +335,13 @@ class LBFactory_Multi extends LBFactory {
$serverInfo['load'] = $load;
$servers[] = $serverInfo;
}
+
return $servers;
}
/**
* Take a group load array indexed by group then server, and reindex it by server then group
- * @param $groupLoads
+ * @param array $groupLoads
* @return array
*/
function reindexGroupLoads( $groupLoads ) {
@@ -260,17 +351,19 @@ class LBFactory_Multi extends LBFactory {
$reindexed[$server][$group] = $load;
}
}
+
return $reindexed;
}
/**
* Get the database name and prefix based on the wiki ID
- * @param $wiki bool
+ * @param bool|string $wiki
* @return array
*/
function getDBNameAndPrefix( $wiki = false ) {
if ( $wiki === false ) {
global $wgDBname, $wgDBprefix;
+
return array( $wgDBname, $wgDBprefix );
} else {
return wfSplitWikiID( $wiki );
@@ -281,8 +374,8 @@ class LBFactory_Multi extends LBFactory {
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
- * @param $callback
- * @param $params array
+ * @param callable $callback
+ * @param array $params
*/
function forEachLB( $callback, $params = array() ) {
foreach ( $this->mainLBs as $lb ) {
diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactorySingle.php
index 7dca06d7..3a4d829b 100644
--- a/includes/db/LBFactory_Single.php
+++ b/includes/db/LBFactorySingle.php
@@ -24,7 +24,8 @@
/**
* An LBFactory class that always returns a single database object.
*/
-class LBFactory_Single extends LBFactory {
+class LBFactorySingle extends LBFactory {
+ /** @var LoadBalancerSingle */
protected $lb;
/**
@@ -32,50 +33,46 @@ class LBFactory_Single extends LBFactory {
* - connection: The DatabaseBase connection object
*/
function __construct( $conf ) {
- $this->lb = new LoadBalancer_Single( $conf );
+ $this->lb = new LoadBalancerSingle( $conf );
}
/**
- * @param $wiki bool|string
- *
- * @return LoadBalancer_Single
+ * @param bool|string $wiki
+ * @return LoadBalancerSingle
*/
function newMainLB( $wiki = false ) {
return $this->lb;
}
/**
- * @param $wiki bool|string
- *
- * @return LoadBalancer_Single
+ * @param bool|string $wiki
+ * @return LoadBalancerSingle
*/
function getMainLB( $wiki = false ) {
return $this->lb;
}
/**
- * @param $cluster
- * @param $wiki bool|string
- *
- * @return LoadBalancer_Single
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
+ * @return LoadBalancerSingle
*/
function newExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
/**
- * @param $cluster
- * @param $wiki bool|string
- *
- * @return LoadBalancer_Single
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
+ * @return LoadBalancerSingle
*/
function &getExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
/**
- * @param $callback string|array
- * @param $params array
+ * @param string|callable $callback
+ * @param array $params
*/
function forEachLB( $callback, $params = array() ) {
call_user_func_array( $callback, array_merge( array( $this->lb ), $params ) );
@@ -83,17 +80,14 @@ class LBFactory_Single extends LBFactory {
}
/**
- * Helper class for LBFactory_Single.
+ * Helper class for LBFactorySingle.
*/
-class LoadBalancer_Single extends LoadBalancer {
-
- /**
- * @var DatabaseBase
- */
- var $db;
+class LoadBalancerSingle extends LoadBalancer {
+ /** @var DatabaseBase */
+ protected $db;
/**
- * @param $params array
+ * @param array $params
*/
function __construct( $params ) {
$this->db = $params['connection'];
@@ -107,8 +101,8 @@ class LoadBalancer_Single extends LoadBalancer {
/**
*
- * @param $server string
- * @param $dbNameOverride bool
+ * @param string $server
+ * @param bool $dbNameOverride
*
* @return DatabaseBase
*/
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index 857109db..e517a025 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -28,19 +28,43 @@
* @ingroup Database
*/
class LoadBalancer {
- private $mServers, $mConns, $mLoads, $mGroupLoads;
+ /** @var array Map of (server index => server config array) */
+ private $mServers;
+ /** @var array Map of (local/foreignUsed/foreignFree => server index => DatabaseBase array) */
+ private $mConns;
+ /** @var array Map of (server index => weight) */
+ private $mLoads;
+ /** @var array Map of (group => server index => weight) */
+ private $mGroupLoads;
+ /** @var bool Whether to disregard slave lag as a factor in slave selection */
+ private $mAllowLagged;
+ /** @var integer Seconds to spend waiting on slave lag to resolve */
+ private $mWaitTimeout;
+
+ /** @var array LBFactory information */
+ private $mParentInfo;
+ /** @var string The LoadMonitor subclass name */
+ private $mLoadMonitorClass;
+ /** @var LoadMonitor */
+ private $mLoadMonitor;
+
+ /** @var bool|DatabaseBase Database connection that caused a problem */
private $mErrorConnection;
- private $mReadIndex, $mAllowLagged;
- private $mWaitForPos, $mWaitTimeout;
- private $mLaggedSlaveMode, $mLastError = 'Unknown error';
- private $mParentInfo, $mLagTimes;
- private $mLoadMonitorClass, $mLoadMonitor;
+ /** @var integer The generic (not query grouped) slave index (of $mServers) */
+ private $mReadIndex;
+ /** @var bool|DBMasterPos False if not set */
+ private $mWaitForPos;
+ /** @var bool Whether the generic reader fell back to a lagged slave */
+ private $mLaggedSlaveMode;
+ /** @var string The last DB selection or connection error */
+ private $mLastError = 'Unknown error';
+ /** @var array Process cache of LoadMonitor::getLagTimes() */
+ private $mLagTimes;
/**
- * @param array $params with keys:
- * servers Required. Array of server info structures.
- * masterWaitTimeout Replication lag wait timeout
- * loadMonitor Name of a class used to fetch server lag and load.
+ * @param array $params Array with keys:
+ * servers Required. Array of server info structures.
+ * loadMonitor Name of a class used to fetch server lag and load.
* @throws MWException
*/
function __construct( $params ) {
@@ -48,12 +72,7 @@ class LoadBalancer {
throw new MWException( __CLASS__ . ': missing servers parameter' );
}
$this->mServers = $params['servers'];
-
- if ( isset( $params['waitTimeout'] ) ) {
- $this->mWaitTimeout = $params['waitTimeout'];
- } else {
- $this->mWaitTimeout = 10;
- }
+ $this->mWaitTimeout = 10;
$this->mReadIndex = -1;
$this->mWriteIndex = -1;
@@ -72,9 +91,9 @@ class LoadBalancer {
} else {
$master = reset( $params['servers'] );
if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
- $this->mLoadMonitorClass = 'LoadMonitor_MySQL';
+ $this->mLoadMonitorClass = 'LoadMonitorMySQL';
} else {
- $this->mLoadMonitorClass = 'LoadMonitor_Null';
+ $this->mLoadMonitorClass = 'LoadMonitorNull';
}
}
@@ -101,13 +120,14 @@ class LoadBalancer {
$class = $this->mLoadMonitorClass;
$this->mLoadMonitor = new $class( $this );
}
+
return $this->mLoadMonitor;
}
/**
* Get or set arbitrary data used by the parent object, usually an LBFactory
- * @param $x
- * @return Mixed
+ * @param mixed $x
+ * @return mixed
*/
function parentInfo( $x = null ) {
return wfSetVar( $this->mParentInfo, $x );
@@ -119,8 +139,7 @@ class LoadBalancer {
*
* @deprecated since 1.21, use ArrayUtils::pickRandom()
*
- * @param $weights array
- *
+ * @param array $weights
* @return bool|int|string
*/
function pickRandom( $weights ) {
@@ -128,8 +147,8 @@ class LoadBalancer {
}
/**
- * @param $loads array
- * @param $wiki bool
+ * @param array $loads
+ * @param bool|string $wiki Wiki to get non-lagged for
* @return bool|int|string
*/
function getRandomNonLagged( $loads, $wiki = false ) {
@@ -138,10 +157,10 @@ class LoadBalancer {
foreach ( $lags as $i => $lag ) {
if ( $i != 0 ) {
if ( $lag === false ) {
- wfDebugLog( 'replication', "Server #$i is not replicating\n" );
+ wfDebugLog( 'replication', "Server #$i is not replicating" );
unset( $loads[$i] );
} elseif ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) {
- wfDebugLog( 'replication', "Server #$i is excessively lagged ($lag seconds)\n" );
+ wfDebugLog( 'replication', "Server #$i is excessively lagged ($lag seconds)" );
unset( $loads[$i] );
}
}
@@ -176,13 +195,13 @@ class LoadBalancer {
* always return a consistent index during a given invocation
*
* Side effect: opens connections to databases
- * @param $group bool
- * @param $wiki bool
+ * @param bool|string $group
+ * @param bool|string $wiki
* @throws MWException
* @return bool|int|string
*/
function getReaderIndex( $group = false, $wiki = false ) {
- global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype;
+ global $wgReadOnly, $wgDBtype;
# @todo FIXME: For now, only go through all this for mysql databases
if ( $wgDBtype != 'mysql' ) {
@@ -192,17 +211,12 @@ class LoadBalancer {
if ( count( $this->mServers ) == 1 ) {
# Skip the load balancing if there's only one server
return 0;
- } elseif ( $group === false and $this->mReadIndex >= 0 ) {
+ } elseif ( $group === false && $this->mReadIndex >= 0 ) {
# Shortcut if generic reader exists already
return $this->mReadIndex;
}
- wfProfileIn( __METHOD__ );
-
- $totalElapsed = 0;
-
- # convert from seconds to microseconds
- $timeout = $wgDBClusterTimeout * 1e6;
+ $section = new ProfileSection( __METHOD__ );
# Find the relevant load array
if ( $group !== false ) {
@@ -211,15 +225,14 @@ class LoadBalancer {
} else {
# No loads for this group, return false and the caller can use some other group
wfDebug( __METHOD__ . ": no loads for group $group\n" );
- wfProfileOut( __METHOD__ );
+
return false;
}
} else {
$nonErrorLoads = $this->mLoads;
}
- if ( !$nonErrorLoads ) {
- wfProfileOut( __METHOD__ );
+ if ( !count( $nonErrorLoads ) ) {
throw new MWException( "Empty server array given to LoadBalancer" );
}
@@ -228,92 +241,60 @@ class LoadBalancer {
$laggedSlaveMode = false;
+ # No server found yet
+ $i = false;
# First try quickly looking through the available servers for a server that
# meets our criteria
- do {
- $totalThreadsConnected = 0;
- $overloadedServers = 0;
- $currentLoads = $nonErrorLoads;
- while ( count( $currentLoads ) ) {
- if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
+ $currentLoads = $nonErrorLoads;
+ while ( count( $currentLoads ) ) {
+ if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
+ $i = ArrayUtils::pickRandom( $currentLoads );
+ } else {
+ $i = $this->getRandomNonLagged( $currentLoads, $wiki );
+ if ( $i === false && count( $currentLoads ) != 0 ) {
+ # All slaves lagged. Switch to read-only mode
+ wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode" );
+ $wgReadOnly = 'The database has been automatically locked ' .
+ 'while the slave database servers catch up to the master';
$i = ArrayUtils::pickRandom( $currentLoads );
- } else {
- $i = $this->getRandomNonLagged( $currentLoads, $wiki );
- if ( $i === false && count( $currentLoads ) != 0 ) {
- # All slaves lagged. Switch to read-only mode
- wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode\n" );
- $wgReadOnly = 'The database has been automatically locked ' .
- 'while the slave database servers catch up to the master';
- $i = ArrayUtils::pickRandom( $currentLoads );
- $laggedSlaveMode = true;
- }
- }
-
- if ( $i === false ) {
- # pickRandom() returned false
- # This is permanent and means the configuration or the load monitor
- # wants us to return false.
- wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false\n" );
- wfProfileOut( __METHOD__ );
- return false;
+ $laggedSlaveMode = true;
}
+ }
- wfDebugLog( 'connect', __METHOD__ . ": Using reader #$i: {$this->mServers[$i]['host']}...\n" );
- $conn = $this->openConnection( $i, $wiki );
-
- if ( !$conn ) {
- wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki\n" );
- unset( $nonErrorLoads[$i] );
- unset( $currentLoads[$i] );
- continue;
- }
-
- // Perform post-connection backoff
- $threshold = isset( $this->mServers[$i]['max threads'] )
- ? $this->mServers[$i]['max threads'] : false;
- $backoff = $this->getLoadMonitor()->postConnectionBackoff( $conn, $threshold );
-
- // Decrement reference counter, we are finished with this connection.
- // It will be incremented for the caller later.
- if ( $wiki !== false ) {
- $this->reuseConnection( $conn );
- }
+ if ( $i === false ) {
+ # pickRandom() returned false
+ # This is permanent and means the configuration or the load monitor
+ # wants us to return false.
+ wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false" );
- if ( $backoff ) {
- # Post-connection overload, don't use this server for now
- $totalThreadsConnected += $backoff;
- $overloadedServers++;
- unset( $currentLoads[$i] );
- } else {
- # Return this server
- break 2;
- }
+ return false;
}
- # No server found yet
- $i = false;
+ wfDebugLog( 'connect', __METHOD__ .
+ ": Using reader #$i: {$this->mServers[$i]['host']}..." );
- # If all servers were down, quit now
- if ( !count( $nonErrorLoads ) ) {
- wfDebugLog( 'connect', "All servers down\n" );
- break;
+ $conn = $this->openConnection( $i, $wiki );
+ if ( !$conn ) {
+ wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki" );
+ unset( $nonErrorLoads[$i] );
+ unset( $currentLoads[$i] );
+ $i = false;
+ continue;
}
- # Some servers must have been overloaded
- if ( $overloadedServers == 0 ) {
- throw new MWException( __METHOD__ . ": unexpectedly found no overloaded servers" );
+ // Decrement reference counter, we are finished with this connection.
+ // It will be incremented for the caller later.
+ if ( $wiki !== false ) {
+ $this->reuseConnection( $conn );
}
- # Back off for a while
- # Scale the sleep time by the number of connected threads, to produce a
- # roughly constant global poll rate
- $avgThreads = $totalThreadsConnected / $overloadedServers;
- $totalElapsed += $this->sleep( $wgDBAvgStatusPoll * $avgThreads );
- } while ( $totalElapsed < $timeout );
-
- if ( $totalElapsed >= $timeout ) {
- wfDebugLog( 'connect', "All servers busy\n" );
- $this->mErrorConnection = false;
- $this->mLastError = 'All servers busy';
+
+ # Return this server
+ break;
+ }
+
+ # If all servers were down, quit now
+ if ( !count( $nonErrorLoads ) ) {
+ wfDebugLog( 'connect', "All servers down" );
}
if ( $i !== false ) {
@@ -324,17 +305,17 @@ class LoadBalancer {
$this->mServers[$i]['slave pos'] = $conn->getSlavePos();
}
}
- if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $i !== false ) {
+ if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group !== false ) {
$this->mReadIndex = $i;
}
}
- wfProfileOut( __METHOD__ );
+
return $i;
}
/**
* Wait for a specified number of microseconds, and return the period waited
- * @param $t int
+ * @param int $t
* @return int
*/
function sleep( $t ) {
@@ -342,6 +323,7 @@ class LoadBalancer {
wfDebug( __METHOD__ . ": waiting $t us\n" );
usleep( $t );
wfProfileOut( __METHOD__ );
+
return $t;
}
@@ -349,7 +331,7 @@ class LoadBalancer {
* Set the master wait position
* If a DB_SLAVE connection has been opened already, waits
* Otherwise sets a variable telling it to wait if such a connection is opened
- * @param $pos DBMasterPos
+ * @param DBMasterPos $pos
*/
public function waitFor( $pos ) {
wfProfileIn( __METHOD__ );
@@ -367,22 +349,31 @@ class LoadBalancer {
/**
* Set the master wait position and wait for ALL slaves to catch up to it
- * @param $pos DBMasterPos
+ * @param DBMasterPos $pos
+ * @param int $timeout Max seconds to wait; default is mWaitTimeout
+ * @return bool Success (able to connect and no timeouts reached)
*/
- public function waitForAll( $pos ) {
+ public function waitForAll( $pos, $timeout = null ) {
wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
- for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
- $this->doWait( $i, true );
+ $serverCount = count( $this->mServers );
+
+ $ok = true;
+ for ( $i = 1; $i < $serverCount; $i++ ) {
+ if ( $this->mLoads[$i] > 0 ) {
+ $ok = $this->doWait( $i, true, $timeout ) && $ok;
+ }
}
wfProfileOut( __METHOD__ );
+
+ return $ok;
}
/**
* Get any open connection to a given server index, local or foreign
* Returns false if there is no connection open
*
- * @param $i int
+ * @param int $i
* @return DatabaseBase|bool False on failure
*/
function getAnyOpenConnection( $i ) {
@@ -391,40 +382,47 @@ class LoadBalancer {
return reset( $conns[$i] );
}
}
+
return false;
}
/**
* Wait for a given slave to catch up to the master pos stored in $this
- * @param $index
- * @param $open bool
+ * @param int $index Server index
+ * @param bool $open Check the server even if a new connection has to be made
+ * @param int $timeout Max seconds to wait; default is mWaitTimeout
* @return bool
*/
- protected function doWait( $index, $open = false ) {
+ protected function doWait( $index, $open = false, $timeout = null ) {
# Find a connection to wait on
$conn = $this->getAnyOpenConnection( $index );
if ( !$conn ) {
if ( !$open ) {
wfDebug( __METHOD__ . ": no connection open\n" );
+
return false;
} else {
$conn = $this->openConnection( $index, '' );
if ( !$conn ) {
wfDebug( __METHOD__ . ": failed to open connection\n" );
+
return false;
}
}
}
wfDebug( __METHOD__ . ": Waiting for slave #$index to catch up...\n" );
- $result = $conn->masterPosWait( $this->mWaitForPos, $this->mWaitTimeout );
+ $timeout = $timeout ?: $this->mWaitTimeout;
+ $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
if ( $result == -1 || is_null( $result ) ) {
# Timed out waiting for slave, use master instead
wfDebug( __METHOD__ . ": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" );
+
return false;
} else {
wfDebug( __METHOD__ . ": Done\n" );
+
return true;
}
}
@@ -433,8 +431,8 @@ class LoadBalancer {
* Get a connection by index
* This is the main entry point for this class.
*
- * @param $i Integer: server index
- * @param array $groups query groups
+ * @param int $i Server index
+ * @param array $groups Query groups
* @param bool|string $wiki Wiki ID
*
* @throws MWException
@@ -443,12 +441,10 @@ class LoadBalancer {
public function &getConnection( $i, $groups = array(), $wiki = false ) {
wfProfileIn( __METHOD__ );
- if ( $i == DB_LAST ) {
+ if ( $i === null || $i === false ) {
wfProfileOut( __METHOD__ );
- throw new MWException( 'Attempt to call ' . __METHOD__ . ' with deprecated server index DB_LAST' );
- } elseif ( $i === null || $i === false ) {
- wfProfileOut( __METHOD__ );
- throw new MWException( 'Attempt to call ' . __METHOD__ . ' with invalid server index' );
+ throw new MWException( 'Attempt to call ' . __METHOD__ .
+ ' with invalid server index' );
}
if ( $wiki === wfWikiID() ) {
@@ -485,6 +481,7 @@ class LoadBalancer {
if ( $i === false ) {
$this->mLastError = 'No working slave server: ' . $this->mLastError;
wfProfileOut( __METHOD__ );
+
return $this->reportConnectionError();
}
}
@@ -493,10 +490,12 @@ class LoadBalancer {
$conn = $this->openConnection( $i, $wiki );
if ( !$conn ) {
wfProfileOut( __METHOD__ );
+
return $this->reportConnectionError();
}
wfProfileOut( __METHOD__ );
+
return $conn;
}
@@ -511,15 +510,9 @@ class LoadBalancer {
public function reuseConnection( $conn ) {
$serverIndex = $conn->getLBInfo( 'serverIndex' );
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
- $dbName = $conn->getDBname();
- $prefix = $conn->tablePrefix();
- if ( strval( $prefix ) !== '' ) {
- $wiki = "$dbName-$prefix";
- } else {
- $wiki = $dbName;
- }
if ( $serverIndex === null || $refCount === null ) {
wfDebug( __METHOD__ . ": this connection was not opened as a foreign connection\n" );
+
/**
* This can happen in code like:
* foreach ( $dbs as $db ) {
@@ -530,10 +523,20 @@ class LoadBalancer {
* When a connection to the local DB is opened in this way, reuseConnection()
* should be ignored
*/
+
return;
}
+
+ $dbName = $conn->getDBname();
+ $prefix = $conn->tablePrefix();
+ if ( strval( $prefix ) !== '' ) {
+ $wiki = "$dbName-$prefix";
+ } else {
+ $wiki = $dbName;
+ }
if ( $this->mConns['foreignUsed'][$serverIndex][$wiki] !== $conn ) {
- throw new MWException( __METHOD__ . ": connection not found, has the connection been freed already?" );
+ throw new MWException( __METHOD__ . ": connection not found, has " .
+ "the connection been freed already?" );
}
$conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
if ( $refCount <= 0 ) {
@@ -552,9 +555,9 @@ class LoadBalancer {
*
* @see LoadBalancer::getConnection() for parameter information
*
- * @param integer $db
+ * @param int $db
* @param mixed $groups
- * @param string $wiki
+ * @param bool|string $wiki
* @return DBConnRef
*/
public function getConnectionRef( $db, $groups = array(), $wiki = false ) {
@@ -568,9 +571,9 @@ class LoadBalancer {
*
* @see LoadBalancer::getConnection() for parameter information
*
- * @param integer $db
+ * @param int $db
* @param mixed $groups
- * @param string $wiki
+ * @param bool|string $wiki
* @return DBConnRef
*/
public function getLazyConnectionRef( $db, $groups = array(), $wiki = false ) {
@@ -585,8 +588,8 @@ class LoadBalancer {
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
- * @param $i Integer server index
- * @param string $wiki wiki ID to open
+ * @param int $i Server index
+ * @param bool|string $wiki Wiki ID to open
* @return DatabaseBase
*
* @access private
@@ -596,6 +599,7 @@ class LoadBalancer {
if ( $wiki !== false ) {
$conn = $this->openForeignConnection( $i, $wiki );
wfProfileOut( __METHOD__ );
+
return $conn;
}
if ( isset( $this->mConns['local'][$i][0] ) ) {
@@ -614,6 +618,7 @@ class LoadBalancer {
}
}
wfProfileOut( __METHOD__ );
+
return $conn;
}
@@ -631,8 +636,8 @@ class LoadBalancer {
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
- * @param $i Integer: server index
- * @param string $wiki wiki ID to open
+ * @param int $i Server index
+ * @param string $wiki Wiki ID to open
* @return DatabaseBase
*/
function openForeignConnection( $i, $wiki ) {
@@ -688,13 +693,14 @@ class LoadBalancer {
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
}
wfProfileOut( __METHOD__ );
+
return $conn;
}
/**
* Test if the specified index represents an open connection
*
- * @param $index Integer: server index
+ * @param int $index Server index
* @access private
* @return bool
*/
@@ -702,6 +708,7 @@ class LoadBalancer {
if ( !is_integer( $index ) ) {
return false;
}
+
return (bool)$this->getAnyOpenConnection( $index );
}
@@ -710,8 +717,8 @@ class LoadBalancer {
* Returns a Database object whether or not the connection was successful.
* @access private
*
- * @param $server
- * @param $dbNameOverride bool
+ * @param array $server
+ * @param bool $dbNameOverride
* @throws MWException
* @return DatabaseBase
*/
@@ -741,6 +748,7 @@ class LoadBalancer {
if ( isset( $server['fakeMaster'] ) ) {
$db->setFakeMaster( true );
}
+
return $db;
}
@@ -753,15 +761,16 @@ class LoadBalancer {
if ( !is_object( $conn ) ) {
// No last connection, probably due to all servers being too busy
- wfLogDBError( "LB failure with no last connection. Connection error: {$this->mLastError}\n" );
+ wfLogDBError( "LB failure with no last connection. Connection error: {$this->mLastError}" );
// If all servers were busy, mLastError will contain something sensible
throw new DBConnectionError( null, $this->mLastError );
} else {
$server = $conn->getProperty( 'mServer' );
- wfLogDBError( "Connection error: {$this->mLastError} ({$server})\n" );
+ wfLogDBError( "Connection error: {$this->mLastError} ({$server})" );
$conn->reportConnectionError( "{$this->mLastError} ({$server})" ); // throws DBConnectionError
}
+
return false; /* not reached */
}
@@ -775,7 +784,7 @@ class LoadBalancer {
/**
* Returns true if the specified index is a valid server index
*
- * @param $i
+ * @param string $i
* @return bool
*/
function haveIndex( $i ) {
@@ -785,7 +794,7 @@ class LoadBalancer {
/**
* Returns true if the specified index is valid and has non-zero load
*
- * @param $i
+ * @param string $i
* @return bool
*/
function isNonZeroLoad( $i ) {
@@ -804,7 +813,7 @@ class LoadBalancer {
/**
* Get the host name or IP address of the server with the specified index
* Prefer a readable name if available.
- * @param $i
+ * @param string $i
* @return string
*/
function getServerName( $i ) {
@@ -819,8 +828,8 @@ class LoadBalancer {
/**
* Return the server info structure for a given index, or false if the index is invalid.
- * @param $i
- * @return bool
+ * @param int $i
+ * @return array|bool
*/
function getServerInfo( $i ) {
if ( isset( $this->mServers[$i] ) ) {
@@ -831,9 +840,10 @@ class LoadBalancer {
}
/**
- * Sets the server info structure for the given index. Entry at index $i is created if it doesn't exist
- * @param $i
- * @param $serverInfo
+ * Sets the server info structure for the given index. Entry at index $i
+ * is created if it doesn't exist
+ * @param int $i
+ * @param array $serverInfo
*/
function setServerInfo( $i, $serverInfo ) {
$this->mServers[$i] = $serverInfo;
@@ -848,17 +858,21 @@ class LoadBalancer {
# master (however unlikely that may be), then we can fetch the position from the slave.
$masterConn = $this->getAnyOpenConnection( 0 );
if ( !$masterConn ) {
- for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
+ $serverCount = count( $this->mServers );
+ for ( $i = 1; $i < $serverCount; $i++ ) {
$conn = $this->getAnyOpenConnection( $i );
if ( $conn ) {
wfDebug( "Master pos fetched from slave\n" );
+
return $conn->getSlavePos();
}
}
} else {
wfDebug( "Master pos fetched from master\n" );
+
return $masterConn->getMasterPos();
}
+
return false;
}
@@ -868,6 +882,7 @@ class LoadBalancer {
function closeAll() {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
+ /** @var DatabaseBase $conn */
foreach ( $conns3 as $conn ) {
$conn->close();
}
@@ -881,21 +896,10 @@ class LoadBalancer {
}
/**
- * Deprecated function, typo in function name
- *
- * @deprecated in 1.18
- * @param $conn
- */
- function closeConnecton( $conn ) {
- wfDeprecated( __METHOD__, '1.18' );
- $this->closeConnection( $conn );
- }
-
- /**
* Close a connection
* Using this function makes sure the LoadBalancer knows the connection is closed.
* If you use $conn->close() directly, the load balancer won't update its state.
- * @param $conn DatabaseBase
+ * @param DatabaseBase $conn
*/
function closeConnection( $conn ) {
$done = false;
@@ -922,6 +926,7 @@ class LoadBalancer {
function commitAll() {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
+ /** @var DatabaseBase[] $conns3 */
foreach ( $conns3 as $conn ) {
if ( $conn->trxLevel() ) {
$conn->commit( __METHOD__, 'flush' );
@@ -941,6 +946,7 @@ class LoadBalancer {
if ( empty( $conns2[$masterIndex] ) ) {
continue;
}
+ /** @var DatabaseBase $conn */
foreach ( $conns2[$masterIndex] as $conn ) {
if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
$conn->commit( __METHOD__, 'flush' );
@@ -950,8 +956,59 @@ class LoadBalancer {
}
/**
- * @param $value null
- * @return Mixed
+ * Issue ROLLBACK only on master, only if queries were done on connection
+ * @since 1.23
+ */
+ function rollbackMasterChanges() {
+ // Always 0, but who knows.. :)
+ $masterIndex = $this->getWriterIndex();
+ foreach ( $this->mConns as $conns2 ) {
+ if ( empty( $conns2[$masterIndex] ) ) {
+ continue;
+ }
+ /** @var DatabaseBase $conn */
+ foreach ( $conns2[$masterIndex] as $conn ) {
+ if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
+ $conn->rollback( __METHOD__, 'flush' );
+ }
+ }
+ }
+ }
+
+ /**
+ * @return bool Whether a master connection is already open
+ * @since 1.24
+ */
+ function hasMasterConnection() {
+ return $this->isOpen( $this->getWriterIndex() );
+ }
+
+ /**
+ * Determine if there are any pending changes that need to be rolled back
+ * or committed.
+ * @since 1.23
+ * @return bool
+ */
+ function hasMasterChanges() {
+ // Always 0, but who knows.. :)
+ $masterIndex = $this->getWriterIndex();
+ foreach ( $this->mConns as $conns2 ) {
+ if ( empty( $conns2[$masterIndex] ) ) {
+ continue;
+ }
+ /** @var DatabaseBase $conn */
+ foreach ( $conns2[$masterIndex] as $conn ) {
+ if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param mixed $value
+ * @return mixed
*/
function waitTimeout( $value = null ) {
return wfSetVar( $this->mWaitTimeout, $value );
@@ -966,7 +1023,7 @@ class LoadBalancer {
/**
* Disables/enables lag checks
- * @param $mode null
+ * @param null|bool $mode
* @return bool
*/
function allowLagged( $mode = null ) {
@@ -974,6 +1031,7 @@ class LoadBalancer {
return $this->mAllowLagged;
}
$this->mAllowLagged = $mode;
+
return $this->mAllowLagged;
}
@@ -984,6 +1042,7 @@ class LoadBalancer {
$success = true;
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
+ /** @var DatabaseBase[] $conns3 */
foreach ( $conns3 as $conn ) {
if ( !$conn->ping() ) {
$success = false;
@@ -991,12 +1050,13 @@ class LoadBalancer {
}
}
}
+
return $success;
}
/**
* Call a function with each open connection object
- * @param $callback
+ * @param callable $callback
* @param array $params
*/
function forEachOpenConnection( $callback, $params = array() ) {
@@ -1016,16 +1076,29 @@ class LoadBalancer {
* May attempt to open connections to slaves on the default DB. If there is
* no lag, the maximum lag will be reported as -1.
*
- * @param string $wiki Wiki ID, or false for the default database
- *
+ * @param bool|string $wiki Wiki ID, or false for the default database
* @return array ( host, max lag, index of max lagged host )
*/
function getMaxLag( $wiki = false ) {
$maxLag = -1;
$host = '';
$maxIndex = 0;
- if ( $this->getServerCount() > 1 ) { // no replication = no lag
+
+ if ( $this->getServerCount() <= 1 ) { // no replication = no lag
+ return array( $host, $maxLag, $maxIndex );
+ }
+
+ // Try to get the max lag info from the server cache
+ $key = 'loadbalancer:maxlag:cluster:' . $this->mServers[0]['host'];
+ $cache = ObjectCache::newAccelerator( array(), 'hash' );
+ $maxLagInfo = $cache->get( $key ); // (host, lag, index)
+
+ // Fallback to connecting to each slave and getting the lag
+ if ( !$maxLagInfo ) {
foreach ( $this->mServers as $i => $conn ) {
+ if ( $i == $this->getWriterIndex() ) {
+ continue; // nothing to check
+ }
$conn = false;
if ( $wiki === false ) {
$conn = $this->getAnyOpenConnection( $i );
@@ -1043,16 +1116,18 @@ class LoadBalancer {
$maxIndex = $i;
}
}
+ $maxLagInfo = array( $host, $maxLag, $maxIndex );
+ $cache->set( $key, $maxLagInfo, 5 );
}
- return array( $host, $maxLag, $maxIndex );
+
+ return $maxLagInfo;
}
/**
* Get lag time for each server
* Results are cached for a short time in memcached, and indefinitely in the process cache
*
- * @param $wiki
- *
+ * @param string|bool $wiki
* @return array
*/
function getLagTimes( $wiki = false ) {
@@ -1068,6 +1143,7 @@ class LoadBalancer {
$this->mLagTimes = $this->getLoadMonitor()->getLagTimes(
array_keys( $this->mServers ), $wiki );
}
+
return $this->mLagTimes;
}
@@ -1082,8 +1158,7 @@ class LoadBalancer {
* function instead of Database::getLag() avoids a fatal error in this
* case on many installations.
*
- * @param $conn DatabaseBase
- *
+ * @param DatabaseBase $conn
* @return int
*/
function safeGetLag( $conn ) {
@@ -1112,14 +1187,16 @@ class LoadBalancer {
class DBConnRef implements IDatabase {
/** @var LoadBalancer */
protected $lb;
+
/** @var DatabaseBase|null */
protected $conn;
- /** @var Array|null */
+
+ /** @var array|null */
protected $params;
/**
- * @param $lb LoadBalancer
- * @param $conn DatabaseBase|array Connection or (server index, group, wiki ID) array
+ * @param LoadBalancer $lb
+ * @param DatabaseBase|array $conn Connection or (server index, group, wiki ID) array
*/
public function __construct( LoadBalancer $lb, $conn ) {
$this->lb = $lb;
@@ -1135,6 +1212,7 @@ class DBConnRef implements IDatabase {
list( $db, $groups, $wiki ) = $this->params;
$this->conn = $this->lb->getConnection( $db, $groups, $wiki );
}
+
return call_user_func_array( array( $this->conn, $name ), $arguments );
}
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 519e2dfd..7281485b 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -32,61 +32,35 @@ interface LoadMonitor {
*
* @param LoadBalancer $parent
*/
- function __construct( $parent );
+ public function __construct( $parent );
/**
* Perform pre-connection load ratio adjustment.
- * @param $loads array
- * @param string $group the selected query group
- * @param $wiki String
+ * @param array $loads
+ * @param string|bool $group The selected query group. Default: false
+ * @param string|bool $wiki Default: false
*/
- function scaleLoads( &$loads, $group = false, $wiki = false );
-
- /**
- * Perform post-connection backoff.
- *
- * If the connection is in overload, this should return a backoff factor
- * which will be used to control polling time. The number of threads
- * connected is a good measure.
- *
- * If there is no overload, zero can be returned.
- *
- * A threshold thread count is given, the concrete class may compare this
- * to the running thread count. The threshold may be false, which indicates
- * that the sysadmin has not configured this feature.
- *
- * @param $conn DatabaseBase
- * @param $threshold Float
- */
- function postConnectionBackoff( $conn, $threshold );
+ public function scaleLoads( &$loads, $group = false, $wiki = false );
/**
* Return an estimate of replication lag for each server
*
- * @param $serverIndexes
- * @param $wiki
+ * @param array $serverIndexes
+ * @param string $wiki
*
* @return array
*/
- function getLagTimes( $serverIndexes, $wiki );
+ public function getLagTimes( $serverIndexes, $wiki );
}
-class LoadMonitor_Null implements LoadMonitor {
- function __construct( $parent ) {
- }
-
- function scaleLoads( &$loads, $group = false, $wiki = false ) {
+class LoadMonitorNull implements LoadMonitor {
+ public function __construct( $parent ) {
}
- function postConnectionBackoff( $conn, $threshold ) {
+ public function scaleLoads( &$loads, $group = false, $wiki = false ) {
}
- /**
- * @param $serverIndexes
- * @param $wiki
- * @return array
- */
- function getLagTimes( $serverIndexes, $wiki ) {
+ public function getLagTimes( $serverIndexes, $wiki ) {
return array_fill_keys( $serverIndexes, 0 );
}
}
@@ -97,58 +71,44 @@ class LoadMonitor_Null implements LoadMonitor {
*
* @ingroup Database
*/
-class LoadMonitor_MySQL implements LoadMonitor {
+class LoadMonitorMySQL implements LoadMonitor {
+ /** @var LoadBalancer */
+ public $parent;
+ /** @var BagOStuff */
+ protected $cache;
- /**
- * @var LoadBalancer
- */
- var $parent;
+ public function __construct( $parent ) {
+ global $wgMemc;
- /**
- * @param LoadBalancer $parent
- */
- function __construct( $parent ) {
$this->parent = $parent;
+ $this->cache = $wgMemc ?: wfGetMainCache();
}
- /**
- * @param $loads
- * @param $group bool
- * @param $wiki bool
- */
- function scaleLoads( &$loads, $group = false, $wiki = false ) {
+ public function scaleLoads( &$loads, $group = false, $wiki = false ) {
}
- /**
- * @param $serverIndexes
- * @param $wiki
- * @return array
- */
- function getLagTimes( $serverIndexes, $wiki ) {
+ public function getLagTimes( $serverIndexes, $wiki ) {
if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
// Single server only, just return zero without caching
return array( 0 => 0 );
}
- wfProfileIn( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ );
+
$expiry = 5;
$requestRate = 10;
- global $wgMemc;
- if ( empty( $wgMemc ) ) {
- $wgMemc = wfGetMainCache();
- }
-
+ $cache = $this->cache;
$masterName = $this->parent->getServerName( 0 );
$memcKey = wfMemcKey( 'lag_times', $masterName );
- $times = $wgMemc->get( $memcKey );
+ $times = $cache->get( $memcKey );
if ( is_array( $times ) ) {
# Randomly recache with probability rising over $expiry
$elapsed = time() - $times['timestamp'];
$chance = max( 0, ( $expiry - $elapsed ) * $requestRate );
if ( mt_rand( 0, $chance ) != 0 ) {
unset( $times['timestamp'] ); // hide from caller
- wfProfileOut( __METHOD__ );
+
return $times;
}
wfIncrStats( 'lag_cache_miss_expired' );
@@ -157,15 +117,15 @@ class LoadMonitor_MySQL implements LoadMonitor {
}
# Cache key missing or expired
- if ( $wgMemc->add( "$memcKey:lock", 1, 10 ) ) {
+ if ( $cache->add( "$memcKey:lock", 1, 10 ) ) {
# Let this process alone update the cache value
- $unlocker = new ScopedCallback( function() use ( $wgMemc, $memcKey ) {
- $wgMemc->delete( $memcKey );
+ $unlocker = new ScopedCallback( function () use ( $cache, $memcKey ) {
+ $cache->delete( $memcKey );
} );
} elseif ( is_array( $times ) ) {
# Could not acquire lock but an old cache exists, so use it
unset( $times['timestamp'] ); // hide from caller
- wfProfileOut( __METHOD__ );
+
return $times;
}
@@ -177,34 +137,19 @@ class LoadMonitor_MySQL implements LoadMonitor {
$times[$i] = $conn->getLag();
} elseif ( false !== ( $conn = $this->parent->openConnection( $i, $wiki ) ) ) {
$times[$i] = $conn->getLag();
+ // Close the connection to avoid sleeper connections piling up.
+ // Note that the caller will pick one of these DBs and reconnect,
+ // which is slightly inefficient, but this only matters for the lag
+ // time cache miss cache, which is far less common that cache hits.
+ $this->parent->closeConnection( $conn );
}
}
# Add a timestamp key so we know when it was cached
$times['timestamp'] = time();
- $wgMemc->set( $memcKey, $times, $expiry + 10 );
+ $cache->set( $memcKey, $times, $expiry + 10 );
unset( $times['timestamp'] ); // hide from caller
- wfProfileOut( __METHOD__ );
return $times;
}
-
- /**
- * @param $conn DatabaseBase
- * @param $threshold
- * @return int
- */
- function postConnectionBackoff( $conn, $threshold ) {
- if ( !$threshold ) {
- return 0;
- }
- $status = $conn->getMysqlStatus( "Thread%" );
- if ( $status['Threads_running'] > $threshold ) {
- $server = $conn->getProperty( 'mServer' );
- wfLogDBError( "LB backoff from $server - Threads_running = {$status['Threads_running']}\n" );
- return $status['Threads_connected'];
- } else {
- return 0;
- }
- }
}
diff --git a/includes/db/ORMIterator.php b/includes/db/ORMIterator.php
index 077eab0f..e8104b6f 100644
--- a/includes/db/ORMIterator.php
+++ b/includes/db/ORMIterator.php
@@ -27,5 +27,4 @@
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
interface ORMIterator extends Iterator {
-
}
diff --git a/includes/db/ORMResult.php b/includes/db/ORMResult.php
index 160033c4..327d20d9 100644
--- a/includes/db/ORMResult.php
+++ b/includes/db/ORMResult.php
@@ -30,14 +30,13 @@
*/
class ORMResult implements ORMIterator {
-
/**
* @var ResultWrapper
*/
protected $res;
/**
- * @var integer
+ * @var int
*/
protected $key;
@@ -63,7 +62,7 @@ class ORMResult implements ORMIterator {
}
/**
- * @param $row
+ * @param bool|object $row
*/
protected function setCurrent( $row ) {
if ( $row === false ) {
@@ -74,14 +73,14 @@ class ORMResult implements ORMIterator {
}
/**
- * @return integer
+ * @return int
*/
public function count() {
return $this->res->numRows();
}
/**
- * @return boolean
+ * @return bool
*/
public function isEmpty() {
return $this->res->numRows() === 0;
@@ -95,7 +94,7 @@ class ORMResult implements ORMIterator {
}
/**
- * @return integer
+ * @return int
*/
public function key() {
return $this->key;
@@ -114,10 +113,9 @@ class ORMResult implements ORMIterator {
}
/**
- * @return boolean
+ * @return bool
*/
public function valid() {
return $this->current !== false;
}
-
}
diff --git a/includes/db/ORMRow.php b/includes/db/ORMRow.php
index 5ce3794d..b0bade33 100644
--- a/includes/db/ORMRow.php
+++ b/includes/db/ORMRow.php
@@ -32,7 +32,6 @@
*/
class ORMRow implements IORMRow {
-
/**
* The fields of the object.
* field name (w/o prefix) => value
@@ -79,7 +78,7 @@ class ORMRow implements IORMRow {
*
* @param IORMTable|null $table Deprecated since 1.22
* @param array|null $fields
- * @param boolean $loadDefaults Deprecated since 1.22
+ * @param bool $loadDefaults Deprecated since 1.22
*/
public function __construct( IORMTable $table = null, $fields = null, $loadDefaults = false ) {
$this->table = $table;
@@ -102,8 +101,8 @@ class ORMRow implements IORMRow {
* @deprecated since 1.22
*
* @param array|null $fields
- * @param boolean $override
- * @param boolean $skipLoaded
+ * @param bool $override
+ * @param bool $skipLoaded
*
* @return bool Success indicator
*/
@@ -130,8 +129,10 @@ class ORMRow implements IORMRow {
if ( $result !== false ) {
$this->setFields( $this->table->getFieldsFromDBResult( $result ), $override );
+
return true;
}
+
return false;
}
@@ -144,7 +145,7 @@ class ORMRow implements IORMRow {
* @since 1.20
*
* @param string $name Field name
- * @param $default mixed: Default value to return when none is found
+ * @param mixed $default Default value to return when none is found
* (default: null)
*
* @throws MWException
@@ -166,7 +167,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param $name string
+ * @param string $name
*
* @return mixed
*/
@@ -194,7 +195,7 @@ class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @return integer|null
+ * @return int|null
*/
public function getId() {
return $this->getField( 'id' );
@@ -205,7 +206,7 @@ class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @param integer|null $id
+ * @param int|null $id
*/
public function setId( $id ) {
$this->setField( 'id', $id );
@@ -218,7 +219,7 @@ class ORMRow implements IORMRow {
*
* @param string $name
*
- * @return boolean
+ * @return bool
*/
public function hasField( $name ) {
return array_key_exists( $name, $this->fields );
@@ -229,11 +230,10 @@ class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @return boolean
+ * @return bool
*/
public function hasIdField() {
- return $this->hasField( 'id' )
- && !is_null( $this->getField( 'id' ) );
+ return $this->hasField( 'id' ) && !is_null( $this->getField( 'id' ) );
}
/**
@@ -252,7 +252,7 @@ class ORMRow implements IORMRow {
$value = $this->fields[$name];
// Skip null id fields so that the DBMS can set the default.
- if ( $name === 'id' && is_null ( $value ) ) {
+ if ( $name === 'id' && is_null( $value ) ) {
continue;
}
@@ -278,7 +278,7 @@ class ORMRow implements IORMRow {
* @since 1.20
*
* @param array $fields The fields to set
- * @param boolean $override Override already set fields with the provided values?
+ * @param bool $override Override already set fields with the provided values?
*/
public function setFields( array $fields, $override = true ) {
foreach ( $fields as $name => $value ) {
@@ -295,7 +295,7 @@ class ORMRow implements IORMRow {
* @since 1.20
*
* @param null|array $fields
- * @param boolean $incNullId
+ * @param bool $incNullId
*
* @return array
*/
@@ -328,7 +328,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $override
+ * @param bool $override
*/
public function loadDefaults( $override = true ) {
$this->setFields( $this->table->getDefaults(), $override );
@@ -343,7 +343,7 @@ class ORMRow implements IORMRow {
*
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function save( $functionName = null ) {
if ( $this->hasIdField() ) {
@@ -361,7 +361,7 @@ class ORMRow implements IORMRow {
*
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
protected function saveExisting( $functionName = null ) {
$dbw = $this->table->getWriteDbConnection();
@@ -400,7 +400,7 @@ class ORMRow implements IORMRow {
* @param string|null $functionName
* @param array|null $options
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
protected function insert( $functionName = null, array $options = null ) {
$dbw = $this->table->getWriteDbConnection();
@@ -430,7 +430,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22, use IORMTable->removeRow
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function remove() {
$this->beforeRemove();
@@ -456,8 +456,9 @@ class ORMRow implements IORMRow {
/**
* Before removal of an object happens, @see beforeRemove gets called.
- * This method loads the fields of which the names have been returned by this one (or all fields if null is returned).
- * This allows for loading info needed after removal to get rid of linked data and the like.
+ * This method loads the fields of which the names have been returned by
+ * this one (or all fields if null is returned). This allows for loading
+ * info needed after removal to get rid of linked data and the like.
*
* @since 1.20
*
@@ -523,9 +524,9 @@ class ORMRow implements IORMRow {
* @deprecated since 1.22, use IORMTable->addToField
*
* @param string $field
- * @param integer $amount
+ * @param int $amount
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function addToField( $field, $amount ) {
return $this->table->addToField( $this->getUpdateConditions(), $field, $amount );
@@ -552,7 +553,6 @@ class ORMRow implements IORMRow {
* @param array|string|null $summaryFields
*/
public function loadSummaryFields( $summaryFields = null ) {
-
}
/**
@@ -561,7 +561,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $update
+ * @param bool $update
*/
public function setUpdateSummaries( $update ) {
$this->updateSummaries = $update;
@@ -573,7 +573,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $summaryMode
+ * @param bool $summaryMode
*/
public function setSummaryMode( $summaryMode ) {
$this->inSummaryMode = $summaryMode;
@@ -590,5 +590,4 @@ class ORMRow implements IORMRow {
public function getTable() {
return $this->table;
}
-
}
diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php
index 5f6723b9..2f898b75 100644
--- a/includes/db/ORMTable.php
+++ b/includes/db/ORMTable.php
@@ -29,7 +29,6 @@
*/
class ORMTable extends DBAccessBase implements IORMTable {
-
/**
* Cache for instances, used by the singleton method.
*
@@ -81,7 +80,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @var integer DB_ enum
+ * @var int DB_ enum
*/
protected $readDb = DB_SLAVE;
@@ -96,7 +95,9 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param string|null $rowClass
* @param string $fieldPrefix
*/
- public function __construct( $tableName = '', array $fields = array(), array $defaults = array(), $rowClass = null, $fieldPrefix = '' ) {
+ public function __construct( $tableName = '', array $fields = array(),
+ array $defaults = array(), $rowClass = null, $fieldPrefix = ''
+ ) {
$this->tableName = $tableName;
$this->fields = $fields;
$this->defaults = $defaults;
@@ -201,8 +202,10 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @return ORMResult
*/
public function select( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
$res = $this->rawSelect( $fields, $conditions, $options, $functionName );
+
return new ORMResult( $this, $res );
}
@@ -217,11 +220,12 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return array of row objects
- * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode).
+ * @return array Array of row objects
+ * @throws DBQueryError If the query failed (even if the database was in ignoreErrors mode).
*/
public function selectObjects( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
$result = $this->selectFields( $fields, $conditions, $options, false, $functionName );
$objects = array();
@@ -239,19 +243,19 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @since 1.20
*
* @param null|string|array $fields
- * @param array $conditions
- * @param array $options
- * @param null|string $functionName
- *
+ * @param array $conditions
+ * @param array $options
+ * @param null|string $functionName
* @return ResultWrapper
- * @throws DBQueryError if the quey failed (even if the database was in ignoreErrors mode).
+ * @throws DBQueryError If the query failed (even if the database was in
+ * ignoreErrors mode).
*/
public function rawSelect( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
if ( is_null( $fields ) ) {
$fields = array_keys( $this->getFields() );
- }
- else {
+ } else {
$fields = (array)$fields;
}
@@ -307,13 +311,14 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array|string|null $fields
* @param array $conditions
* @param array $options
- * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param bool $collapse Set to false to always return each result row as associative array.
* @param string|null $functionName
*
- * @return array of array
+ * @return array Array of array
*/
public function selectFields( $fields = null, array $conditions = array(),
- array $options = array(), $collapse = true, $functionName = null ) {
+ array $options = array(), $collapse = true, $functionName = null
+ ) {
$objects = array();
$result = $this->rawSelect( $fields, $conditions, $options, $functionName );
@@ -325,8 +330,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
if ( $collapse ) {
if ( count( $fields ) === 1 ) {
$objects = array_map( 'array_shift', $objects );
- }
- elseif ( count( $fields ) === 2 ) {
+ } elseif ( count( $fields ) === 2 ) {
$o = array();
foreach ( $objects as $object ) {
@@ -354,7 +358,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @return IORMRow|bool False on failure
*/
public function selectRow( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
$options['LIMIT'] = 1;
$objects = $this->select( $fields, $conditions, $options, $functionName );
@@ -373,10 +378,11 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return ResultWrapper
+ * @return stdClass
*/
public function rawSelectRow( array $fields, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
$dbr = $this->getReadDbConnection();
$result = $dbr->selectRow(
@@ -388,6 +394,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
);
$this->releaseConnection( $dbr );
+
return $result;
}
@@ -403,13 +410,14 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array|string|null $fields
* @param array $conditions
* @param array $options
- * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param bool $collapse Set to false to always return each result row as associative array.
* @param string|null $functionName
*
* @return mixed|array|bool False on failure
*/
public function selectFieldsRow( $fields = null, array $conditions = array(),
- array $options = array(), $collapse = true, $functionName = null ) {
+ array $options = array(), $collapse = true, $functionName = null
+ ) {
$options['LIMIT'] = 1;
$objects = $this->selectFields( $fields, $conditions, $options, $collapse, $functionName );
@@ -425,7 +433,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @param array $conditions
*
- * @return boolean
+ * @return bool
*/
public function has( array $conditions = array() ) {
return $this->selectRow( array( 'id' ), $conditions ) !== false;
@@ -436,7 +444,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.21
*
- * @return boolean
+ * @return bool
*/
public function exists() {
$dbr = $this->getReadDbConnection();
@@ -458,7 +466,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $conditions
* @param array $options
*
- * @return integer
+ * @return int
*/
public function count( array $conditions = array(), array $options = array() ) {
$res = $this->rawSelectRow(
@@ -479,7 +487,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $conditions
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function delete( array $conditions, $functionName = null ) {
$dbw = $this->getWriteDbConnection();
@@ -491,6 +499,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
) !== false; // DatabaseBase::delete does not always return true for success as documented...
$this->releaseConnection( $dbw );
+
return $result;
}
@@ -499,8 +508,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @param boolean $requireParams
- * @param boolean $setDefaults
+ * @param bool $requireParams
+ * @param bool $setDefaults
*
* @return array
*/
@@ -535,7 +544,9 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
if ( $setDefaults && $hasDefault ) {
- $default = is_array( $defaults[$field] ) ? implode( '|', $defaults[$field] ) : $defaults[$field];
+ $default = is_array( $defaults[$field] )
+ ? implode( '|', $defaults[$field] )
+ : $defaults[$field];
$params[$field][ApiBase::PARAM_DFLT] = $default;
}
}
@@ -561,16 +572,17 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @return integer DB_ enum
+ * @return int DB_ enum
*/
public function getReadDb() {
return $this->readDb;
}
/**
- * Set the database ID to use for read operations, use DB_XXX constants or an index to the load balancer setup.
+ * Set the database ID to use for read operations, use DB_XXX constants or
+ * an index to the load balancer setup.
*
- * @param integer $db
+ * @param int $db
*
* @since 1.20
*/
@@ -583,7 +595,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used)
+ * @return string|bool The target wiki, in a form that LBFactory understands
+ * (or false if the local wiki is used)
*/
public function getTargetWiki() {
return $this->wiki;
@@ -592,7 +605,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
/**
* Set the ID of the any foreign wiki to use as a target for database operations
*
- * @param string|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used)
+ * @param string|bool $wiki The target wiki, in a form that LBFactory
+ * understands (or false if the local wiki shall be used)
*
* @since 1.20
*/
@@ -634,13 +648,15 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @see LoadBalancer::reuseConnection
*
- * @param DatabaseBase $db the database
+ * @param DatabaseBase $db
*
* @since 1.20
*/
+ // @codingStandardsIgnoreStart Suppress "useless method overriding" sniffer warning
public function releaseConnection( DatabaseBase $db ) {
parent::releaseConnection( $db ); // just make it public
}
+ // @codingStandardsIgnoreEnd
/**
* Update the records matching the provided conditions by
@@ -652,7 +668,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $values
* @param array $conditions
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function update( array $values, array $conditions = array() ) {
$dbw = $this->getWriteDbConnection();
@@ -665,6 +681,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
) !== false; // DatabaseBase::update does not always return true for success as documented...
$this->releaseConnection( $dbw );
+
return $result;
}
@@ -711,8 +728,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
if ( is_array( $value ) ) {
$field = $value[0];
$value = $value[1];
- }
- else {
+ } else {
$value = explode( ' ', $value, 2 );
$value[0] = $this->getPrefixedField( $value[0] );
$prefixedValues[] = implode( ' ', $value );
@@ -732,7 +748,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @param array|string $fields
+ * @param array $fields
*
* @return array
*/
@@ -809,7 +825,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @since 1.20
*
* @param stdClass $result
- *
+ * @throws MWException
* @return array
*/
public function getFieldsFromDBResult( stdClass $result ) {
@@ -872,7 +888,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
/**
* @see ORMTable::newRowFromFromDBResult
*
- * @deprecated use newRowFromDBResult instead
+ * @deprecated since 1.20 use newRowFromDBResult instead
* @since 1.20
*
* @param stdClass $result
@@ -899,11 +915,11 @@ class ORMTable extends DBAccessBase implements IORMTable {
/**
* @see ORMTable::newRow
*
- * @deprecated use newRow instead
+ * @deprecated since 1.20 use newRow instead
* @since 1.20
*
* @param array $data
- * @param boolean $loadDefaults
+ * @param bool $loadDefaults
*
* @return IORMRow
*/
@@ -917,7 +933,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @since 1.20
*
* @param array $fields
- * @param boolean $loadDefaults
+ * @param bool $loadDefaults
*
* @return IORMRow
*/
@@ -945,7 +961,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @param string $name
*
- * @return boolean
+ * @return bool
*/
public function canHaveField( $name ) {
return array_key_exists( $name, $this->getFields() );
@@ -959,7 +975,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param IORMRow $row The row to save
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function updateRow( IORMRow $row, $functionName = null ) {
$dbw = $this->getWriteDbConnection();
@@ -986,7 +1002,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param string|null $functionName
* @param array|null $options
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function insertRow( IORMRow $row, $functionName = null, array $options = null ) {
$dbw = $this->getWriteDbConnection();
@@ -1052,7 +1068,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param IORMRow $row
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function removeRow( IORMRow $row, $functionName = null ) {
$success = $this->delete(
@@ -1071,9 +1087,9 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @param array $conditions
* @param string $field
- * @param integer $amount
+ * @param int $amount
*
- * @return boolean Success indicator
+ * @return bool Success indicator
* @throws MWException
*/
public function addToField( array $conditions, $field, $amount ) {
@@ -1103,5 +1119,4 @@ class ORMTable extends DBAccessBase implements IORMTable {
return $success;
}
-
}