summaryrefslogtreecommitdiff
path: root/includes/db
diff options
context:
space:
mode:
Diffstat (limited to 'includes/db')
-rw-r--r--includes/db/CloneDatabase.php23
-rw-r--r--includes/db/Database.php625
-rw-r--r--includes/db/DatabaseError.php22
-rw-r--r--includes/db/DatabaseIbm_db2.php1721
-rw-r--r--includes/db/DatabaseMssql.php105
-rw-r--r--includes/db/DatabaseMysql.php61
-rw-r--r--includes/db/DatabaseOracle.php76
-rw-r--r--includes/db/DatabasePostgres.php156
-rw-r--r--includes/db/DatabaseSqlite.php47
-rw-r--r--includes/db/DatabaseUtility.php13
-rw-r--r--includes/db/IORMRow.php5
-rw-r--r--includes/db/IORMTable.php91
-rw-r--r--includes/db/LBFactory.php22
-rw-r--r--includes/db/LBFactory_Multi.php11
-rw-r--r--includes/db/LBFactory_Single.php2
-rw-r--r--includes/db/LoadBalancer.php150
-rw-r--r--includes/db/LoadMonitor.php9
-rw-r--r--includes/db/ORMIterator.php4
-rw-r--r--includes/db/ORMResult.php2
-rw-r--r--includes/db/ORMRow.php47
-rw-r--r--includes/db/ORMTable.php295
21 files changed, 1151 insertions, 2336 deletions
diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
index 4e43642f..4e443741 100644
--- a/includes/db/CloneDatabase.php
+++ b/includes/db/CloneDatabase.php
@@ -60,9 +60,9 @@ class CloneDatabase {
* Constructor
*
* @param $db DatabaseBase A database subclass
- * @param $tablesToClone Array An array of tables to clone, unprefixed
- * @param $newTablePrefix String Prefix to assign to the tables
- * @param $oldTablePrefix String Prefix on current tables, if not $wgDBprefix
+ * @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
*/
public function __construct( DatabaseBase $db, array $tablesToClone,
@@ -77,7 +77,7 @@ class CloneDatabase {
/**
* Set whether to use temporary tables or not
- * @param $u Bool Use temporary tables when cloning the structure
+ * @param bool $u Use temporary tables when cloning the structure
*/
public function useTemporaryTables( $u = true ) {
$this->useTemporaryTables = $u;
@@ -87,35 +87,32 @@ class CloneDatabase {
* Clone the table structure
*/
public function cloneTableStructure() {
-
foreach( $this->tablesToClone as $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.
-
+
self::changePrefix( $this->oldTablePrefix );
$oldTableName = $this->db->tableName( $tbl, 'raw' );
-
+
self::changePrefix( $this->newTablePrefix );
$newTableName = $this->db->tableName( $tbl, 'raw' );
-
+
if( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) ) ) {
$this->db->dropTable( $tbl, __METHOD__ );
- wfDebug( __METHOD__." dropping {$newTableName}\n", true);
+ wfDebug( __METHOD__ . " dropping {$newTableName}\n", true );
//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", true );
$this->db->duplicateTableStructure( $oldTableName, $newTableName, $this->useTemporaryTables );
-
}
-
}
/**
* Change the prefix back to the original.
- * @param $dropTables bool Optionally drop the tables we created
+ * @param bool $dropTables Optionally drop the tables we created
*/
public function destroy( $dropTables = false ) {
if( $dropTables ) {
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 5f10b97d..65a74abf 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -49,10 +49,10 @@ interface DatabaseType {
/**
* Open a connection to the database. Usually aborts on failure
*
- * @param $server String: database server host
- * @param $user String: database user name
- * @param $password String: database user password
- * @param $dbName String: 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
*/
@@ -62,9 +62,10 @@ interface DatabaseType {
* Fetch the next row from the given result object, in object form.
* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
+ * If no more rows are available, false is returned.
*
* @param $res ResultWrapper|object as returned from DatabaseBase::query(), etc.
- * @return Row object
+ * @return object|bool
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchObject( $res );
@@ -72,9 +73,10 @@ interface DatabaseType {
/**
* Fetch the next row from the given result object, in associative array
* form. Fields are retrieved with $row['fieldname'].
+ * If no more rows are available, false is returned.
*
* @param $res ResultWrapper result object as returned from DatabaseBase::query(), etc.
- * @return Row object
+ * @return array|bool
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchRow( $res );
@@ -112,8 +114,8 @@ interface DatabaseType {
* The value inserted should be fetched from nextSequenceValue()
*
* Example:
- * $id = $dbw->nextSequenceValue('page_page_id_seq');
- * $dbw->insert('page',array('page_id' => $id));
+ * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
+ * $dbw->insert( 'page', array( 'page_id' => $id ) );
* $id = $dbw->insertId();
*
* @return int
@@ -149,8 +151,8 @@ interface DatabaseType {
* mysql_fetch_field() wrapper
* Returns false if the field doesn't exist
*
- * @param $table string: table name
- * @param $field string: field name
+ * @param string $table table name
+ * @param string $field field name
*
* @return Field
*/
@@ -158,9 +160,9 @@ interface DatabaseType {
/**
* Get information about an index into an object
- * @param $table string: Table name
- * @param $index string: Index name
- * @param $fname string: Calling function name
+ * @param string $table Table name
+ * @param string $index Index name
+ * @param string $fname Calling function name
* @return Mixed: Database-specific index description class or false if the index does not exist
*/
function indexInfo( $table, $index, $fname = 'Database::indexInfo' );
@@ -176,7 +178,7 @@ interface DatabaseType {
/**
* Wrapper for addslashes()
*
- * @param $s string: to be slashed.
+ * @param string $s to be slashed.
* @return string: slashed string.
*/
function strencode( $s );
@@ -249,6 +251,37 @@ abstract class DatabaseBase implements DatabaseType {
protected $delimiter = ';';
+ /**
+ * Remembers the function name given for starting the most recent transaction via begin().
+ * Used to provide additional context for error reporting.
+ *
+ * @var String
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxFname = null;
+
+ /**
+ * Record if possible write queries were done in the last transaction started
+ *
+ * @var Bool
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxDoneWrites = false;
+
+ /**
+ * Record if the current transaction was started implicitly due to DBO_TRX being set.
+ *
+ * @var Bool
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxAutomatic = false;
+
+ /**
+ * @since 1.21
+ * @var file handle for upgrade
+ */
+ protected $fileHandle = null;
+
# ------------------------------------------------------------------------------
# Accessors
# ------------------------------------------------------------------------------
@@ -266,6 +299,13 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * @return string: command delimiter used by this database engine
+ */
+ public function getDelimiter() {
+ return $this->delimiter;
+ }
+
+ /**
* Boolean, controls output of large amounts of debug information.
* @param $debug bool|null
* - true to enable debugging
@@ -329,7 +369,7 @@ abstract class DatabaseBase implements DatabaseType {
* Historically, transactions were allowed to be "nested". This is no
* longer supported, so this function really only returns a boolean.
*
- * @param $level int An integer (0 or 1), or omitted to leave it unchanged.
+ * @param int $level An integer (0 or 1), or omitted to leave it unchanged.
* @return int The previous value
*/
public function trxLevel( $level = null ) {
@@ -338,7 +378,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Get/set the number of errors logged. Only useful when errors are ignored
- * @param $count int The count to set, or omitted to leave it unchanged.
+ * @param int $count The count to set, or omitted to leave it unchanged.
* @return int The error count
*/
public function errorCount( $count = null ) {
@@ -347,7 +387,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Get/set the table prefix.
- * @param $prefix string The table prefix to set, or omitted to leave it unchanged.
+ * @param string $prefix The table prefix to set, or omitted to leave it unchanged.
* @return string The previous table prefix.
*/
public function tablePrefix( $prefix = null ) {
@@ -355,10 +395,19 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Set the filehandle to copy write statements to.
+ *
+ * @param $fh filehandle
+ */
+ public function setFileHandle( $fh ) {
+ $this->fileHandle = $fh;
+ }
+
+ /**
* Get properties passed down from the server info array of the load
* balancer.
*
- * @param $name string The entry of the info array to get, or null to get the
+ * @param string $name The entry of the info array to get, or null to get the
* whole array
*
* @return LoadBalancer|null
@@ -441,7 +490,7 @@ abstract class DatabaseBase implements DatabaseType {
* Returns true if this database uses timestamps rather than integers
*
* @return bool
- */
+ */
public function realTimestamps() {
return false;
}
@@ -509,7 +558,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return bool
*/
public function writesOrCallbacksPending() {
- return $this->mTrxLevel && ( $this->mDoneWrites || $this->mTrxIdleCallbacks );
+ return $this->mTrxLevel && ( $this->mTrxDoneWrites || $this->mTrxIdleCallbacks );
}
/**
@@ -536,7 +585,7 @@ abstract class DatabaseBase implements DatabaseType {
global $wgDebugDBTransactions;
$this->mFlags |= $flag;
if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) {
- wfDebug("Implicit transactions are now disabled.\n");
+ wfDebug( "Implicit transactions are now disabled.\n" );
}
}
@@ -549,7 +598,7 @@ abstract class DatabaseBase implements DatabaseType {
global $wgDebugDBTransactions;
$this->mFlags &= ~$flag;
if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
- wfDebug("Implicit transactions are now disabled.\n");
+ wfDebug( "Implicit transactions are now disabled.\n" );
}
}
@@ -605,12 +654,12 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Constructor.
- * @param $server String: database server host
- * @param $user String: database user name
- * @param $password String: database user password
- * @param $dbName String: 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
* @param $flags
- * @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php
+ * @param string $tablePrefix database table prefixes. By default use the prefix gave in LocalSettings.php
*/
function __construct( $server = false, $user = false, $password = false, $dbName = false,
$flags = 0, $tablePrefix = 'get from global'
@@ -623,12 +672,12 @@ abstract class DatabaseBase implements DatabaseType {
if ( $wgCommandLineMode ) {
$this->mFlags &= ~DBO_TRX;
if ( $wgDebugDBTransactions ) {
- wfDebug("Implicit transaction open disabled.\n");
+ wfDebug( "Implicit transaction open disabled.\n" );
}
} else {
$this->mFlags |= DBO_TRX;
if ( $wgDebugDBTransactions ) {
- wfDebug("Implicit transaction open enabled.\n");
+ wfDebug( "Implicit transaction open enabled.\n" );
}
}
}
@@ -671,14 +720,14 @@ abstract class DatabaseBase implements DatabaseType {
*
* @since 1.18
*
- * @param $dbType String A possible DB type
- * @param $p Array An array of options to pass to the constructor.
+ * @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
* @return DatabaseBase subclass or null
*/
- public final static function factory( $dbType, $p = array() ) {
+ final public static function factory( $dbType, $p = array() ) {
$canonicalDBTypes = array(
- 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
+ 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql'
);
$dbType = strtolower( $dbType );
$class = 'Database' . ucfirst( $dbType );
@@ -724,7 +773,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $errno
* @param $errstr
*/
- protected function connectionErrorHandler( $errno, $errstr ) {
+ protected function connectionErrorHandler( $errno, $errstr ) {
$this->mPHPError = $errstr;
}
@@ -732,6 +781,7 @@ abstract class DatabaseBase implements DatabaseType {
* Closes a database connection.
* if it is open : commits any open transactions
*
+ * @throws MWException
* @return Bool operation success. true if already closed.
*/
public function close() {
@@ -741,8 +791,14 @@ abstract class DatabaseBase implements DatabaseType {
$this->mOpened = false;
if ( $this->mConn ) {
if ( $this->trxLevel() ) {
- $this->commit( __METHOD__ );
+ if ( !$this->mTrxAutomatic ) {
+ wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
+ " performing implicit commit before closing connection!" );
+ }
+
+ $this->commit( __METHOD__, 'flush' );
}
+
$ret = $this->closeConnection();
$this->mConn = false;
return $ret;
@@ -756,10 +812,11 @@ abstract class DatabaseBase implements DatabaseType {
* @since 1.20
* @return bool: Whether connection was closed successfully
*/
- protected abstract function closeConnection();
+ abstract protected function closeConnection();
/**
- * @param $error String: 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' ) {
$myError = $this->lastError();
@@ -777,7 +834,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $sql String: SQL query.
* @return ResultWrapper Result object to feed to fetchObject, fetchRow, ...; or false on failure
*/
- protected abstract function doQuery( $sql );
+ abstract protected function doQuery( $sql );
/**
* Determine whether a query writes to the DB.
@@ -809,9 +866,9 @@ abstract class DatabaseBase implements DatabaseType {
* comment (you can use __METHOD__ or add some extra info)
* @param $tempIgnore Boolean: 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
* for a successful read query, or false on failure if $tempIgnore set
- * @throws DBQueryError Thrown when the database returns an error of any kind
*/
public function query( $sql, $fname = '', $tempIgnore = false ) {
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
@@ -849,24 +906,34 @@ abstract class DatabaseBase implements DatabaseType {
} else {
$userName = '';
}
- $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 );
+
+ // Add trace comment to the begin of the sql string, right after the operator.
+ // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
+ $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
# If DBO_TRX is set, start a transaction
- if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) {
- # avoid establishing transactions for SHOW and SET statements too -
+ if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
+ $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
$sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
global $wgDebugDBTransactions;
if ( $wgDebugDBTransactions ) {
- wfDebug("Implicit transaction start.\n");
+ wfDebug( "Implicit transaction start.\n" );
}
$this->begin( __METHOD__ . " ($fname)" );
+ $this->mTrxAutomatic = true;
}
}
+ # Keep track of whether the transaction has write queries pending
+ if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
+ $this->mTrxDoneWrites = true;
+ }
+
if ( $this->debug() ) {
static $cnt = 0;
@@ -933,6 +1000,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $sql String
* @param $fname String
* @param $tempIgnore Boolean
+ * @throws DBQueryError
*/
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
# Ignore errors during error handling to avoid infinite recursion
@@ -981,7 +1049,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Execute a prepared query with the various arguments
- * @param $prepared String: the prepared sql
+ * @param string $prepared the prepared sql
* @param $args Mixed: Either an array here, or put scalars as varargs
*
* @return ResultWrapper
@@ -1001,8 +1069,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* For faking prepared SQL statements on DBs that don't support it directly.
*
- * @param $preparedQuery String: a 'preparable' SQL statement
- * @param $args Array of arguments to fill it with
+ * @param string $preparedQuery a 'preparable' SQL statement
+ * @param array $args of arguments to fill it with
* @return string executable SQL
*/
public function fillPrepared( $preparedQuery, $args ) {
@@ -1019,6 +1087,7 @@ abstract class DatabaseBase implements DatabaseType {
* while we're doing this.
*
* @param $matches Array
+ * @throws DBUnexpectedError
* @return String
*/
protected function fillPreparedArg( $matches ) {
@@ -1028,7 +1097,7 @@ abstract class DatabaseBase implements DatabaseType {
case '\\&': return '&';
}
- list( /* $n */ , $arg ) = each( $this->preparedArgs );
+ list( /* $n */, $arg ) = each( $this->preparedArgs );
switch( $matches[1] ) {
case '?': return $this->addQuotes( $arg );
@@ -1058,12 +1127,12 @@ abstract class DatabaseBase implements DatabaseType {
*
* If no result rows are returned from the query, false is returned.
*
- * @param $table string|array Table name. See DatabaseBase::select() for details.
- * @param $var string The field name to select. This must be a valid SQL
+ * @param string|array $table Table name. See DatabaseBase::select() for details.
+ * @param string $var The field name to select. This must be a valid SQL
* fragment: do not use unvalidated user input.
- * @param $cond string|array The condition array. See DatabaseBase::select() for details.
- * @param $fname string The function name of the caller.
- * @param $options string|array The query options. See DatabaseBase::select() for details.
+ * @param string|array $cond The condition array. See DatabaseBase::select() for details.
+ * @param string $fname The function name of the caller.
+ * @param string|array $options The query options. See DatabaseBase::select() for details.
*
* @return bool|mixed The value from the field, or false on failure.
*/
@@ -1095,7 +1164,7 @@ abstract class DatabaseBase implements DatabaseType {
* Returns an optional USE INDEX clause to go after the table, and a
* string to go at the end of the query.
*
- * @param $options Array: associative array of options to be turned into
+ * @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()
@@ -1112,26 +1181,9 @@ abstract class DatabaseBase implements DatabaseType {
}
}
- if ( isset( $options['GROUP BY'] ) ) {
- $gb = is_array( $options['GROUP BY'] )
- ? implode( ',', $options['GROUP BY'] )
- : $options['GROUP BY'];
- $preLimitTail .= " GROUP BY {$gb}";
- }
+ $preLimitTail .= $this->makeGroupByWithHaving( $options );
- if ( isset( $options['HAVING'] ) ) {
- $having = is_array( $options['HAVING'] )
- ? $this->makeList( $options['HAVING'], LIST_AND )
- : $options['HAVING'];
- $preLimitTail .= " HAVING {$having}";
- }
-
- if ( isset( $options['ORDER BY'] ) ) {
- $ob = is_array( $options['ORDER BY'] )
- ? implode( ',', $options['ORDER BY'] )
- : $options['ORDER BY'];
- $preLimitTail .= " ORDER BY {$ob}";
- }
+ $preLimitTail .= $this->makeOrderBy( $options );
// if (isset($options['LIMIT'])) {
// $tailOpts .= $this->limitResult('', $options['LIMIT'],
@@ -1194,14 +1246,57 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Returns an optional GROUP BY with an optional HAVING
+ *
+ * @param array $options associative array of options
+ * @return string
+ * @see DatabaseBase::select()
+ * @since 1.21
+ */
+ public function makeGroupByWithHaving( $options ) {
+ $sql = '';
+ if ( isset( $options['GROUP BY'] ) ) {
+ $gb = is_array( $options['GROUP BY'] )
+ ? implode( ',', $options['GROUP BY'] )
+ : $options['GROUP BY'];
+ $sql .= ' GROUP BY ' . $gb;
+ }
+ if ( isset( $options['HAVING'] ) ) {
+ $having = is_array( $options['HAVING'] )
+ ? $this->makeList( $options['HAVING'], LIST_AND )
+ : $options['HAVING'];
+ $sql .= ' HAVING ' . $having;
+ }
+ return $sql;
+ }
+
+ /**
+ * Returns an optional ORDER BY
+ *
+ * @param array $options associative array of options
+ * @return string
+ * @see DatabaseBase::select()
+ * @since 1.21
+ */
+ public function makeOrderBy( $options ) {
+ if ( isset( $options['ORDER BY'] ) ) {
+ $ob = is_array( $options['ORDER BY'] )
+ ? implode( ',', $options['ORDER BY'] )
+ : $options['ORDER BY'];
+ return ' ORDER BY ' . $ob;
+ }
+ return '';
+ }
+
+ /**
* Execute a SELECT query constructed using the various parameters provided.
* See below for full details of the parameters.
*
- * @param $table String|Array Table name
- * @param $vars String|Array Field names
- * @param $conds String|Array Conditions
- * @param $fname String Caller function name
- * @param $options Array Query options
+ * @param string|array $table Table name
+ * @param string|array $vars Field names
+ * @param string|array $conds Conditions
+ * @param string $fname Caller function name
+ * @param array $options Query options
* @param $join_conds Array Join conditions
*
* @param $table string|array
@@ -1325,7 +1420,7 @@ abstract class DatabaseBase implements DatabaseType {
* join, the second is an SQL fragment giving the join condition for that
* table. For example:
*
- * array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * array( 'page' => array( 'LEFT JOIN', 'page_latest=rev_id' ) )
*
* @return ResultWrapper. If the query returned no rows, a ResultWrapper
* with no rows in it will be returned. If there was a query error, a
@@ -1345,11 +1440,11 @@ abstract class DatabaseBase implements DatabaseType {
* doing UNION queries, where the SQL text of each query is needed. In general,
* however, callers outside of Database classes should just use select().
*
- * @param $table string|array Table name
- * @param $vars string|array Field names
- * @param $conds string|array Conditions
- * @param $fname string Caller function name
- * @param $options string|array Query options
+ * @param string|array $table Table name
+ * @param string|array $vars Field names
+ * @param string|array $conds Conditions
+ * @param string $fname Caller function name
+ * @param string|array $options Query options
* @param $join_conds string|array Join conditions
*
* @return string SQL query string.
@@ -1413,11 +1508,11 @@ abstract class DatabaseBase implements DatabaseType {
* that a single row object is returned. If the query returns no rows,
* false is returned.
*
- * @param $table string|array Table name
- * @param $vars string|array Field names
- * @param $conds array Conditions
- * @param $fname string Caller function name
- * @param $options string|array Query options
+ * @param string|array $table Table name
+ * @param string|array $vars Field names
+ * @param array $conds Conditions
+ * @param string $fname Caller function name
+ * @param string|array $options Query options
* @param $join_conds array|string Join conditions
*
* @return object|bool
@@ -1455,11 +1550,11 @@ abstract class DatabaseBase implements DatabaseType {
*
* Takes the same arguments as DatabaseBase::select().
*
- * @param $table String: table name
- * @param Array|string $vars : unused
- * @param Array|string $conds : filters on the table
- * @param $fname String: function name for profiling
- * @param $options Array: options for 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 = '',
@@ -1480,7 +1575,7 @@ abstract class DatabaseBase implements DatabaseType {
* 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.
*
- * @param $sql String A SQL Query
+ * @param string $sql A SQL Query
*
* @return string
*/
@@ -1507,9 +1602,9 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Determines whether a field exists in a table
*
- * @param $table String: table name
- * @param $field String: filed to check on that table
- * @param $fname String: calling function name (optional)
+ * @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
*/
public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
@@ -1530,6 +1625,10 @@ abstract class DatabaseBase implements DatabaseType {
* @return bool|null
*/
public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
+ if( !$this->tableExists( $table ) ) {
+ return null;
+ }
+
$info = $this->indexInfo( $table, $index, $fname );
if ( is_null( $info ) ) {
return null;
@@ -1626,7 +1725,7 @@ abstract class DatabaseBase implements DatabaseType {
* DatabaseBase::tableName().
* @param $a Array of rows to insert
* @param $fname String Calling function name (use __METHOD__) for logs/profiling
- * @param $options Array of options
+ * @param array $options of options
*
* @return bool
*/
@@ -1642,6 +1741,10 @@ abstract class DatabaseBase implements DatabaseType {
$options = array( $options );
}
+ $fh = null;
+ if ( isset( $options['fileHandle'] ) ) {
+ $fh = $options['fileHandle'];
+ }
$options = $this->makeInsertOptions( $options );
if ( isset( $a[0] ) && is_array( $a[0] ) ) {
@@ -1669,13 +1772,19 @@ abstract class DatabaseBase implements DatabaseType {
$sql .= '(' . $this->makeList( $a ) . ')';
}
+ if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
+ return false;
+ } elseif ( $fh !== null ) {
+ return true;
+ }
+
return (bool)$this->query( $sql, $fname );
}
/**
* Make UPDATE options for the DatabaseBase::update function
*
- * @param $options Array: The options passed to DatabaseBase::update
+ * @param array $options The options passed to DatabaseBase::update
* @return string
*/
protected function makeUpdateOptions( $options ) {
@@ -1702,7 +1811,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $table String name of the table to UPDATE. This will be passed through
* DatabaseBase::tableName().
*
- * @param $values Array: An array of values to SET. For each array element,
+ * @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().
@@ -1714,7 +1823,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fname String: The function name of the caller (from __METHOD__),
* for logging and profiling.
*
- * @param $options Array: An array of UPDATE options, can be:
+ * @param array $options An array of UPDATE options, can be:
* - IGNORE: Ignore unique key conflicts
* - LOW_PRIORITY: MySQL-specific, see MySQL manual.
* @return Boolean
@@ -1733,8 +1842,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Makes an encoded list of strings from an array
- * @param $a Array containing the data
- * @param $mode int Constant
+ * @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().
@@ -1742,6 +1851,7 @@ abstract class DatabaseBase implements DatabaseType {
* - LIST_SET: comma separated with field names, like a SET clause
* - LIST_NAMES: comma separated field names
*
+ * @throws MWException|DBUnexpectedError
* @return string
*/
public function makeList( $a, $mode = LIST_COMMA ) {
@@ -1771,7 +1881,7 @@ abstract class DatabaseBase implements DatabaseType {
$list .= "$value";
} elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
if ( count( $value ) == 0 ) {
- throw new MWException( __METHOD__ . ': empty input' );
+ 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
@@ -1803,10 +1913,10 @@ abstract class DatabaseBase implements 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 $data Array: organized as 2-d
+ * @param array $data organized as 2-d
* array(baseKeyVal => array(subKeyVal => [ignored], ...), ...)
- * @param $baseKey String: field name to match the base-level keys to (eg 'pl_namespace')
- * @param $subKey String: field name to match the sub-level keys to (eg 'pl_title')
+ * @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.
*/
public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
@@ -1868,7 +1978,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Build a concatenation list to feed into a SQL query
- * @param $stringList Array: list of raw SQL expressions; caller is responsible for any quoting
+ * @param array $stringList list of raw SQL expressions; caller is responsible for any quoting
* @return String
*/
public function buildConcat( $stringList ) {
@@ -1916,8 +2026,8 @@ abstract class DatabaseBase implements DatabaseType {
* themselves. Pass the canonical name to such functions. This is only needed
* when calling query() directly.
*
- * @param $name String: database table name
- * @param $format String One of:
+ * @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
@@ -1947,47 +2057,39 @@ abstract class DatabaseBase implements DatabaseType {
# Split database and table into proper variables.
# We reverse the explode so that database.table and table both output
# the correct table.
- $dbDetails = array_reverse( explode( '.', $name, 2 ) );
- if ( isset( $dbDetails[1] ) ) {
- list( $table, $database ) = $dbDetails;
+ $dbDetails = explode( '.', $name, 2 );
+ if ( count( $dbDetails ) == 2 ) {
+ list( $database, $table ) = $dbDetails;
+ # We don't want any prefix added in this case
+ $prefix = '';
} else {
list( $table ) = $dbDetails;
- }
- $prefix = $this->mTablePrefix; # Default prefix
-
- # A database name has been specified in input. We don't want any
- # prefixes added.
- if ( isset( $database ) ) {
- $prefix = '';
+ if ( $wgSharedDB !== null # We have a shared database
+ && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
+ && in_array( $table, $wgSharedTables ) # A shared table is selected
+ ) {
+ $database = $wgSharedDB;
+ $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
+ } else {
+ $database = null;
+ $prefix = $this->mTablePrefix; # Default prefix
+ }
}
- # Note that we use the long format because php will complain in in_array if
- # the input is not an array, and will complain in is_array if it is not set.
- if ( !isset( $database ) # Don't use shared database if pre selected.
- && isset( $wgSharedDB ) # We have a shared database
- && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
- && isset( $wgSharedTables )
- && is_array( $wgSharedTables )
- && in_array( $table, $wgSharedTables ) ) { # A shared table is selected
- $database = $wgSharedDB;
- $prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
+ # Quote $table and apply the prefix if not quoted.
+ $tableName = "{$prefix}{$table}";
+ if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) ) {
+ $tableName = $this->addIdentifierQuotes( $tableName );
}
- # Quote the $database and $table and apply the prefix if not quoted.
- if ( isset( $database ) ) {
+ # Quote $database and merge it with the table name if needed
+ if ( $database !== null ) {
if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
$database = $this->addIdentifierQuotes( $database );
}
+ $tableName = $database . '.' . $tableName;
}
- $table = "{$prefix}{$table}";
- if ( $format == 'quoted' && !$this->isQuotedIdentifier( $table ) ) {
- $table = $this->addIdentifierQuotes( "{$table}" );
- }
-
- # Merge our database and table into our final table name.
- $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
-
return $tableName;
}
@@ -1996,7 +2098,7 @@ abstract class DatabaseBase implements DatabaseType {
* This is handy when you need to construct SQL for joins
*
* Example:
- * extract($dbr->tableNames('user','watchlist'));
+ * extract( $dbr->tableNames( 'user', 'watchlist' ) );
* $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
* WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
*
@@ -2018,7 +2120,7 @@ abstract class DatabaseBase implements DatabaseType {
* This is handy when you need to construct SQL for joins
*
* Example:
- * list( $user, $watchlist ) = $dbr->tableNamesN('user','watchlist');
+ * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' );
* $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
* WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
*
@@ -2039,8 +2141,8 @@ abstract class DatabaseBase implements DatabaseType {
* Get an aliased table name
* e.g. tableName AS newTableName
*
- * @param $name string Table name, see tableName()
- * @param $alias string|bool Alias (optional)
+ * @param string $name Table name, see tableName()
+ * @param string|bool $alias Alias (optional)
* @return string SQL name for aliased table. Will not alias a table to its own name
*/
public function tableNameWithAlias( $name, $alias = false ) {
@@ -2072,8 +2174,8 @@ abstract class DatabaseBase implements DatabaseType {
* Get an aliased field name
* e.g. fieldName AS newFieldName
*
- * @param $name string Field name
- * @param $alias string|bool Alias (optional)
+ * @param string $name Field name
+ * @param string|bool $alias Alias (optional)
* @return string SQL name for aliased field. Will not alias a field to its own name
*/
public function fieldNameWithAlias( $name, $alias = false ) {
@@ -2105,7 +2207,7 @@ abstract class DatabaseBase implements DatabaseType {
* Get the aliased table name clause for a FROM clause
* which might have a JOIN and/or USE INDEX clause
*
- * @param $tables array ( [alias] => table )
+ * @param array $tables ( [alias] => table )
* @param $use_index array Same as for select()
* @param $join_conds array Same as for select()
* @return string
@@ -2336,12 +2438,12 @@ abstract class DatabaseBase implements DatabaseType {
* to collide. However if you do this, you run the risk of encountering
* errors which wouldn't have occurred in MySQL.
*
- * @param $table String: The table to replace the row(s) in.
- * @param $rows array Can be either a single row to insert, or multiple rows,
+ * @param string $table The table to replace the row(s) in.
+ * @param array $rows Can be either a single row to insert, or multiple rows,
* in the same format as for DatabaseBase::insert()
- * @param $uniqueIndexes array is an array of indexes. Each element may be either
+ * @param array $uniqueIndexes is an array of indexes. Each element may be either
* a field name or an array of field names
- * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
*/
public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
$quotedTable = $this->tableName( $table );
@@ -2394,9 +2496,9 @@ abstract class DatabaseBase implements DatabaseType {
* REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE
* statement.
*
- * @param $table string Table name
- * @param $rows array Rows to insert
- * @param $fname string Caller function name
+ * @param string $table Table name
+ * @param array $rows Rows to insert
+ * @param string $fname Caller function name
*
* @return ResultWrapper
*/
@@ -2443,6 +2545,7 @@ abstract class DatabaseBase implements DatabaseType {
* ANDed together in the WHERE clause
* @param $fname String: Calling function name (use __METHOD__) for
* logs/profiling
+ * @throws DBUnexpectedError
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
$fname = 'DatabaseBase::deleteJoin' )
@@ -2503,12 +2606,13 @@ abstract class DatabaseBase implements DatabaseType {
/**
* DELETE query wrapper.
*
- * @param $table Array Table name
- * @param $conds String|Array of conditions. See $conds in DatabaseBase::select() for
+ * @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 $fname String name of the calling function
+ * @param string $fname name of the calling function
*
- * @return bool
+ * @throws DBUnexpectedError
+ * @return bool|ResultWrapper
*/
public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
if ( !$conds ) {
@@ -2529,24 +2633,24 @@ abstract class DatabaseBase implements DatabaseType {
* INSERT SELECT wrapper. Takes data from a SELECT query and inserts it
* into another table.
*
- * @param $destTable string The table name to insert into
- * @param $srcTable string|array May be either a table name, or an array of table names
+ * @param string $destTable The table name to insert into
+ * @param string|array $srcTable May be either a table name, or an array of table names
* to include in a join.
*
- * @param $varMap array 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()
*
- * @param $conds array Condition array. See $conds in DatabaseBase::select() for
+ * @param array $conds Condition array. See $conds in DatabaseBase::select() for
* the details of the format of condition arrays. May be "*" to copy the
* whole table.
*
- * @param $fname string The function name of the caller, from __METHOD__
+ * @param string $fname The function name of the caller, from __METHOD__
*
- * @param $insertOptions array Options for the INSERT part of the query, see
+ * @param array $insertOptions Options for the INSERT part of the query, see
* DatabaseBase::insert() for details.
- * @param $selectOptions array Options for the SELECT part of the query, see
+ * @param array $selectOptions Options for the SELECT part of the query, see
* DatabaseBase::select() for details.
*
* @return ResultWrapper
@@ -2568,7 +2672,7 @@ abstract class DatabaseBase implements DatabaseType {
list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
if ( is_array( $srcTable ) ) {
- $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
+ $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
} else {
$srcTable = $this->tableName( $srcTable );
}
@@ -2602,10 +2706,11 @@ abstract class DatabaseBase implements DatabaseType {
* The version provided by default works in MySQL and SQLite. It will very
* likely need to be overridden for most other DBMSes.
*
- * @param $sql String SQL query we will append the limit too
+ * @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)
*
+ * @throws DBUnexpectedError
* @return string
*/
public function limitResult( $sql, $limit, $offset = false ) {
@@ -2630,7 +2735,7 @@ abstract class DatabaseBase implements DatabaseType {
* Construct a UNION query
* This is used for providing overload point for other DB abstractions
* not compatible with the MySQL syntax.
- * @param $sqls Array: SQL statements to combine
+ * @param array $sqls SQL statements to combine
* @param $all Boolean: use UNION ALL
* @return String: SQL fragment
*/
@@ -2643,9 +2748,9 @@ abstract class DatabaseBase implements DatabaseType {
* Returns an SQL expression for a simple conditional. This doesn't need
* to be overridden unless CASE isn't supported in your DBMS.
*
- * @param $cond string|array SQL expression which will result in a boolean value
- * @param $trueVal String: SQL expression to return if true
- * @param $falseVal String: SQL expression to return if false
+ * @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
*/
public function conditional( $cond, $trueVal, $falseVal ) {
@@ -2659,9 +2764,9 @@ abstract class DatabaseBase implements DatabaseType {
* Returns a comand for str_replace function in SQL query.
* Uses REPLACE() in MySQL
*
- * @param $orig String: column to modify
- * @param $old String: column to seek
- * @param $new String: 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
*/
@@ -2854,8 +2959,9 @@ abstract class DatabaseBase implements DatabaseType {
*
* This is useful for updates to different systems or separate transactions are needed.
*
+ * @since 1.20
+ *
* @param Closure $callback
- * @return void
*/
final public function onTransactionIdle( Closure $callback ) {
if ( $this->mTrxLevel ) {
@@ -2866,7 +2972,9 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Actually run the "on transaction idle" callbacks
+ * Actually run the "on transaction idle" callbacks.
+ *
+ * @since 1.20
*/
protected function runOnTransactionIdleCallbacks() {
$autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
@@ -2890,19 +2998,50 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Begin a transaction
+ * 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 $fname string
*/
final public function begin( $fname = 'DatabaseBase::begin' ) {
+ global $wgDebugDBTransactions;
+
if ( $this->mTrxLevel ) { // implicit commit
+ if ( !$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}), " .
+ " performing implicit commit!";
+ wfWarn( $msg );
+ wfLogDBError( $msg );
+ } else {
+ // if the transaction was automatic and has done write operations,
+ // log it if $wgDebugDBTransactions is enabled.
+ if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
+ wfDebug( "$fname: Automatic transaction with writes in progress" .
+ " (from {$this->mTrxFname}), performing implicit commit!\n" );
+ }
+ }
+
$this->doCommit( $fname );
$this->runOnTransactionIdleCallbacks();
}
+
$this->doBegin( $fname );
+ $this->mTrxFname = $fname;
+ $this->mTrxDoneWrites = false;
+ $this->mTrxAutomatic = false;
}
/**
+ * Issues the BEGIN command to the database server.
+ *
* @see DatabaseBase::begin()
* @param type $fname
*/
@@ -2912,16 +3051,39 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * End a transaction
+ * Commits a transaction previously started using begin().
+ * If no transaction is in progress, a warning is issued.
+ *
+ * Nesting of transactions is not supported.
*
* @param $fname string
- */
- final public function commit( $fname = 'DatabaseBase::commit' ) {
+ * @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.
+ */
+ final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) {
+ 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!" );
+ }
+ } else {
+ if ( !$this->mTrxLevel ) {
+ return; // nothing to do
+ } elseif( !$this->mTrxAutomatic ) {
+ wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
+ }
+ }
+
$this->doCommit( $fname );
$this->runOnTransactionIdleCallbacks();
}
/**
+ * Issues the COMMIT command to the database server.
+ *
* @see DatabaseBase::commit()
* @param type $fname
*/
@@ -2933,17 +3095,24 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Rollback a transaction.
+ * Rollback a transaction previously started using begin().
+ * If no transaction is in progress, a warning is issued.
+ *
* No-op on non-transactional databases.
*
* @param $fname string
*/
final public function rollback( $fname = 'DatabaseBase::rollback' ) {
+ if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
+ }
$this->doRollback( $fname );
$this->mTrxIdleCallbacks = array(); // cancel
}
/**
+ * Issues the ROLLBACK command to the database server.
+ *
* @see DatabaseBase::rollback()
* @param type $fname
*/
@@ -2962,10 +3131,11 @@ abstract class DatabaseBase implements DatabaseType {
* The table names passed to this function shall not be quoted (this
* function calls addIdentifierQuotes when needed).
*
- * @param $oldName String: name of table whose structure should be copied
- * @param $newName String: name of table to be created
+ * @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 $fname String: calling function name
+ * @param string $fname calling function name
+ * @throws MWException
* @return Boolean: true if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
@@ -2978,8 +3148,9 @@ abstract class DatabaseBase implements DatabaseType {
/**
* List all tables on the database
*
- * @param $prefix string Only show tables with this prefix, e.g. mw_
- * @param $fname String: calling function name
+ * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string $fname calling function name
+ * @throws MWException
*/
function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
@@ -3122,15 +3293,18 @@ abstract class DatabaseBase implements DatabaseType {
* Returns true on success, error string or exception on failure (depending
* on object's error ignore settings).
*
- * @param $filename String: File name to open
- * @param $lineCallback Callback: Optional function called before reading each line
- * @param $resultCallback Callback: Optional function called for each MySQL result
- * @param $fname String: Calling function name or false if name should be
+ * @param string $filename File name to open
+ * @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
+ * @throws Exception|MWException
* @return bool|string
*/
public function sourceFile(
- $filename, $lineCallback = false, $resultCallback = false, $fname = false
+ $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
) {
wfSuppressWarnings();
$fp = fopen( $filename, 'r' );
@@ -3145,7 +3319,7 @@ abstract class DatabaseBase implements DatabaseType {
}
try {
- $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname );
+ $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
}
catch ( MWException $e ) {
fclose( $fp );
@@ -3162,7 +3336,7 @@ abstract class DatabaseBase implements DatabaseType {
* from updaters.inc. Keep in mind this always returns a patch, as
* it fails back to MySQL if no DB-specific patch can be found
*
- * @param $patch String The name of the patch, like patch-something.sql
+ * @param string $patch The name of the patch, like patch-something.sql
* @return String Full path to patch file
*/
public function patchPath( $patch ) {
@@ -3181,7 +3355,7 @@ abstract class DatabaseBase implements 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 $vars bool|array mapping variable name to value.
+ * @param bool|array $vars mapping variable name to value.
*/
public function setSchemaVars( $vars ) {
$this->mSchemaVars = $vars;
@@ -3194,10 +3368,10 @@ abstract class DatabaseBase implements DatabaseType {
* on object's error ignore settings).
*
* @param $fp Resource: File handle
- * @param $lineCallback Callback: Optional function called before reading each line
+ * @param $lineCallback Callback: Optional function called before reading each query
* @param $resultCallback Callback: Optional function called for each MySQL result
- * @param $fname String: Calling function name
- * @param $inputCallback Callback: Optional function called for each complete line (ended with ;) sent
+ * @param string $fname Calling function name
+ * @param $inputCallback Callback: Optional function called for each complete query sent
* @return bool|string
*/
public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
@@ -3230,20 +3404,19 @@ abstract class DatabaseBase implements DatabaseType {
if ( $done || feof( $fp ) ) {
$cmd = $this->replaceVars( $cmd );
- if ( $inputCallback ) {
- call_user_func( $inputCallback, $cmd );
- }
- $res = $this->query( $cmd, $fname );
- if ( $resultCallback ) {
- call_user_func( $resultCallback, $res, $this );
- }
+ if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
+ $res = $this->query( $cmd, $fname );
- if ( false === $res ) {
- $err = $this->lastError();
- return "Query \"{$cmd}\" failed with error code \"$err\".\n";
- }
+ if ( $resultCallback ) {
+ call_user_func( $resultCallback, $res, $this );
+ }
+ if ( false === $res ) {
+ $err = $this->lastError();
+ return "Query \"{$cmd}\" failed with error code \"$err\".\n";
+ }
+ }
$cmd = '';
}
}
@@ -3254,8 +3427,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Called by sourceStream() to check if we've reached a statement end
*
- * @param $sql String SQL assembled so far
- * @param $newLine String New line about to be added to $sql
+ * @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
*/
public function streamStatementEnd( &$sql, &$newLine ) {
@@ -3283,7 +3456,7 @@ abstract class DatabaseBase implements DatabaseType {
* - / *$var* / is just encoded, besides traditional table prefix and
* table options its use should be avoided.
*
- * @param $ins String: SQL statement to replace variables in
+ * @param string $ins SQL statement to replace variables in
* @return String The new SQL statement with variables replaced
*/
protected function replaceSchemaVars( $ins ) {
@@ -3294,7 +3467,7 @@ abstract class DatabaseBase implements DatabaseType {
// replace `{$var}`
$ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
// replace /*$var*/
- $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ) , $ins );
+ $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
}
return $ins;
}
@@ -3371,8 +3544,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Check to see if a named lock is available. This is non-blocking.
*
- * @param $lockName String: name of lock to poll
- * @param $method String: name of method calling us
+ * @param string $lockName name of lock to poll
+ * @param string $method name of method calling us
* @return Boolean
* @since 1.20
*/
@@ -3386,8 +3559,8 @@ abstract class DatabaseBase implements DatabaseType {
* Abstracted from Filestore::lock() so child classes can implement for
* their own needs.
*
- * @param $lockName String: name of lock to aquire
- * @param $method String: name of method calling us
+ * @param string $lockName name of lock to aquire
+ * @param string $method name of method calling us
* @param $timeout Integer: timeout
* @return Boolean
*/
@@ -3398,8 +3571,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Release a lock.
*
- * @param $lockName String: Name of lock to release
- * @param $method String: Name of method calling us
+ * @param string $lockName Name of lock to release
+ * @param string $method Name of method calling us
*
* @return int Returns 1 if the lock was released, 0 if the lock was not established
* by this thread (in which case the lock is not released), and NULL if the named
@@ -3412,10 +3585,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Lock specific tables
*
- * @param $read Array of tables to lock for read access
- * @param $write Array of tables to lock for write access
- * @param $method String name of caller
- * @param $lowPriority bool Whether to indicate writes to be LOW PRIORITY
+ * @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 bool $lowPriority Whether to indicate writes to be LOW PRIORITY
*
* @return bool
*/
@@ -3426,7 +3599,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Unlock specific tables
*
- * @param $method String the caller
+ * @param string $method the caller
*
* @return bool
*/
@@ -3476,7 +3649,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Encode an expiry time into the DBMS dependent format
*
- * @param $expiry String: timestamp for expiry, or the 'infinity' string
+ * @param string $expiry timestamp for expiry, or the 'infinity' string
* @return String
*/
public function encodeExpiry( $expiry ) {
@@ -3488,7 +3661,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Decode an expiry time into a DBMS independent format
*
- * @param $expiry String: DB timestamp field value for expiry
+ * @param string $expiry DB timestamp field value for expiry
* @param $format integer: TS_* constant, defaults to TS_MW
* @return String
*/
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
index a53a6747..628a2afc 100644
--- a/includes/db/DatabaseError.php
+++ b/includes/db/DatabaseError.php
@@ -35,9 +35,9 @@ class DBError extends MWException {
/**
* Construct a database error
* @param $db DatabaseBase object which threw the error
- * @param $error String A simple error message to be used for debugging
+ * @param string $error A simple error message to be used for debugging
*/
- function __construct( DatabaseBase &$db, $error ) {
+ function __construct( DatabaseBase $db = null, $error ) {
$this->db = $db;
parent::__construct( $error );
}
@@ -91,7 +91,7 @@ class DBError extends MWException {
class DBConnectionError extends DBError {
public $error;
- function __construct( DatabaseBase &$db, $error = 'unknown error' ) {
+ function __construct( DatabaseBase $db = null, $error = 'unknown error' ) {
$msg = 'DB connection error';
if ( trim( $error ) != '' ) {
@@ -153,12 +153,12 @@ class DBConnectionError extends DBError {
$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.' ) );
- $info = htmlspecialchars( $this->msg( 'dberr-info', '(Can\'t contact the database server: $1)' ) );
+ $info = htmlspecialchars( $this->msg( 'dberr-info', '(Can\'t contact the database server: $1)' ) );
# No database access
MessageCache::singleton()->disable();
- if ( trim( $this->error ) == '' ) {
+ if ( trim( $this->error ) == '' && $this->db ) {
$this->error = $this->db->getProperty( 'mServer' );
}
@@ -176,7 +176,7 @@ class DBConnectionError extends DBError {
return "$text<hr />$extra";
}
- public function reportHTML(){
+ public function reportHTML() {
global $wgUseFileCache;
# Check whether we can serve a file-cached copy of the page with the error underneath
@@ -288,11 +288,11 @@ class DBQueryError extends DBError {
* @param $sql string
* @param $fname string
*/
- 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" .
- "Query: $sql\n" .
- "Function: $fname\n" .
- "Error: $errno $error\n";
+ 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" .
+ "Query: $sql\n" .
+ "Function: $fname\n" .
+ "Error: $errno $error\n";
parent::__construct( $db, $message );
$this->error = $error;
diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php
deleted file mode 100644
index f1f6dfca..00000000
--- a/includes/db/DatabaseIbm_db2.php
+++ /dev/null
@@ -1,1721 +0,0 @@
-<?php
-/**
- * This is the IBM DB2 database abstraction layer.
- * See maintenance/ibm_db2/README for development notes
- * and other specific information.
- *
- * 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
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- * @author leo.petr+mediawiki@gmail.com
- */
-
-/**
- * This represents a column in a DB2 database
- * @ingroup Database
- */
-class IBM_DB2Field implements Field {
- private $name = '';
- private $tablename = '';
- private $type = '';
- private $nullable = false;
- private $max_length = 0;
-
- /**
- * Builder method for the class
- * @param $db DatabaseIbm_db2: Database interface
- * @param $table String: table name
- * @param $field String: column name
- * @return IBM_DB2Field
- */
- static function fromText( $db, $table, $field ) {
- global $wgDBmwschema;
-
- $q = <<<SQL
-SELECT
-lcase( coltype ) AS typname,
-nulls AS attnotnull, length AS attlen
-FROM sysibm.syscolumns
-WHERE tbcreator=%s AND tbname=%s AND name=%s;
-SQL;
- $res = $db->query(
- sprintf( $q,
- $db->addQuotes( $wgDBmwschema ),
- $db->addQuotes( $table ),
- $db->addQuotes( $field )
- )
- );
- $row = $db->fetchObject( $res );
- if ( !$row ) {
- return null;
- }
- $n = new IBM_DB2Field;
- $n->type = $row->typname;
- $n->nullable = ( $row->attnotnull == 'N' );
- $n->name = $field;
- $n->tablename = $table;
- $n->max_length = $row->attlen;
- return $n;
- }
- /**
- * Get column name
- * @return string column name
- */
- function name() { return $this->name; }
- /**
- * Get table name
- * @return string table name
- */
- function tableName() { return $this->tablename; }
- /**
- * Get column type
- * @return string column type
- */
- function type() { return $this->type; }
- /**
- * Can column be null?
- * @return bool true or false
- */
- function isNullable() { return $this->nullable; }
- /**
- * How much can you fit in the column per row?
- * @return int length
- */
- function maxLength() { return $this->max_length; }
-}
-
-/**
- * Wrapper around binary large objects
- * @ingroup Database
- */
-class IBM_DB2Blob {
- private $mData;
-
- public function __construct( $data ) {
- $this->mData = $data;
- }
-
- public function getData() {
- return $this->mData;
- }
-
- public function __toString() {
- return $this->mData;
- }
-}
-
-/**
- * Wrapper to address lack of certain operations in the DB2 driver
- * ( seek, num_rows )
- * @ingroup Database
- * @since 1.19
- */
-class IBM_DB2Result{
- private $db;
- private $result;
- private $num_rows;
- private $current_pos;
- private $columns = array();
- private $sql;
-
- private $resultSet = array();
- private $loadedLines = 0;
-
- /**
- * Construct and initialize a wrapper for DB2 query results
- * @param $db DatabaseBase
- * @param $result Object
- * @param $num_rows Integer
- * @param $sql String
- * @param $columns Array
- */
- public function __construct( $db, $result, $num_rows, $sql, $columns ){
- $this->db = $db;
-
- if( $result instanceof ResultWrapper ){
- $this->result = $result->result;
- }
- else{
- $this->result = $result;
- }
-
- $this->num_rows = $num_rows;
- $this->current_pos = 0;
- if ( $this->num_rows > 0 ) {
- // Make a lower-case list of the column names
- // By default, DB2 column names are capitalized
- // while MySQL column names are lowercase
-
- // Is there a reasonable maximum value for $i?
- // Setting to 2048 to prevent an infinite loop
- for( $i = 0; $i < 2048; $i++ ) {
- $name = db2_field_name( $this->result, $i );
- if ( $name != false ) {
- continue;
- }
- else {
- return false;
- }
-
- $this->columns[$i] = strtolower( $name );
- }
- }
-
- $this->sql = $sql;
- }
-
- /**
- * Unwrap the DB2 query results
- * @return mixed Object on success, false on failure
- */
- public function getResult() {
- if ( $this->result ) {
- return $this->result;
- }
- else return false;
- }
-
- /**
- * Get the number of rows in the result set
- * @return integer
- */
- public function getNum_rows() {
- return $this->num_rows;
- }
-
- /**
- * Return a row from the result set in object format
- * @return mixed Object on success, false on failure.
- */
- public function fetchObject() {
- if ( $this->result
- && $this->num_rows > 0
- && $this->current_pos >= 0
- && $this->current_pos < $this->num_rows )
- {
- $row = $this->fetchRow();
- $ret = new stdClass();
-
- foreach ( $row as $k => $v ) {
- $lc = $this->columns[$k];
- $ret->$lc = $v;
- }
- return $ret;
- }
- return false;
- }
-
- /**
- * Return a row form the result set in array format
- * @return mixed Array on success, false on failure
- * @throws DBUnexpectedError
- */
- public function fetchRow(){
- if ( $this->result
- && $this->num_rows > 0
- && $this->current_pos >= 0
- && $this->current_pos < $this->num_rows )
- {
- if ( $this->loadedLines <= $this->current_pos ) {
- $row = db2_fetch_array( $this->result );
- $this->resultSet[$this->loadedLines++] = $row;
- if ( $this->db->lastErrno() ) {
- throw new DBUnexpectedError( $this->db, 'Error in fetchRow(): '
- . htmlspecialchars( $this->db->lastError() ) );
- }
- }
-
- if ( $this->loadedLines > $this->current_pos ){
- return $this->resultSet[$this->current_pos++];
- }
-
- }
- return false;
- }
-
- /**
- * Free a DB2 result object
- * @throws DBUnexpectedError
- */
- public function freeResult(){
- unset( $this->resultSet );
- if ( !@db2_free_result( $this->result ) ) {
- throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
- }
- }
-}
-
-/**
- * Primary database interface
- * @ingroup Database
- */
-class DatabaseIbm_db2 extends DatabaseBase {
- /*
- * Inherited members
- protected $mLastQuery = '';
- protected $mPHPError = false;
-
- protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
- protected $mOpened = false;
-
- protected $mTablePrefix;
- protected $mFlags;
- protected $mTrxLevel = 0;
- protected $mErrorCount = 0;
- protected $mLBInfo = array();
- protected $mFakeSlaveLag = null, $mFakeMaster = false;
- *
- */
-
- /** Database server port */
- protected $mPort = null;
- /** Schema for tables, stored procedures, triggers */
- protected $mSchema = null;
- /** Whether the schema has been applied in this session */
- protected $mSchemaSet = false;
- /** Result of last query */
- protected $mLastResult = null;
- /** Number of rows affected by last INSERT/UPDATE/DELETE */
- protected $mAffectedRows = null;
- /** Number of rows returned by last SELECT */
- protected $mNumRows = null;
- /** Current row number on the cursor of the last SELECT */
- protected $currentRow = 0;
-
- /** Connection config options - see constructor */
- public $mConnOptions = array();
- /** Statement config options -- see constructor */
- public $mStmtOptions = array();
-
- /** Default schema */
- const USE_GLOBAL = 'get from global';
-
- /** Option that applies to nothing */
- const NONE_OPTION = 0x00;
- /** Option that applies to connection objects */
- const CONN_OPTION = 0x01;
- /** Option that applies to statement objects */
- const STMT_OPTION = 0x02;
-
- /** Regular operation mode -- minimal debug messages */
- const REGULAR_MODE = 'regular';
- /** Installation mode -- lots of debug messages */
- const INSTALL_MODE = 'install';
-
- /** Controls the level of debug message output */
- protected $mMode = self::REGULAR_MODE;
-
- /** Last sequence value used for a primary key */
- protected $mInsertId = null;
-
- ######################################
- # Getters and Setters
- ######################################
-
- /**
- * Returns true if this database supports (and uses) cascading deletes
- * @return bool
- */
- function cascadingDeletes() {
- return true;
- }
-
- /**
- * Returns true if this database supports (and uses) triggers (e.g. on the
- * page table)
- * @return bool
- */
- function cleanupTriggers() {
- return true;
- }
-
- /**
- * Returns true if this database is strict about what can be put into an
- * IP field.
- * Specifically, it uses a NULL value instead of an empty string.
- * @return bool
- */
- function strictIPs() {
- return true;
- }
-
- /**
- * Returns true if this database uses timestamps rather than integers
- * @return bool
- */
- function realTimestamps() {
- return true;
- }
-
- /**
- * Returns true if this database does an implicit sort when doing GROUP BY
- * @return bool
- */
- function implicitGroupby() {
- return false;
- }
-
- /**
- * Returns true if this database does an implicit order by when the column
- * has an index
- * For example: SELECT page_title FROM page LIMIT 1
- * @return bool
- */
- function implicitOrderby() {
- return false;
- }
-
- /**
- * Returns true if this database can do a native search on IP columns
- * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
- * @return bool
- */
- function searchableIPs() {
- return true;
- }
-
- /**
- * Returns true if this database can use functional indexes
- * @return bool
- */
- function functionalIndexes() {
- return true;
- }
-
- /**
- * Returns a unique string representing the wiki on the server
- * @return string
- */
- public function getWikiID() {
- if( $this->mSchema ) {
- return "{$this->mDBname}-{$this->mSchema}";
- } else {
- return $this->mDBname;
- }
- }
-
- /**
- * Returns the database software identifieir
- * @return string
- */
- public function getType() {
- return 'ibm_db2';
- }
-
- /**
- * Returns the database connection object
- * @return Object
- */
- public function getDb(){
- return $this->mConn;
- }
-
- /**
- *
- * @param $server String: hostname of database server
- * @param $user String: username
- * @param $password String: password
- * @param $dbName String: database name on the server
- * @param $flags Integer: database behaviour flags (optional, unused)
- * @param $schema String
- */
- public function __construct( $server = false, $user = false,
- $password = false,
- $dbName = false, $flags = 0,
- $schema = self::USE_GLOBAL )
- {
- global $wgDBmwschema;
-
- if ( $schema == self::USE_GLOBAL ) {
- $this->mSchema = $wgDBmwschema;
- } else {
- $this->mSchema = $schema;
- }
-
- // configure the connection and statement objects
- $this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER',
- self::CONN_OPTION | self::STMT_OPTION );
- $this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON',
- self::STMT_OPTION );
- $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON',
- self::STMT_OPTION );
- parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags );
- }
-
- /**
- * Enables options only if the ibm_db2 extension version supports them
- * @param $name String: name of the option in the options array
- * @param $const String: name of the constant holding the right option value
- * @param $type Integer: whether this is a Connection or Statement otion
- */
- private function setDB2Option( $name, $const, $type ) {
- if ( defined( $const ) ) {
- if ( $type & self::CONN_OPTION ) {
- $this->mConnOptions[$name] = constant( $const );
- }
- if ( $type & self::STMT_OPTION ) {
- $this->mStmtOptions[$name] = constant( $const );
- }
- } else {
- $this->installPrint(
- "$const is not defined. ibm_db2 version is likely too low." );
- }
- }
-
- /**
- * Outputs debug information in the appropriate place
- * @param $string String: the relevant debug message
- */
- private function installPrint( $string ) {
- wfDebug( "$string\n" );
- if ( $this->mMode == self::INSTALL_MODE ) {
- print "<li><pre>$string</pre></li>";
- flush();
- }
- }
-
- /**
- * Opens a database connection and returns it
- * Closes any existing connection
- *
- * @param $server String: hostname
- * @param $user String
- * @param $password String
- * @param $dbName String: database name
- * @return DatabaseBase a fresh connection
- */
- public function open( $server, $user, $password, $dbName ) {
- wfProfileIn( __METHOD__ );
-
- # Load IBM DB2 driver if missing
- wfDl( 'ibm_db2' );
-
- # Test for IBM DB2 support, to avoid suppressed fatal error
- if ( !function_exists( 'db2_connect' ) ) {
- throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" );
- }
-
- global $wgDBport;
-
- // Close existing connection
- $this->close();
- // Cache conn info
- $this->mServer = $server;
- $this->mPort = $port = $wgDBport;
- $this->mUser = $user;
- $this->mPassword = $password;
- $this->mDBname = $dbName;
-
- $this->openUncataloged( $dbName, $user, $password, $server, $port );
-
- if ( !$this->mConn ) {
- $this->installPrint( "DB connection error\n" );
- $this->installPrint(
- "Server: $server, Database: $dbName, User: $user, Password: "
- . substr( $password, 0, 3 ) . "...\n" );
- $this->installPrint( $this->lastError() . "\n" );
- wfProfileOut( __METHOD__ );
- wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- wfDebug( $this->lastError() . "\n" );
- throw new DBConnectionError( $this, $this->lastError() );
- }
-
- // Some MediaWiki code is still transaction-less (?).
- // The strategy is to keep AutoCommit on for that code
- // but switch it off whenever a transaction is begun.
- db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
-
- $this->mOpened = true;
- $this->applySchema();
-
- wfProfileOut( __METHOD__ );
- return $this->mConn;
- }
-
- /**
- * Opens a cataloged database connection, sets mConn
- */
- protected function openCataloged( $dbName, $user, $password ) {
- wfSuppressWarnings();
- $this->mConn = db2_pconnect( $dbName, $user, $password );
- wfRestoreWarnings();
- }
-
- /**
- * Opens an uncataloged database connection, sets mConn
- */
- protected function openUncataloged( $dbName, $user, $password, $server, $port )
- {
- $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";
- wfSuppressWarnings();
- $this->mConn = db2_pconnect( $dsn, "", "", array() );
- wfRestoreWarnings();
- }
-
- /**
- * Closes a database connection, if it is open
- * Returns success, true if already closed
- * @return bool
- */
- protected function closeConnection() {
- return db2_close( $this->mConn );
- }
-
- /**
- * Retrieves the most current database error
- * Forces a database rollback
- * @return bool|string
- */
- public function lastError() {
- $connerr = db2_conn_errormsg();
- if ( $connerr ) {
- //$this->rollback( __METHOD__ );
- return $connerr;
- }
- $stmterr = db2_stmt_errormsg();
- if ( $stmterr ) {
- //$this->rollback( __METHOD__ );
- return $stmterr;
- }
-
- return false;
- }
-
- /**
- * Get the last error number
- * Return 0 if no error
- * @return integer
- */
- public function lastErrno() {
- $connerr = db2_conn_error();
- if ( $connerr ) {
- return $connerr;
- }
- $stmterr = db2_stmt_error();
- if ( $stmterr ) {
- return $stmterr;
- }
- return 0;
- }
-
- /**
- * Is a database connection open?
- * @return
- */
- public function isOpen() { return $this->mOpened; }
-
- /**
- * The DBMS-dependent part of query()
- * @param $sql String: SQL query.
- * @return object Result object for fetch functions or false on failure
- */
- protected function doQuery( $sql ) {
- $this->applySchema();
-
- // Needed to handle any UTF-8 encoding issues in the raw sql
- // Note that we fully support prepared statements for DB2
- // prepare() and execute() should be used instead of doQuery() whenever possible
- $sql = utf8_decode( $sql );
-
- $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
- if( $ret == false ) {
- $error = db2_stmt_errormsg();
-
- $this->installPrint( "<pre>$sql</pre>" );
- $this->installPrint( $error );
- throw new DBUnexpectedError( $this, 'SQL error: '
- . htmlspecialchars( $error ) );
- }
- $this->mLastResult = $ret;
- $this->mAffectedRows = null; // Not calculated until asked for
- return $ret;
- }
-
- /**
- * @return string Version information from the database
- */
- public function getServerVersion() {
- $info = db2_server_info( $this->mConn );
- return $info->DBMS_VER;
- }
-
- /**
- * Queries whether a given table exists
- * @return boolean
- */
- public function tableExists( $table, $fname = __METHOD__ ) {
- $schema = $this->mSchema;
-
- $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" .
- strtoupper( $table ) .
- "' AND ST.CREATOR = '" .
- strtoupper( $schema ) . "'";
- $res = $this->query( $sql );
- if ( !$res ) {
- return false;
- }
-
- // If the table exists, there should be one of it
- $row = $this->fetchRow( $res );
- $count = $row[0];
- if ( $count == '1' || $count == 1 ) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Fetch the next row from the given result object, in object form.
- * Fields can be retrieved with $row->fieldname, with fields acting like
- * member variables.
- *
- * @param $res array|ResultWrapper SQL result object as returned from Database::query(), etc.
- * @return DB2 row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- public function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- wfSuppressWarnings();
- $row = db2_fetch_object( $res );
- wfRestoreWarnings();
- if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): '
- . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
-
- /**
- * Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
- *
- * @param $res array|ResultWrapper SQL result object as returned from Database::query(), etc.
- * @return ResultWrapper row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- public function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( db2_num_rows( $res ) > 0) {
- wfSuppressWarnings();
- $row = db2_fetch_array( $res );
- wfRestoreWarnings();
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
- . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
- return false;
- }
-
- /**
- * Escapes strings
- * Doesn't escape numbers
- *
- * @param $s String: string to escape
- * @return string escaped string
- */
- public function addQuotes( $s ) {
- //$this->installPrint( "DB2::addQuotes( $s )\n" );
- if ( is_null( $s ) ) {
- return 'NULL';
- } elseif ( $s instanceof Blob ) {
- return "'" . $s->fetch( $s ) . "'";
- } elseif ( $s instanceof IBM_DB2Blob ) {
- return "'" . $this->decodeBlob( $s ) . "'";
- }
- $s = $this->strencode( $s );
- if ( is_numeric( $s ) ) {
- return $s;
- } else {
- return "'$s'";
- }
- }
-
- /**
- * Verifies that a DB2 column/field type is numeric
- *
- * @param $type String: DB2 column type
- * @return Boolean: true if numeric
- */
- public function is_numeric_type( $type ) {
- switch ( strtoupper( $type ) ) {
- case 'SMALLINT':
- case 'INTEGER':
- case 'INT':
- case 'BIGINT':
- case 'DECIMAL':
- case 'REAL':
- case 'DOUBLE':
- case 'DECFLOAT':
- return true;
- }
- return false;
- }
-
- /**
- * Alias for addQuotes()
- * @param $s String: string to escape
- * @return string escaped string
- */
- public function strencode( $s ) {
- // Bloody useless function
- // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
- // But also necessary
- $s = db2_escape_string( $s );
- // Wide characters are evil -- some of them look like '
- $s = utf8_encode( $s );
- // Fix its stupidity
- $from = array( "\\\\", "\\'", '\\n', '\\t', '\\"', '\\r' );
- $to = array( "\\", "''", "\n", "\t", '"', "\r" );
- $s = str_replace( $from, $to, $s ); // DB2 expects '', not \' escaping
- return $s;
- }
-
- /**
- * Switch into the database schema
- */
- protected function applySchema() {
- if ( !( $this->mSchemaSet ) ) {
- $this->mSchemaSet = true;
- $this->begin( __METHOD__ );
- $this->doQuery( "SET SCHEMA = $this->mSchema" );
- $this->commit( __METHOD__ );
- }
- }
-
- /**
- * Start a transaction (mandatory)
- */
- protected function doBegin( $fname = 'DatabaseIbm_db2::begin' ) {
- // BEGIN is implicit for DB2
- // However, it requires that AutoCommit be off.
-
- // Some MediaWiki code is still transaction-less (?).
- // The strategy is to keep AutoCommit on for that code
- // but switch it off whenever a transaction is begun.
- db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_OFF );
-
- $this->mTrxLevel = 1;
- }
-
- /**
- * End a transaction
- * Must have a preceding begin()
- */
- protected function doCommit( $fname = 'DatabaseIbm_db2::commit' ) {
- db2_commit( $this->mConn );
-
- // Some MediaWiki code is still transaction-less (?).
- // The strategy is to keep AutoCommit on for that code
- // but switch it off whenever a transaction is begun.
- db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
-
- $this->mTrxLevel = 0;
- }
-
- /**
- * Cancel a transaction
- */
- protected function doRollback( $fname = 'DatabaseIbm_db2::rollback' ) {
- db2_rollback( $this->mConn );
- // turn auto-commit back on
- // not sure if this is appropriate
- db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
- $this->mTrxLevel = 0;
- }
-
- /**
- * Makes an encoded list of strings from an array
- * $mode:
- * LIST_COMMA - comma separated, no field names
- * LIST_AND - ANDed WHERE clause (without the WHERE)
- * 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
- * LIST_SET_PREPARED - like LIST_SET, except with ? tokens as values
- * @return string
- */
- function makeList( $a, $mode = LIST_COMMA ) {
- if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this,
- 'DatabaseIbm_db2::makeList called with incorrect parameters' );
- }
-
- // if this is for a prepared UPDATE statement
- // (this should be promoted to the parent class
- // once other databases use prepared statements)
- if ( $mode == LIST_SET_PREPARED ) {
- $first = true;
- $list = '';
- foreach ( $a as $field => $value ) {
- if ( !$first ) {
- $list .= ", $field = ?";
- } else {
- $list .= "$field = ?";
- $first = false;
- }
- }
- $list .= '';
-
- return $list;
- }
-
- // otherwise, call the usual function
- return parent::makeList( $a, $mode );
- }
-
- /**
- * Construct a LIMIT query with optional offset
- * This is used for query pages
- *
- * @param $sql string SQL query we will append the limit too
- * @param $limit integer the SQL limit
- * @param $offset integer the SQL offset (default false)
- * @return string
- */
- public function limitResult( $sql, $limit, $offset=false ) {
- if( !is_numeric( $limit ) ) {
- throw new DBUnexpectedError( $this,
- "Invalid non-numeric limit passed to limitResult()\n" );
- }
- if( $offset ) {
- if ( stripos( $sql, 'where' ) === false ) {
- return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )";
- } else {
- return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )";
- }
- }
- return "$sql FETCH FIRST $limit ROWS ONLY ";
- }
-
- /**
- * Handle reserved keyword replacement in table names
- *
- * @param $name Object
- * @param $format String Ignored parameter Default 'quoted'Boolean
- * @return String
- */
- public function tableName( $name, $format = 'quoted' ) {
- // we want maximum compatibility with MySQL schema
- return $name;
- }
-
- /**
- * Generates a timestamp in an insertable format
- *
- * @param $ts string timestamp
- * @return String: timestamp value
- */
- public function timestamp( $ts = 0 ) {
- // TS_MW cannot be easily distinguished from an integer
- return wfTimestamp( TS_DB2, $ts );
- }
-
- /**
- * Return the next in a sequence, save the value for retrieval via insertId()
- * @param $seqName String: name of a defined sequence in the database
- * @return int next value in that sequence
- */
- public function nextSequenceValue( $seqName ) {
- // Not using sequences in the primary schema to allow for easier migration
- // from MySQL
- // Emulating MySQL behaviour of using NULL to signal that sequences
- // aren't used
- /*
- $safeseq = preg_replace( "/'/", "''", $seqName );
- $res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
- $row = $this->fetchRow( $res );
- $this->mInsertId = $row[0];
- return $this->mInsertId;
- */
- return null;
- }
-
- /**
- * This must be called after nextSequenceVal
- * @return int Last sequence value used as a primary key
- */
- public function insertId() {
- return $this->mInsertId;
- }
-
- /**
- * Updates the mInsertId property with the value of the last insert
- * into a generated column
- *
- * @param $table String: sanitized table name
- * @param $primaryKey Mixed: string name of the primary key
- * @param $stmt Resource: prepared statement resource
- * of the SELECT primary_key FROM FINAL TABLE ( INSERT ... ) form
- */
- private function calcInsertId( $table, $primaryKey, $stmt ) {
- if ( $primaryKey ) {
- $this->mInsertId = db2_last_insert_id( $this->mConn );
- }
- }
-
- /**
- * INSERT wrapper, inserts an array into a table
- *
- * $args may be a single associative array, or an array of arrays
- * with numeric keys, for multi-row insert
- *
- * @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 $options String or Array. Valid options: IGNORE
- *
- * @return bool Success of insert operation. IGNORE always returns true.
- */
- public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert',
- $options = array() )
- {
- if ( !count( $args ) ) {
- return true;
- }
- // get database-specific table name (not used)
- $table = $this->tableName( $table );
- // format options as an array
- $options = IBM_DB2Helper::makeArray( $options );
- // format args as an array of arrays
- if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) {
- $args = array( $args );
- }
-
- // prevent insertion of NULL into primary key columns
- list( $args, $primaryKeys ) = $this->removeNullPrimaryKeys( $table, $args );
- // if there's only one primary key
- // we'll be able to read its value after insertion
- $primaryKey = false;
- if ( count( $primaryKeys ) == 1 ) {
- $primaryKey = $primaryKeys[0];
- }
-
- // get column names
- $keys = array_keys( $args[0] );
- $key_count = count( $keys );
-
- // If IGNORE is set, we use savepoints to emulate mysql's behavior
- $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
-
- // assume success
- $res = true;
- // If we are not in a transaction, we need to be for savepoint trickery
- if ( !$this->mTrxLevel ) {
- $this->begin( __METHOD__ );
- }
-
- $sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES ';
- if ( $key_count == 1 ) {
- $sql .= '( ? )';
- } else {
- $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )';
- }
- $this->installPrint( "Preparing the following SQL:" );
- $this->installPrint( "$sql" );
- $this->installPrint( print_r( $args, true ));
- $stmt = $this->prepare( $sql );
-
- // start a transaction/enter transaction mode
- $this->begin( __METHOD__ );
-
- if ( !$ignore ) {
- //$first = true;
- foreach ( $args as $row ) {
- //$this->installPrint( "Inserting " . print_r( $row, true ));
- // insert each row into the database
- $res = $res & $this->execute( $stmt, $row );
- if ( !$res ) {
- $this->installPrint( 'Last error:' );
- $this->installPrint( $this->lastError() );
- }
- // get the last inserted value into a generated column
- $this->calcInsertId( $table, $primaryKey, $stmt );
- }
- } else {
- $olde = error_reporting( 0 );
- // For future use, we may want to track the number of actual inserts
- // Right now, insert (all writes) simply return true/false
- $numrowsinserted = 0;
-
- // always return true
- $res = true;
-
- foreach ( $args as $row ) {
- $overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
- db2_exec( $this->mConn, $overhead, $this->mStmtOptions );
-
- $res2 = $this->execute( $stmt, $row );
-
- if ( !$res2 ) {
- $this->installPrint( 'Last error:' );
- $this->installPrint( $this->lastError() );
- }
- // get the last inserted value into a generated column
- $this->calcInsertId( $table, $primaryKey, $stmt );
-
- $errNum = $this->lastErrno();
- if ( $errNum ) {
- db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore",
- $this->mStmtOptions );
- } else {
- db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore",
- $this->mStmtOptions );
- $numrowsinserted++;
- }
- }
-
- $olde = error_reporting( $olde );
- // Set the affected row count for the whole operation
- $this->mAffectedRows = $numrowsinserted;
- }
- // commit either way
- $this->commit( __METHOD__ );
- $this->freePrepared( $stmt );
-
- return $res;
- }
-
- /**
- * Given a table name and a hash of columns with values
- * Removes primary key columns from the hash where the value is NULL
- *
- * @param $table String: name of the table
- * @param $args Array of hashes of column names with values
- * @return Array: tuple( filtered array of columns, array of primary keys )
- */
- private function removeNullPrimaryKeys( $table, $args ) {
- $schema = $this->mSchema;
-
- // find out the primary keys
- $keyres = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '"
- . strtoupper( $table )
- . "' AND TBCREATOR = '"
- . strtoupper( $schema )
- . "' AND KEYSEQ > 0" );
-
- $keys = array();
- for (
- $row = $this->fetchRow( $keyres );
- $row != null;
- $row = $this->fetchRow( $keyres )
- )
- {
- $keys[] = strtolower( $row[0] );
- }
- // remove primary keys
- foreach ( $args as $ai => $row ) {
- foreach ( $keys as $key ) {
- if ( $row[$key] == null ) {
- unset( $row[$key] );
- }
- }
- $args[$ai] = $row;
- }
- // return modified hash
- return array( $args, $keys );
- }
-
- /**
- * UPDATE wrapper, takes a condition array and a SET array
- *
- * @param $table String: The table to UPDATE
- * @param $values array An array of values to SET
- * @param $conds array An array of conditions ( WHERE ). Use '*' to update all rows.
- * @param $fname String: The Class::Function calling this function
- * ( for the log )
- * @param $options array An array of UPDATE options, can be one or
- * more of IGNORE, LOW_PRIORITY
- * @return Boolean
- */
- public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update',
- $options = array() )
- {
- $table = $this->tableName( $table );
- $opts = $this->makeUpdateOptions( $options );
- $sql = "UPDATE $opts $table SET "
- . $this->makeList( $values, LIST_SET_PREPARED );
- if ( $conds != '*' ) {
- $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
- }
- $stmt = $this->prepare( $sql );
- $this->installPrint( 'UPDATE: ' . print_r( $values, true ) );
- // assuming for now that an array with string keys will work
- // if not, convert to simple array first
- $result = $this->execute( $stmt, $values );
- $this->freePrepared( $stmt );
-
- return $result;
- }
-
- /**
- * DELETE query wrapper
- *
- * Use $conds == "*" to delete all rows
- * @return bool|\ResultWrapper
- */
- public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
- if ( !$conds ) {
- throw new DBUnexpectedError( $this,
- 'DatabaseIbm_db2::delete() called with no conditions' );
- }
- $table = $this->tableName( $table );
- $sql = "DELETE FROM $table";
- if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
- }
- $result = $this->query( $sql, $fname );
-
- return $result;
- }
-
- /**
- * Returns the number of rows affected by the last query or 0
- * @return Integer: the number of rows affected by the last query
- */
- public function affectedRows() {
- if ( !is_null( $this->mAffectedRows ) ) {
- // Forced result for simulated queries
- return $this->mAffectedRows;
- }
- if( empty( $this->mLastResult ) ) {
- return 0;
- }
- return db2_num_rows( $this->mLastResult );
- }
-
- /**
- * Returns the number of rows in the result set
- * Has to be called right after the corresponding select query
- * @param $res Object result set
- * @return Integer: number of rows
- */
- public function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- if ( $this->mNumRows ) {
- return $this->mNumRows;
- } else {
- return 0;
- }
- }
-
- /**
- * Moves the row pointer of the result set
- * @param $res Object: result set
- * @param $row Integer: row number
- * @return bool success or failure
- */
- public function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- return $res = $res->result;
- }
- if ( $res instanceof IBM_DB2Result ) {
- return $res->dataSeek( $row );
- }
- wfDebug( "dataSeek operation in DB2 database\n" );
- return false;
- }
-
- ###
- # Fix notices in Block.php
- ###
-
- /**
- * Frees memory associated with a statement resource
- * @param $res Object: statement resource to free
- * @return Boolean success or failure
- */
- public function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- wfSuppressWarnings();
- $ok = db2_free_result( $res );
- wfRestoreWarnings();
- if ( !$ok ) {
- throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
- }
- }
-
- /**
- * Returns the number of columns in a resource
- * @param $res Object: statement resource
- * @return Number of fields/columns in resource
- */
- public function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( $res instanceof IBM_DB2Result ) {
- $res = $res->getResult();
- }
- return db2_num_fields( $res );
- }
-
- /**
- * Returns the nth column name
- * @param $res Object: statement resource
- * @param $n Integer: Index of field or column
- * @return String name of nth column
- */
- public function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( $res instanceof IBM_DB2Result ) {
- $res = $res->getResult();
- }
- return db2_field_name( $res, $n );
- }
-
- /**
- * SELECT wrapper
- *
- * @param $table Array or string, table name(s) (prefix auto-added)
- * @param $vars Array or string, field name(s) to be retrieved
- * @param $conds Array or string, condition(s) for WHERE
- * @param $fname String: calling function name (use __METHOD__)
- * for logs/profiling
- * @param $options array 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 for fetch functions or false
- * on failure
- */
- public function select( $table, $vars, $conds = '', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
- {
- $res = parent::select( $table, $vars, $conds, $fname, $options,
- $join_conds );
- $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
-
- // We must adjust for offset
- if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
- $limit = $options['LIMIT'];
- $offset = $options['OFFSET'];
- }
-
- // DB2 does not have a proper num_rows() function yet, so we must emulate
- // DB2 9.5.4 and the corresponding ibm_db2 driver will introduce
- // a working one
- // TODO: Yay!
-
- // we want the count
- $vars2 = array( 'count( * ) as num_rows' );
- // respecting just the limit option
- $options2 = array();
- if ( isset( $options['LIMIT'] ) ) {
- $options2['LIMIT'] = $options['LIMIT'];
- }
- // but don't try to emulate for GROUP BY
- if ( isset( $options['GROUP BY'] ) ) {
- return $res;
- }
-
- $res2 = parent::select( $table, $vars2, $conds, $fname, $options2,
- $join_conds );
-
- $obj = $this->fetchObject( $res2 );
- $this->mNumRows = $obj->num_rows;
-
- return new ResultWrapper( $this, new IBM_DB2Result( $this, $res, $obj->num_rows, $vars, $sql ) );
- }
-
- /**
- * Handles ordering, grouping, and having options ('GROUP BY' => colname)
- * Has limited support for per-column options (colnum => 'DISTINCT')
- *
- * @private
- *
- * @param $options array Associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return Array
- */
- function makeSelectOptions( $options ) {
- $preLimitTail = $postLimitTail = '';
- $startOpts = '';
-
- $noKeyOptions = array();
- foreach ( $options as $key => $option ) {
- if ( is_numeric( $key ) ) {
- $noKeyOptions[$option] = true;
- }
- }
-
- if ( isset( $options['GROUP BY'] ) ) {
- $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- }
- if ( isset( $options['HAVING'] ) ) {
- $preLimitTail .= " HAVING {$options['HAVING']}";
- }
- if ( isset( $options['ORDER BY'] ) ) {
- $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
- }
-
- if ( isset( $noKeyOptions['DISTINCT'] )
- || isset( $noKeyOptions['DISTINCTROW'] ) )
- {
- $startOpts .= 'DISTINCT';
- }
-
- return array( $startOpts, '', $preLimitTail, $postLimitTail );
- }
-
- /**
- * Returns link to IBM DB2 free download
- * @return String: wikitext of a link to the server software's web site
- */
- public static function getSoftwareLink() {
- return '[http://www.ibm.com/db2/express/ IBM DB2]';
- }
-
- /**
- * Get search engine class. All subclasses of this
- * need to implement this if they wish to use searching.
- *
- * @return String
- */
- public function getSearchEngine() {
- return 'SearchIBM_DB2';
- }
-
- /**
- * Did the last database access fail because of deadlock?
- * @return Boolean
- */
- public function wasDeadlock() {
- // get SQLSTATE
- $err = $this->lastErrno();
- switch( $err ) {
- // This is literal port of the MySQL logic and may be wrong for DB2
- case '40001': // sql0911n, Deadlock or timeout, rollback
- case '57011': // sql0904n, Resource unavailable, no rollback
- case '57033': // sql0913n, Deadlock or timeout, no rollback
- $this->installPrint( "In a deadlock because of SQLSTATE $err" );
- return true;
- }
- return false;
- }
-
- /**
- * Ping the server and try to reconnect if it there is no connection
- * The connection may be closed and reopened while this happens
- * @return Boolean: whether the connection exists
- */
- public function ping() {
- // db2_ping() doesn't exist
- // Emulate
- $this->close();
- $this->openUncataloged( $this->mDBName, $this->mUser,
- $this->mPassword, $this->mServer, $this->mPort );
-
- return false;
- }
- ######################################
- # Unimplemented and not applicable
- ######################################
-
- /**
- * Only useful with fake prepare like in base Database class
- * @return string
- */
- public function fillPreparedArg( $matches ) {
- $this->installPrint( 'Not useful for DB2: fillPreparedArg()' );
- return '';
- }
-
- ######################################
- # Reflection
- ######################################
-
- /**
- * Returns information about an index
- * If errors are explicitly ignored, returns NULL on failure
- * @param $table String: table name
- * @param $index String: index name
- * @param $fname String: function name for logging and profiling
- * @return Object query row in object form
- */
- public function indexInfo( $table, $index,
- $fname = 'DatabaseIbm_db2::indexExists' )
- {
- $table = $this->tableName( $table );
- $sql = <<<SQL
-SELECT name as indexname
-FROM sysibm.sysindexes si
-WHERE si.name='$index' AND si.tbname='$table'
-AND sc.tbcreator='$this->mSchema'
-SQL;
- $res = $this->query( $sql, $fname );
- if ( !$res ) {
- return null;
- }
- $row = $this->fetchObject( $res );
- if ( $row != null ) {
- return $row;
- } else {
- return false;
- }
- }
-
- /**
- * Returns an information object on a table column
- * @param $table String: table name
- * @param $field String: column name
- * @return IBM_DB2Field
- */
- public function fieldInfo( $table, $field ) {
- return IBM_DB2Field::fromText( $this, $table, $field );
- }
-
- /**
- * db2_field_type() wrapper
- * @param $res Object: result of executed statement
- * @param $index Mixed: number or name of the column
- * @return String column type
- */
- public function fieldType( $res, $index ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( $res instanceof IBM_DB2Result ) {
- $res = $res->getResult();
- }
- return db2_field_type( $res, $index );
- }
-
- /**
- * Verifies that an index was created as unique
- * @param $table String: table name
- * @param $index String: index name
- * @param $fname string function name for profiling
- * @return Bool
- */
- public function indexUnique ( $table, $index,
- $fname = 'DatabaseIbm_db2::indexUnique' )
- {
- $table = $this->tableName( $table );
- $sql = <<<SQL
-SELECT si.name as indexname
-FROM sysibm.sysindexes si
-WHERE si.name='$index' AND si.tbname='$table'
-AND sc.tbcreator='$this->mSchema'
-AND si.uniquerule IN ( 'U', 'P' )
-SQL;
- $res = $this->query( $sql, $fname );
- if ( !$res ) {
- return null;
- }
- if ( $this->fetchObject( $res ) ) {
- return true;
- }
- return false;
-
- }
-
- /**
- * Returns the size of a text field, or -1 for "unlimited"
- * @param $table String: table name
- * @param $field String: column name
- * @return Integer: length or -1 for unlimited
- */
- public function textFieldSize( $table, $field ) {
- $table = $this->tableName( $table );
- $sql = <<<SQL
-SELECT length as size
-FROM sysibm.syscolumns sc
-WHERE sc.name='$field' AND sc.tbname='$table'
-AND sc.tbcreator='$this->mSchema'
-SQL;
- $res = $this->query( $sql );
- $row = $this->fetchObject( $res );
- $size = $row->size;
- return $size;
- }
-
- /**
- * Description is left as an exercise for the reader
- * @param $b Mixed: data to be encoded
- * @return IBM_DB2Blob
- */
- public function encodeBlob( $b ) {
- return new IBM_DB2Blob( $b );
- }
-
- /**
- * Description is left as an exercise for the reader
- * @param $b IBM_DB2Blob: data to be decoded
- * @return mixed
- */
- public function decodeBlob( $b ) {
- return "$b";
- }
-
- /**
- * Convert into a list of string being concatenated
- * @param $stringList Array: strings that need to be joined together
- * by the SQL engine
- * @return String: joined by the concatenation operator
- */
- public function buildConcat( $stringList ) {
- // || is equivalent to CONCAT
- // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz'
- return implode( ' || ', $stringList );
- }
-
- /**
- * Generates the SQL required to convert a DB2 timestamp into a Unix epoch
- * @param $column String: name of timestamp column
- * @return String: SQL code
- */
- public function extractUnixEpoch( $column ) {
- // TODO
- // see SpecialAncientpages
- }
-
- ######################################
- # Prepared statements
- ######################################
-
- /**
- * Intended to be compatible with the PEAR::DB wrapper functions.
- * http://pear.php.net/manual/en/package.database.db.intro-execute.php
- *
- * ? = scalar value, quoted as necessary
- * ! = raw SQL bit (a function for instance)
- * & = filename; reads the file and inserts as a blob
- * (we don't use this though...)
- * @param $sql String: SQL statement with appropriate markers
- * @param $func String: Name of the function, for profiling
- * @return resource a prepared DB2 SQL statement
- */
- public function prepare( $sql, $func = 'DB2::prepare' ) {
- $stmt = db2_prepare( $this->mConn, $sql, $this->mStmtOptions );
- return $stmt;
- }
-
- /**
- * Frees resources associated with a prepared statement
- * @return Boolean success or failure
- */
- public function freePrepared( $prepared ) {
- return db2_free_stmt( $prepared );
- }
-
- /**
- * Execute a prepared query with the various arguments
- * @param $prepared String: the prepared sql
- * @param $args Mixed: either an array here, or put scalars as varargs
- * @return Resource: results object
- */
- public function execute( $prepared, $args = null ) {
- if( !is_array( $args ) ) {
- # Pull the var args
- $args = func_get_args();
- array_shift( $args );
- }
- $res = db2_execute( $prepared, $args );
- if ( !$res ) {
- $this->installPrint( db2_stmt_errormsg() );
- }
- return $res;
- }
-
- /**
- * For faking prepared SQL statements on DBs that don't support
- * it directly.
- * @param $preparedQuery String: a 'preparable' SQL statement
- * @param $args Array of arguments to fill it with
- * @return String: executable statement
- */
- public function fillPrepared( $preparedQuery, $args ) {
- reset( $args );
- $this->preparedArgs =& $args;
-
- foreach ( $args as $i => $arg ) {
- db2_bind_param( $preparedQuery, $i+1, $args[$i] );
- }
-
- return $preparedQuery;
- }
-
- /**
- * Switches module between regular and install modes
- * @return string
- */
- public function setMode( $mode ) {
- $old = $this->mMode;
- $this->mMode = $mode;
- return $old;
- }
-
- /**
- * Bitwise negation of a column or value in SQL
- * Same as (~field) in C
- * @param $field String
- * @return String
- */
- function bitNot( $field ) {
- // expecting bit-fields smaller than 4bytes
- return "BITNOT( $field )";
- }
-
- /**
- * Bitwise AND of two columns or values in SQL
- * Same as (fieldLeft & fieldRight) in C
- * @param $fieldLeft String
- * @param $fieldRight String
- * @return String
- */
- function bitAnd( $fieldLeft, $fieldRight ) {
- return "BITAND( $fieldLeft, $fieldRight )";
- }
-
- /**
- * Bitwise OR of two columns or values in SQL
- * Same as (fieldLeft | fieldRight) in C
- * @param $fieldLeft String
- * @param $fieldRight String
- * @return String
- */
- function bitOr( $fieldLeft, $fieldRight ) {
- return "BITOR( $fieldLeft, $fieldRight )";
- }
-}
-
-class IBM_DB2Helper {
- public static function makeArray( $maybeArray ) {
- if ( !is_array( $maybeArray ) ) {
- return array( $maybeArray );
- }
-
- return $maybeArray;
- }
-}
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 914ab408..6c45ffaf 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -28,9 +28,9 @@
* @ingroup Database
*/
class DatabaseMssql extends DatabaseBase {
- var $mInsertId = NULL;
- var $mLastResult = NULL;
- var $mAffectedRows = NULL;
+ var $mInsertId = null;
+ var $mLastResult = null;
+ var $mAffectedRows = null;
var $mPort;
@@ -61,6 +61,11 @@ class DatabaseMssql extends DatabaseBase {
/**
* Usually aborts on failure
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws DBConnectionError
* @return bool|DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
@@ -97,7 +102,7 @@ class DatabaseMssql extends DatabaseBase {
$ntAuthPassTest = strtolower( $password );
// Decide which auth scenerio to use
- if( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ){
+ if( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ) {
// Don't add credentials to $connectionInfo
} else {
$connectionInfo['UID'] = $user;
@@ -139,7 +144,7 @@ class DatabaseMssql extends DatabaseBase {
// $this->limitResult();
if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
// massage LIMIT -> TopN
- $sql = $this->LimitToTopN( $sql ) ;
+ $sql = $this->LimitToTopN( $sql );
}
// MSSQL doesn't have EXTRACT(epoch FROM XXX)
@@ -151,7 +156,7 @@ 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" .
+ $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)
@@ -279,7 +284,7 @@ class DatabaseMssql extends DatabaseBase {
* @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 $options Array: associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * @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') )
@@ -304,7 +309,7 @@ class DatabaseMssql extends DatabaseBase {
* @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 $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * @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') )
@@ -314,7 +319,7 @@ class DatabaseMssql extends DatabaseBase {
if ( isset( $options['EXPLAIN'] ) ) {
unset( $options['EXPLAIN'] );
}
- return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+ return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
}
/**
@@ -348,7 +353,7 @@ class DatabaseMssql extends DatabaseBase {
$sql = "sp_helpindex '" . $table . "'";
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
$result = array();
@@ -380,6 +385,11 @@ class DatabaseMssql extends DatabaseBase {
*
* Usually aborts on failure
* If errors are explicitly ignored, returns success
+ * @param string $table
+ * @param array $arrToInsert
+ * @param string $fname
+ * @param array $options
+ * @throws DBQueryError
* @return bool
*/
function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) {
@@ -404,7 +414,7 @@ class DatabaseMssql extends DatabaseBase {
$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() ){
+ if( $res && $res->numrows() ) {
// There is an identity for this table.
$identity = array_pop( $res->fetch( SQLSRV_FETCH_ASSOC ) );
}
@@ -421,9 +431,9 @@ class DatabaseMssql extends DatabaseBase {
// iterate through
foreach ($a as $k => $v ) {
if ( $k == $identity ) {
- if( !is_null($v) ){
+ if( !is_null($v) ) {
// there is a value being passed to us, we need to turn on and off inserted identity
- $sqlPre = "SET IDENTITY_INSERT $table ON;" ;
+ $sqlPre = "SET IDENTITY_INSERT $table ON;";
$sqlPost = ";SET IDENTITY_INSERT $table OFF;";
} else {
@@ -474,7 +484,7 @@ class DatabaseMssql extends DatabaseBase {
} elseif ( is_array( $value ) || is_object( $value ) ) {
if ( is_object( $value ) && strtolower( get_class( $value ) ) == 'blob' ) {
$sql .= $this->addQuotes( $value );
- } else {
+ } else {
$sql .= $this->addQuotes( serialize( $value ) );
}
} else {
@@ -488,7 +498,7 @@ class DatabaseMssql extends DatabaseBase {
if ( $ret === false ) {
throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname );
- } elseif ( $ret != NULL ) {
+ } elseif ( $ret != null ) {
// remember number of rows affected
$this->mAffectedRows = sqlsrv_rows_affected( $ret );
if ( !is_null($identity) ) {
@@ -510,7 +520,15 @@ class DatabaseMssql extends DatabaseBase {
* 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.
- * @return null|\ResultWrapper
+ * @param string $destTable
+ * @param array|string $srcTable
+ * @param array $varMap
+ * @param array $conds
+ * @param string $fname
+ * @param array $insertOptions
+ * @param array $selectOptions
+ * @throws DBQueryError
+ * @return null|ResultWrapper
*/
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect',
$insertOptions = array(), $selectOptions = array() ) {
@@ -518,12 +536,12 @@ class DatabaseMssql extends DatabaseBase {
if ( $ret === false ) {
throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), /*$sql*/ '', $fname );
- } elseif ( $ret != NULL ) {
+ } elseif ( $ret != null ) {
// remember number of rows affected
$this->mAffectedRows = sqlsrv_rows_affected( $ret );
return $ret;
}
- return NULL;
+ return null;
}
/**
@@ -590,9 +608,9 @@ class DatabaseMssql extends DatabaseBase {
} 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
+ 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 );
return $sql;
@@ -720,6 +738,8 @@ 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
+ * @throws MWException
* @return string
*/
private function escapeIdentifier( $identifier ) {
@@ -750,17 +770,17 @@ class DatabaseMssql extends DatabaseBase {
$newUser = $this->escapeIdentifier( $newUser );
$loginPassword = $this->addQuotes( $loginPassword );
- $this->doQuery("CREATE DATABASE $dbName;");
- $this->doQuery("USE $dbName;");
- $this->doQuery("CREATE SCHEMA $dbName;");
- $this->doQuery("
+ $this->doQuery( "CREATE DATABASE $dbName;" );
+ $this->doQuery( "USE $dbName;" );
+ $this->doQuery( "CREATE SCHEMA $dbName;" );
+ $this->doQuery( "
CREATE
LOGIN $newUser
WITH
PASSWORD=$loginPassword
;
- ");
- $this->doQuery("
+ " );
+ $this->doQuery( "
CREATE
USER $newUser
FOR
@@ -768,8 +788,8 @@ class DatabaseMssql extends DatabaseBase {
WITH
DEFAULT_SCHEMA=$dbName
;
- ");
- $this->doQuery("
+ " );
+ $this->doQuery( "
GRANT
BACKUP DATABASE,
BACKUP LOG,
@@ -784,17 +804,15 @@ class DatabaseMssql extends DatabaseBase {
DATABASE::$dbName
TO $newUser
;
- ");
- $this->doQuery("
+ " );
+ $this->doQuery( "
GRANT
CONTROL
ON
SCHEMA::$dbName
TO $newUser
;
- ");
-
-
+ " );
}
function encodeBlob( $b ) {
@@ -873,7 +891,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* @private
*
- * @param $options Array: an associative array of options to be turned into
+ * @param array $options an associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return Array
*/
@@ -888,29 +906,23 @@ class DatabaseMssql extends DatabaseBase {
}
}
- if ( isset( $options['GROUP BY'] ) ) {
- $tailOpts .= " GROUP BY {$options['GROUP BY']}";
- }
- if ( isset( $options['HAVING'] ) ) {
- $tailOpts .= " HAVING {$options['GROUP BY']}";
- }
- if ( isset( $options['ORDER BY'] ) ) {
- $tailOpts .= " ORDER BY {$options['ORDER BY']}";
- }
+ $tailOpts .= $this->makeGroupByWithHaving( $options );
+
+ $tailOpts .= $this->makeOrderBy( $options );
if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) {
$startOpts .= 'DISTINCT';
}
// we want this to be compatible with the output of parent::makeSelectOptions()
- return array( $startOpts, '' , $tailOpts, '' );
+ return array( $startOpts, '', $tailOpts, '' );
}
/**
* Get the type of the DBMS, as it appears in $wgDBtype.
* @return string
*/
- function getType(){
+ function getType() {
return 'mssql';
}
@@ -1118,6 +1130,5 @@ class MssqlResult {
public function free() {
unset( $this->mRows );
- return;
}
}
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index 7f389da9..27aae188 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -133,7 +133,7 @@ class DatabaseMysql extends DatabaseBase {
substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
wfProfileOut( __METHOD__ );
- $this->reportConnectionError( $error );
+ return $this->reportConnectionError( $error );
}
if ( $dbName != '' ) {
@@ -146,7 +146,7 @@ class DatabaseMysql extends DatabaseBase {
"from client host " . wfHostname() . "\n" );
wfProfileOut( __METHOD__ );
- $this->reportConnectionError( "Error selecting database $dbName" );
+ return $this->reportConnectionError( "Error selecting database $dbName" );
}
}
@@ -193,7 +193,7 @@ class DatabaseMysql extends DatabaseBase {
/**
* @param $res ResultWrapper
- * @return object|stdClass
+ * @return object|bool
* @throws DBUnexpectedError
*/
function fetchObject( $res ) {
@@ -208,7 +208,7 @@ class DatabaseMysql extends DatabaseBase {
// Unfortunately, mysql_fetch_object does not reset the last errno.
// Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
// these are the only errors mysql_fetch_object can cause.
- // See http://dev.mysql.com/doc/refman/5.0/es/mysql-fetch-row.html.
+ // 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() ) );
}
@@ -217,7 +217,7 @@ class DatabaseMysql extends DatabaseBase {
/**
* @param $res ResultWrapper
- * @return array
+ * @return array|bool
* @throws DBUnexpectedError
*/
function fetchRow( $res ) {
@@ -232,7 +232,7 @@ class DatabaseMysql extends DatabaseBase {
// Unfortunately, mysql_fetch_array does not reset the last errno.
// Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
// these are the only errors mysql_fetch_object can cause.
- // See http://dev.mysql.com/doc/refman/5.0/es/mysql-fetch-row.html.
+ // 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() ) );
}
@@ -251,9 +251,11 @@ class DatabaseMysql extends DatabaseBase {
wfSuppressWarnings();
$n = mysql_num_rows( $res );
wfRestoreWarnings();
- if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
- }
+ // 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.
+ // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
+ // See https://bugzilla.wikimedia.org/42430
return $n;
}
@@ -361,7 +363,7 @@ class DatabaseMysql extends DatabaseBase {
* @param $options string|array
* @return int
*/
- public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) {
+ public function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
if ( $res === false ) {
@@ -393,7 +395,7 @@ class DatabaseMysql extends DatabaseBase {
for( $i = 0; $i < $n; $i++ ) {
$meta = mysql_fetch_field( $res->result, $i );
if( $field == $meta->name ) {
- return new MySQLField($meta);
+ return new MySQLField( $meta );
}
}
return false;
@@ -414,6 +416,7 @@ class DatabaseMysql extends DatabaseBase {
# http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
$table = $this->tableName( $table );
$index = $this->indexName( $index );
+
$sql = 'SHOW INDEX FROM ' . $table;
$res = $this->query( $sql, $fname );
@@ -428,7 +431,6 @@ class DatabaseMysql extends DatabaseBase {
$result[] = $row;
}
}
-
return empty( $result ) ? false : $result;
}
@@ -449,7 +451,7 @@ class DatabaseMysql extends DatabaseBase {
function strencode( $s ) {
$sQuoted = mysql_real_escape_string( $s, $this->mConn );
- if($sQuoted === false) {
+ if( $sQuoted === false ) {
$this->ping();
$sQuoted = mysql_real_escape_string( $s, $this->mConn );
}
@@ -597,10 +599,9 @@ class DatabaseMysql extends DatabaseBase {
if ( $res && $row = $this->fetchRow( $res ) ) {
wfProfileOut( $fname );
return $row[0];
- } else {
- wfProfileOut( $fname );
- return false;
}
+ wfProfileOut( $fname );
+ return false;
}
/**
@@ -686,7 +687,7 @@ class DatabaseMysql extends DatabaseBase {
public function streamStatementEnd( &$sql, &$newLine ) {
if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
- preg_match( '/^DELIMITER\s+(\S+)/' , $newLine, $m );
+ preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
$this->delimiter = $m[1];
$newLine = '';
}
@@ -696,8 +697,8 @@ class DatabaseMysql extends DatabaseBase {
/**
* Check to see if a named lock is available. This is non-blocking.
*
- * @param $lockName String: name of lock to poll
- * @param $method String: name of method calling us
+ * @param string $lockName name of lock to poll
+ * @param string $method name of method calling us
* @return Boolean
* @since 1.20
*/
@@ -722,7 +723,7 @@ class DatabaseMysql extends DatabaseBase {
if( $row->lockstatus == 1 ) {
return true;
} else {
- wfDebug( __METHOD__." failed to acquire lock\n" );
+ wfDebug( __METHOD__ . " failed to acquire lock\n" );
return false;
}
}
@@ -745,6 +746,7 @@ class DatabaseMysql extends DatabaseBase {
* @param $write array
* @param $method string
* @param $lowPriority bool
+ * @return bool
*/
public function lockTables( $read, $write, $method, $lowPriority = true ) {
$items = array();
@@ -760,13 +762,16 @@ class DatabaseMysql extends DatabaseBase {
}
$sql = "LOCK TABLES " . implode( ',', $items );
$this->query( $sql, $method );
+ return true;
}
/**
* @param $method string
+ * @return bool
*/
public function unlockTables( $method ) {
$this->query( "UNLOCK TABLES", $method );
+ return true;
}
/**
@@ -805,7 +810,8 @@ class DatabaseMysql extends DatabaseBase {
* @param $delVar string
* @param $joinVar string
* @param $conds array|string
- * @param $fname bool
+ * @param bool|string $fname bool
+ * @throws DBUnexpectedError
* @return bool|ResultWrapper
*/
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
@@ -889,8 +895,8 @@ class DatabaseMysql extends DatabaseBase {
/**
* List all tables on the database
*
- * @param $prefix string Only show tables with this prefix, e.g. mw_
- * @param $fname String: calling function name
+ * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string $fname calling function name
* @return array
*/
function listTables( $prefix = null, $fname = 'DatabaseMysql::listTables' ) {
@@ -899,7 +905,7 @@ class DatabaseMysql extends DatabaseBase {
$endArray = array();
foreach( $result as $table ) {
- $vars = get_object_vars($table);
+ $vars = get_object_vars( $table );
$table = array_pop( $vars );
if( !$prefix || strpos( $table, $prefix ) === 0 ) {
@@ -952,13 +958,6 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * Legacy support: Database == DatabaseMysql
- *
- * @deprecated in 1.16
- */
-class Database extends DatabaseMysql {}
-
-/**
* Utility class.
* @ingroup Database
*/
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 7d8884fb..75b3550a 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -23,7 +23,7 @@
/**
* The oci8 extension is fairly weak and doesn't support oci_num_rows, among
- * other things. We use a wrapper class to handle that and other
+ * other things. We use a wrapper class to handle that and other
* Oracle-specific bits, like converting column names back to lowercase.
* @ingroup Database
*/
@@ -69,7 +69,7 @@ class ORAResult {
$this->nrows = count( $this->rows );
}
- if ($this->nrows > 0) {
+ if ( $this->nrows > 0 ) {
foreach ( $this->rows[0] as $k => $v ) {
$this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) );
}
@@ -80,7 +80,7 @@ class ORAResult {
}
public function free() {
- unset($this->db);
+ unset( $this->db );
}
public function seek( $row ) {
@@ -92,7 +92,7 @@ class ORAResult {
}
public function numFields() {
- return count($this->columns);
+ return count( $this->columns );
}
public function fetchObject() {
@@ -241,6 +241,11 @@ class DatabaseOracle extends DatabaseBase {
/**
* Usually aborts on failure
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws DBConnectionError
* @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
@@ -313,7 +318,7 @@ class DatabaseOracle extends DatabaseBase {
protected function doQuery( $sql ) {
wfDebug( "SQL: [$sql]\n" );
- if ( !mb_check_encoding( $sql ) ) {
+ if ( !StringUtils::isUtf8( $sql ) ) {
throw new MWException( "SQL encoding is invalid\n$sql" );
}
@@ -628,7 +633,7 @@ class DatabaseOracle extends DatabaseBase {
}
list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
if ( is_array( $srcTable ) ) {
- $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
+ $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
} else {
$srcTable = $this->tableName( $srcTable );
}
@@ -751,7 +756,7 @@ class DatabaseOracle extends DatabaseBase {
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() {
@@ -773,8 +778,8 @@ class DatabaseOracle extends DatabaseBase {
function listTables( $prefix = null, $fname = 'DatabaseOracle::listTables' ) {
$listWhere = '';
- if (!empty($prefix)) {
- $listWhere = ' AND table_name LIKE \''.strtoupper($prefix).'%\'';
+ if ( !empty( $prefix ) ) {
+ $listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
}
$owner = strtoupper( $this->mDBname );
@@ -782,12 +787,12 @@ class DatabaseOracle extends DatabaseBase {
// dirty code ... i know
$endArray = array();
- $endArray[] = strtoupper($prefix.'MWUSER');
- $endArray[] = strtoupper($prefix.'PAGE');
- $endArray[] = strtoupper($prefix.'IMAGE');
+ $endArray[] = strtoupper( $prefix . 'MWUSER' );
+ $endArray[] = strtoupper( $prefix . 'PAGE' );
+ $endArray[] = strtoupper( $prefix . 'IMAGE' );
$fixedOrderTabs = $endArray;
- while (($row = $result->fetchRow()) !== false) {
- if (!in_array($row['table_name'], $fixedOrderTabs))
+ while ( ($row = $result->fetchRow()) !== false ) {
+ if ( !in_array( $row['table_name'], $fixedOrderTabs ) )
$endArray[] = $row['table_name'];
}
@@ -795,7 +800,7 @@ class DatabaseOracle extends DatabaseBase {
}
public function dropTable( $tableName, $fName = 'DatabaseOracle::dropTable' ) {
- $tableName = $this->tableName($tableName);
+ $tableName = $this->tableName( $tableName );
if( !$this->tableExists( $tableName ) ) {
return false;
}
@@ -841,7 +846,7 @@ 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%\'' );
- if ( !( $row = $rset->fetchRow() ) ) {
+ if ( !( $row = $rset->fetchRow() ) ) {
return oci_server_version( $this->mConn );
}
return $row['version'];
@@ -902,7 +907,7 @@ class DatabaseOracle extends DatabaseBase {
$table = array_map( array( &$this, 'tableNameInternal' ), $table );
$tableWhere = 'IN (';
foreach( $table as &$singleTable ) {
- $singleTable = $this->removeIdentifierQuotes($singleTable);
+ $singleTable = $this->removeIdentifierQuotes( $singleTable );
if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
return $this->mFieldInfoCache["$singleTable.$field"];
}
@@ -910,14 +915,14 @@ class DatabaseOracle extends DatabaseBase {
}
$tableWhere = rtrim( $tableWhere, ',' ) . ')';
} else {
- $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
+ $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
return $this->mFieldInfoCache["$table.$field"];
}
$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__ );
@@ -952,7 +957,7 @@ class DatabaseOracle extends DatabaseBase {
if ( is_array( $table ) ) {
throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
}
- return $this->fieldInfoMulti ($table, $field);
+ return $this->fieldInfoMulti( $table, $field );
}
protected function doBegin( $fname = 'DatabaseOracle::begin' ) {
@@ -1061,7 +1066,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $db == null || $db == $this->mUser ) {
return true;
}
- $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper($db);
+ $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
$stmt = oci_parse( $this->mConn, $sql );
wfSuppressWarnings();
$success = oci_execute( $stmt );
@@ -1096,11 +1101,11 @@ class DatabaseOracle extends DatabaseBase {
}
public function removeIdentifierQuotes( $s ) {
- return strpos($s, '/*Q*/') === FALSE ? $s : substr($s, 5);
+ return strpos( $s, '/*Q*/' ) === false ? $s : substr( $s, 5 );
}
public function isQuotedIdentifier( $s ) {
- return strpos($s, '/*Q*/') !== FALSE;
+ return strpos( $s, '/*Q*/' ) !== false;
}
private function wrapFieldForWhere( $table, &$col, &$val ) {
@@ -1111,7 +1116,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $col_type == 'CLOB' ) {
$col = 'TO_CHAR(' . $col . ')';
$val = $wgContLang->checkTitleEncoding( $val );
- } elseif ( $col_type == 'VARCHAR2' && !mb_check_encoding( $val ) ) {
+ } elseif ( $col_type == 'VARCHAR2' ) {
$val = $wgContLang->checkTitleEncoding( $val );
}
}
@@ -1134,7 +1139,7 @@ class DatabaseOracle extends DatabaseBase {
}
function selectRow( $table, $vars, $conds, $fname = 'DatabaseOracle::selectRow', $options = array(), $join_conds = array() ) {
- if ( is_array($conds) ) {
+ if ( is_array( $conds ) ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
}
return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -1146,7 +1151,7 @@ class DatabaseOracle extends DatabaseBase {
*
* @private
*
- * @param $options Array: an associative array of options to be turned into
+ * @param array $options an associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return array
*/
@@ -1161,15 +1166,14 @@ class DatabaseOracle extends DatabaseBase {
}
}
- if ( isset( $options['GROUP BY'] ) ) {
- $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- }
- if ( isset( $options['ORDER BY'] ) ) {
- $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
+ $preLimitTail .= $this->makeGroupByWithHaving( $options );
+
+ $preLimitTail .= $this->makeOrderBy( $options );
+
+ if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+ $postLimitTail .= ' FOR UPDATE';
}
- # if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
- # if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
$startOpts .= 'DISTINCT';
}
@@ -1184,13 +1188,13 @@ class DatabaseOracle extends DatabaseBase {
}
public function delete( $table, $conds, $fname = 'DatabaseOracle::delete' ) {
- if ( is_array($conds) ) {
+ if ( is_array( $conds ) ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
}
// a hack for deleting pages, users and images (which have non-nullable FKs)
// all deletions on these tables have transactions so final failure rollbacks these updates
$table = $this->tableName( $table );
- if ( $table == $this->tableName( 'user' ) ) {
+ 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 );
@@ -1200,7 +1204,7 @@ class DatabaseOracle extends DatabaseBase {
$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' ) ) {
+ } elseif ( $table == $this->tableName( 'image' ) ) {
$this->update( 'oldimage', array( 'oi_name' => 0 ), array( 'oi_name' => $conds['img_name'] ), $fname );
}
return parent::delete( $table, $conds, $fname );
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index 457bf384..f32d7758 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -176,8 +176,8 @@ class PostgresTransactionState {
$old = reset( $this->mCurrentState );
$new = reset( $this->mNewState );
foreach ( self::$WATCHED as $watched ) {
- if ($old !== $new) {
- $this->log_changed($old, $new, $watched);
+ if ( $old !== $new ) {
+ $this->log_changed( $old, $new, $watched );
}
$old = next( $this->mCurrentState );
$new = next( $this->mNewState );
@@ -197,11 +197,11 @@ class PostgresTransactionState {
}
protected function log_changed( $old, $new, $watched ) {
- wfDebug(sprintf($watched["desc"],
+ wfDebug( sprintf( $watched["desc"],
$this->mConn,
$this->describe_changed( $old, $watched["states"] ),
- $this->describe_changed( $new, $watched["states"] ))
- );
+ $this->describe_changed( $new, $watched["states"] )
+ ) );
}
}
@@ -218,7 +218,7 @@ class SavepointPostgres {
protected $id;
protected $didbegin;
- public function __construct ($dbw, $id) {
+ public function __construct ( $dbw, $id ) {
$this->dbw = $dbw;
$this->id = $id;
$this->didbegin = false;
@@ -232,12 +232,14 @@ class SavepointPostgres {
public function __destruct() {
if ( $this->didbegin ) {
$this->dbw->rollback();
+ $this->didbegin = false;
}
}
public function commit() {
if ( $this->didbegin ) {
$this->dbw->commit();
+ $this->didbegin = false;
}
}
@@ -245,29 +247,29 @@ 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 ) );
}
}
public function savepoint() {
- $this->query("SAVEPOINT",
+ $this->query( "SAVEPOINT",
"Transaction state: savepoint \"%s\" established.\n",
"Transaction state: establishment of savepoint \"%s\" FAILED.\n"
);
}
public function release() {
- $this->query("RELEASE",
+ $this->query( "RELEASE",
"Transaction state: savepoint \"%s\" released.\n",
"Transaction state: release of savepoint \"%s\" FAILED.\n"
);
}
public function rollback() {
- $this->query("ROLLBACK TO",
+ $this->query( "ROLLBACK TO",
"Transaction state: savepoint \"%s\" rolled back.\n",
"Transaction state: rollback of savepoint \"%s\" FAILED.\n"
);
@@ -325,6 +327,11 @@ class DatabasePostgres extends DatabaseBase {
/**
* Usually aborts on failure
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws DBConnectionError
* @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
@@ -386,6 +393,9 @@ class DatabasePostgres extends DatabaseBase {
$this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
$this->query( "SET timezone = 'GMT'", __METHOD__ );
$this->query( "SET standard_conforming_strings = on", __METHOD__ );
+ if ( $this->getServerVersion() >= 9.0 ) {
+ $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
+ }
global $wgDBmwschema;
$this->determineCoreSchema( $wgDBmwschema );
@@ -472,7 +482,6 @@ class DatabasePostgres extends DatabaseBase {
parent::reportQueryError( $error, $errno, $sql, $fname, false );
}
-
function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) {
return $this->query( $sql, $fname, true );
}
@@ -599,7 +608,7 @@ class DatabasePostgres extends DatabaseBase {
* Takes same arguments as Database::select()
* @return int
*/
- function estimateRowCount( $table, $vars = '*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
+ function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
@@ -677,7 +686,7 @@ class DatabasePostgres extends DatabaseBase {
AND i.indclass[s.g] = opcls.oid
AND pg_am.oid = opcls.opcmethod
__INDEXATTR__;
- $res = $this->query($sql, __METHOD__);
+ $res = $this->query( $sql, __METHOD__ );
$a = array();
if ( $res ) {
foreach ( $res as $row ) {
@@ -685,7 +694,7 @@ __INDEXATTR__;
$row->attname,
$row->opcname,
$row->amname,
- $row->option);
+ $row->option );
}
} else {
return null;
@@ -693,7 +702,6 @@ __INDEXATTR__;
return $a;
}
-
function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
$sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
" AND indexdef LIKE 'CREATE UNIQUE%(" .
@@ -718,7 +726,7 @@ __INDEXATTR__;
* @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 $options String or Array. Valid options: IGNORE
+ * @param string $options or Array. Valid options: IGNORE
*
* @return bool Success of insert operation. IGNORE always returns true.
*/
@@ -728,7 +736,7 @@ __INDEXATTR__;
}
$table = $this->tableName( $table );
- if (! isset( $this->numeric_version ) ) {
+ if ( !isset( $this->numeric_version ) ) {
$this->getServerVersion();
}
@@ -980,7 +988,7 @@ __INDEXATTR__;
$endArray = array();
foreach( $result as $table ) {
- $vars = get_object_vars($table);
+ $vars = get_object_vars( $table );
$table = array_pop( $vars );
if( !$prefix || strpos( $table, $prefix ) === 0 ) {
$endArray[] = $table;
@@ -1052,7 +1060,6 @@ __INDEXATTR__;
return '[http://www.postgresql.org/ PostgreSQL]';
}
-
/**
* Return current schema (executes SELECT current_schema())
* Needs transaction
@@ -1061,7 +1068,7 @@ __INDEXATTR__;
* @return string return default schema for the current session
*/
function getCurrentSchema() {
- $res = $this->query( "SELECT current_schema()", __METHOD__);
+ $res = $this->query( "SELECT current_schema()", __METHOD__ );
$row = $this->fetchRow( $res );
return $row[0];
}
@@ -1071,17 +1078,17 @@ __INDEXATTR__;
* This is list does not contain magic keywords like "$user"
* Needs transaction
*
- * @seealso getSearchPath()
- * @seealso setSearchPath()
+ * @see getSearchPath()
+ * @see setSearchPath()
* @since 1.19
* @return array list of actual schemas for the current sesson
*/
function getSchemas() {
- $res = $this->query( "SELECT current_schemas(false)", __METHOD__);
+ $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);
+ return $this->pg_array_parse( $row[0], $schemas );
}
/**
@@ -1094,10 +1101,10 @@ __INDEXATTR__;
* @return array how to search for table names schemas for the current user
*/
function getSearchPath() {
- $res = $this->query( "SHOW search_path", __METHOD__);
+ $res = $this->query( "SHOW search_path", __METHOD__ );
$row = $this->fetchRow( $res );
/* PostgreSQL returns SHOW values as strings */
- return explode(",", $row[0]);
+ return explode( ",", $row[0] );
}
/**
@@ -1108,7 +1115,7 @@ __INDEXATTR__;
* @param $search_path array list of schemas to be searched by default
*/
function setSearchPath( $search_path ) {
- $this->query( "SET search_path = " . implode(", ", $search_path) );
+ $this->query( "SET search_path = " . implode( ", ", $search_path ) );
}
/**
@@ -1129,7 +1136,7 @@ __INDEXATTR__;
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");
+ wfDebug( "Schema \"" . $desired_schema . "\" already in the search path\n" );
} else {
/**
* Prepend our schema (e.g. 'mediawiki') in front
@@ -1141,11 +1148,11 @@ __INDEXATTR__;
$this->addIdentifierQuotes( $desired_schema ));
$this->setSearchPath( $search_path );
$this->mCoreSchema = $desired_schema;
- wfDebug("Schema \"" . $desired_schema . "\" added to the search path\n");
+ wfDebug( "Schema \"" . $desired_schema . "\" added to the search path\n" );
}
} else {
$this->mCoreSchema = $this->getCurrentSchema();
- wfDebug("Schema \"" . $desired_schema . "\" not found, using current \"". $this->mCoreSchema ."\"\n");
+ wfDebug( "Schema \"" . $desired_schema . "\" not found, using current \"" . $this->mCoreSchema . "\"\n" );
}
/* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
$this->commit( __METHOD__ );
@@ -1251,8 +1258,8 @@ SQL;
}
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 )
@@ -1340,7 +1347,7 @@ SQL;
*
* @private
*
- * @param $ins String: SQL string, read from a stream (usually tables.sql)
+ * @param string $ins SQL string, read from a stream (usually tables.sql)
*
* @return string SQL string
*/
@@ -1364,7 +1371,7 @@ SQL;
*
* @private
*
- * @param $options Array: an associative array of options to be turned into
+ * @param array $options an associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return array
*/
@@ -1379,23 +1386,9 @@ SQL;
}
}
- if ( isset( $options['GROUP BY'] ) ) {
- $gb = is_array( $options['GROUP BY'] )
- ? implode( ',', $options['GROUP BY'] )
- : $options['GROUP BY'];
- $preLimitTail .= " GROUP BY {$gb}";
- }
+ $preLimitTail .= $this->makeGroupByWithHaving( $options );
- if ( isset( $options['HAVING'] ) ) {
- $preLimitTail .= " HAVING {$options['HAVING']}";
- }
-
- if ( isset( $options['ORDER BY'] ) ) {
- $ob = is_array( $options['ORDER BY'] )
- ? implode( ',', $options['ORDER BY'] )
- : $options['ORDER BY'];
- $preLimitTail .= " ORDER BY {$ob}";
- }
+ $preLimitTail .= $this->makeOrderBy( $options );
//if ( isset( $options['LIMIT'] ) ) {
// $tailOpts .= $this->limitResult( '', $options['LIMIT'],
@@ -1443,4 +1436,65 @@ SQL;
}
return parent::streamStatementEnd( $sql, $newLine );
}
+
+ /**
+ * 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
+ * @since 1.20
+ */
+ public function lockIsFree( $lockName, $method ) {
+ $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+ $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
+ * @return bool
+ */
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+ for ( $attempts = 1; $attempts <= $timeout; ++$attempts ) {
+ $result = $this->query(
+ "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
+ $row = $this->fetchObject( $result );
+ if ( $row->lockstatus === 't' ) {
+ return true;
+ } else {
+ sleep( 1 );
+ }
+ }
+ 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
+ * @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' );
+ }
+
+ /**
+ * @param string $lockName
+ * @return string Integer
+ */
+ private function bigintFromLockName( $lockName ) {
+ return wfBaseConvert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
+ }
} // end DatabasePostgres class
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index f1e553d7..0789e1b1 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -79,11 +79,12 @@ class DatabaseSqlite extends DatabaseBase {
/** Open an SQLite database and return a resource handle to it
* NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
*
- * @param $server
- * @param $user
- * @param $pass
- * @param $dbName
+ * @param string $server
+ * @param string $user
+ * @param string $pass
+ * @param string $dbName
*
+ * @throws DBConnectionError
* @return PDO
*/
function open( $server, $user, $pass, $dbName ) {
@@ -103,6 +104,7 @@ class DatabaseSqlite extends DatabaseBase {
*
* @param $fileName string
*
+ * @throws DBConnectionError
* @return PDO|bool SQL connection or false if failed
*/
function openFile( $fileName ) {
@@ -125,6 +127,8 @@ class DatabaseSqlite extends DatabaseBase {
# 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;
}
}
@@ -140,8 +144,8 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Generates a database file name. Explicitly public for installer.
- * @param $dir String: Directory where database resides
- * @param $dbName String: Database name
+ * @param string $dir Directory where database resides
+ * @param string $dbName Database name
* @return String
*/
public static function generateFileName( $dir, $dbName ) {
@@ -159,7 +163,7 @@ class DatabaseSqlite extends DatabaseBase {
$res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
if ( $res ) {
$row = $res->fetchRow();
- self::$fulltextEnabled = stristr($row['sql'], 'fts' ) !== false;
+ self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
}
}
return self::$fulltextEnabled;
@@ -190,9 +194,9 @@ class DatabaseSqlite extends DatabaseBase {
* Attaches external database to our connection, see http://sqlite.org/lang_attach.html
* for details.
*
- * @param $name String: database name to be used in queries like SELECT foo FROM dbname.table
- * @param $file String: database file name. If omitted, will be generated using $name and $wgSQLiteDataDir
- * @param $fname String: calling function name
+ * @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
*
* @return ResultWrapper
*/
@@ -248,7 +252,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* @param $res ResultWrapper
- * @return
+ * @return object|bool
*/
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -274,7 +278,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* @param $res ResultWrapper
- * @return bool|mixed
+ * @return array|bool
*/
function fetchRow( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -467,7 +471,7 @@ class DatabaseSqlite extends DatabaseBase {
*/
function makeSelectOptions( $options ) {
foreach ( $options as $k => $v ) {
- if ( is_numeric( $k ) && $v == 'FOR UPDATE' ) {
+ if ( is_numeric( $k ) && ($v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE') ) {
$options[$k] = '';
}
}
@@ -593,7 +597,7 @@ class DatabaseSqlite extends DatabaseBase {
* @return bool
*/
function wasErrorReissuable() {
- return $this->lastErrno() == 17; // SQLITE_SCHEMA;
+ return $this->lastErrno() == 17; // SQLITE_SCHEMA;
}
/**
@@ -703,6 +707,14 @@ class DatabaseSqlite extends DatabaseBase {
function addQuotes( $s ) {
if ( $s instanceof Blob ) {
return "x'" . bin2hex( $s->fetch() ) . "'";
+ } else if ( strpos( $s, "\0" ) !== false ) {
+ // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
+ // This is a known limitation of SQLite's mprintf function which PDO should work around,
+ // but doesn't. I have reported this to php.net as bug #63419:
+ // https://bugs.php.net/bug.php?id=63419
+ // There was already a similar report for SQLite3::escapeString, bug #62361:
+ // https://bugs.php.net/bug.php?id=62361
+ return "x'" . bin2hex( $s ) . "'";
} else {
return $this->mConn->quote( $s );
}
@@ -819,12 +831,11 @@ class DatabaseSqlite extends DatabaseBase {
return $this->query( $sql, $fname );
}
-
/**
* List all tables on the database
*
- * @param $prefix string Only show tables with this prefix, e.g. mw_
- * @param $fname String: calling function name
+ * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string $fname calling function name
*
* @return array
*/
@@ -838,7 +849,7 @@ class DatabaseSqlite extends DatabaseBase {
$endArray = array();
foreach( $result as $table ) {
- $vars = get_object_vars($table);
+ $vars = get_object_vars( $table );
$table = array_pop( $vars );
if( !$prefix || strpos( $table, $prefix ) === 0 ) {
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
index c846788d..9a1c8bde 100644
--- a/includes/db/DatabaseUtility.php
+++ b/includes/db/DatabaseUtility.php
@@ -1,6 +1,6 @@
<?php
/**
- * This file contains database-related utiliy classes.
+ * This file contains database-related utility classes.
*
* 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
@@ -138,7 +138,7 @@ class ResultWrapper implements Iterator {
/**
* 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'].
*
* @return Array
* @throws DBUnexpectedError Thrown if the database returns an error
@@ -219,9 +219,9 @@ 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 $result = array();
+ var $db = null; // And it's going to stay that way :D
+ var $pos = 0;
var $currentRow = null;
function __construct( $array ) {
@@ -285,7 +285,7 @@ class LikeMatch {
/**
* Store a string into a LikeMatch marker object.
*
- * @param String $s
+ * @param string $s
*/
public function __construct( $s ) {
$this->str = $s;
@@ -306,4 +306,3 @@ class LikeMatch {
*/
interface DBMasterPos {
}
-
diff --git a/includes/db/IORMRow.php b/includes/db/IORMRow.php
index e99ba6cc..6bc0cdd2 100644
--- a/includes/db/IORMRow.php
+++ b/includes/db/IORMRow.php
@@ -27,13 +27,12 @@
* @file
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
interface IORMRow {
-
/**
* Constructor.
*
@@ -272,4 +271,4 @@ interface IORMRow {
*/
public function getTable();
-} \ No newline at end of file
+}
diff --git a/includes/db/IORMTable.php b/includes/db/IORMTable.php
index 99413f99..36865655 100644
--- a/includes/db/IORMTable.php
+++ b/includes/db/IORMTable.php
@@ -23,7 +23,7 @@
* @file
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
@@ -97,6 +97,8 @@ interface IORMTable {
* Selects the the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
+ * @see DatabaseBase::select()
+ *
* @since 1.20
*
* @param array|string|null $fields
@@ -104,7 +106,8 @@ interface IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return ORMResult
+ * @return ORMResult The result set
+ * @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 );
@@ -136,6 +139,7 @@ interface IORMTable {
* @param null|string $functionName
*
* @return ResultWrapper
+ * @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 );
@@ -230,6 +234,15 @@ interface IORMTable {
public function has( array $conditions = array() );
/**
+ * Checks if the table exists
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function exists();
+
+ /**
* Returns the amount of matching records.
* Condition field names get prefixed.
*
@@ -299,6 +312,71 @@ interface IORMTable {
public function setReadDb( $db );
/**
+ * Get the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @since 1.20
+ *
+ * @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)
+ *
+ * @since 1.20
+ */
+ public function setTargetWiki( $wiki );
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getReadDbConnection();
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getWriteDbConnection();
+
+ /**
+ * Get the database type used for read operations.
+ *
+ * @see wfGetLB
+ *
+ * @since 1.20
+ *
+ * @return LoadBalancer The database load balancer object
+ */
+ public function getLoadBalancer();
+
+ /**
+ * Releases the lease on the given database connection. This is useful mainly
+ * for connections to a foreign wiki. It does nothing for connections to the local wiki.
+ *
+ * @see LoadBalancer::reuseConnection
+ *
+ * @param DatabaseBase $db the database
+ *
+ * @since 1.20
+ */
+ public function releaseConnection( DatabaseBase $db );
+
+ /**
* Update the records matching the provided conditions by
* setting the fields that are keys in the $values param to
* their corresponding values.
@@ -381,15 +459,6 @@ interface IORMTable {
public function unprefixFieldName( $fieldName );
/**
- * Get an instance of this class.
- *
- * @since 1.20
- *
- * @return IORMTable
- */
- public static function singleton();
-
- /**
* Get an array with fields from a database result,
* that can be fed directly to the constructor or
* to setFields.
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index e82c54ba..d469e867 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -86,7 +86,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 $wiki String: wiki ID, or false for the current wiki
+ * @param string $wiki wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function newMainLB( $wiki = false );
@@ -94,7 +94,7 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer object.
*
- * @param $wiki String: wiki ID, or false for the current wiki
+ * @param string $wiki wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function getMainLB( $wiki = false );
@@ -104,8 +104,8 @@ abstract class LBFactory {
* untracked, not chronology-protected, and the caller is responsible for
* cleaning it up.
*
- * @param $cluster String: external storage cluster, or false for core
- * @param $wiki String: wiki ID, or false for the current wiki
+ * @param string $cluster external storage cluster, or false for core
+ * @param string $wiki wiki ID, or false for the current wiki
*
* @return LoadBalancer
*/
@@ -114,8 +114,8 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer for external storage
*
- * @param $cluster String: external storage cluster, or false for core
- * @param $wiki String: wiki ID, or false for the current wiki
+ * @param string $cluster external storage cluster, or false for core
+ * @param string $wiki wiki ID, or false for the current wiki
*
* @return LoadBalancer
*/
@@ -240,7 +240,7 @@ class LBFactory_Simple extends LBFactory {
function newExternalLB( $cluster, $wiki = false ) {
global $wgExternalServers;
if ( !isset( $wgExternalServers[$cluster] ) ) {
- throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
+ throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
return new LoadBalancer( array(
'servers' => $wgExternalServers[$cluster]
@@ -345,7 +345,7 @@ class ChronologyProtector {
if ( $lb->getServerCount() > 1 && !empty( $this->startupPos[$masterName] ) ) {
$info = $lb->parentInfo();
$pos = $this->startupPos[$masterName];
- wfDebug( __METHOD__.": LB " . $info['id'] . " waiting for master pos $pos\n" );
+ wfDebug( __METHOD__ . ": LB " . $info['id'] . " waiting for master pos $pos\n" );
$lb->waitFor( $this->startupPos[$masterName] );
}
}
@@ -370,11 +370,11 @@ class ChronologyProtector {
$db = $lb->getAnyOpenConnection( 0 );
$info = $lb->parentInfo();
if ( !$db || !$db->doneWrites() ) {
- wfDebug( __METHOD__.": LB {$info['id']}, no writes done\n" );
+ wfDebug( __METHOD__ . ": LB {$info['id']}, no writes done\n" );
return;
}
$pos = $db->getMasterPos();
- wfDebug( __METHOD__.": LB {$info['id']} has master pos $pos\n" );
+ wfDebug( __METHOD__ . ": LB {$info['id']} has master pos $pos\n" );
$this->shutdownPos[$masterName] = $pos;
}
@@ -384,7 +384,7 @@ class ChronologyProtector {
*/
function shutdown() {
if ( session_id() != '' && count( $this->shutdownPos ) ) {
- wfDebug( __METHOD__.": saving master pos for " .
+ wfDebug( __METHOD__ . ": saving master pos for " .
count( $this->shutdownPos ) . " master(s)\n" );
$_SESSION[__CLASS__] = $this->shutdownPos;
}
diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactory_Multi.php
index 6008813b..2e4963d4 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactory_Multi.php
@@ -21,7 +21,6 @@
* @ingroup Database
*/
-
/**
* A multi-wiki, multi-master factory for Wikimedia and similar installations.
* Ignores the old configuration globals
@@ -70,6 +69,7 @@ class LBFactory_Multi extends LBFactory {
/**
* @param $conf array
+ * @throws MWException
*/
function __construct( $conf ) {
$this->chronProt = new ChronologyProtector;
@@ -82,7 +82,7 @@ class LBFactory_Multi extends LBFactory {
foreach ( $required as $key ) {
if ( !isset( $conf[$key] ) ) {
- throw new MWException( __CLASS__.": $key is required in configuration" );
+ throw new MWException( __CLASS__ . ": $key is required in configuration" );
}
$this->$key = $conf[$key];
}
@@ -153,13 +153,14 @@ class LBFactory_Multi extends LBFactory {
}
/**
- * @param $cluster
- * @param $wiki
+ * @param string $cluster
+ * @param bool $wiki
+ * @throws MWException
* @return LoadBalancer
*/
function newExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->externalLoads[$cluster] ) ) {
- throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
+ throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
$template = $this->serverTemplate;
if ( isset( $this->externalTemplateOverrides ) ) {
diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactory_Single.php
index 4b165b2a..7dca06d7 100644
--- a/includes/db/LBFactory_Single.php
+++ b/includes/db/LBFactory_Single.php
@@ -28,7 +28,7 @@ class LBFactory_Single extends LBFactory {
protected $lb;
/**
- * @param $conf array An associative array with one member:
+ * @param array $conf An associative array with one member:
* - connection: The DatabaseBase connection object
*/
function __construct( $conf ) {
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index 0e455e0c..1e859278 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -37,14 +37,15 @@ class LoadBalancer {
private $mLoadMonitorClass, $mLoadMonitor;
/**
- * @param $params Array with keys:
+ * @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.
+ * @throws MWException
*/
function __construct( $params ) {
if ( !isset( $params['servers'] ) ) {
- throw new MWException( __CLASS__.': missing servers parameter' );
+ throw new MWException( __CLASS__ . ': missing servers parameter' );
}
$this->mServers = $params['servers'];
@@ -116,34 +117,14 @@ class LoadBalancer {
* Given an array of non-normalised probabilities, this function will select
* an element and return the appropriate key
*
+ * @deprecated 1.21, use ArrayUtils::pickRandom()
+ *
* @param $weights array
*
- * @return int
+ * @return bool|int|string
*/
function pickRandom( $weights ) {
- if ( !is_array( $weights ) || count( $weights ) == 0 ) {
- return false;
- }
-
- $sum = array_sum( $weights );
- if ( $sum == 0 ) {
- # No loads on any of them
- # In previous versions, this triggered an unweighted random selection,
- # but this feature has been removed as of April 2006 to allow for strict
- # separation of query groups.
- return false;
- }
- $max = mt_getrandmax();
- $rand = mt_rand( 0, $max ) / $max * $sum;
-
- $sum = 0;
- foreach ( $weights as $i => $w ) {
- $sum += $w;
- if ( $sum >= $rand ) {
- break;
- }
- }
- return $i;
+ return ArrayUtils::pickRandom( $weights );
}
/**
@@ -197,17 +178,18 @@ class LoadBalancer {
* Side effect: opens connections to databases
* @param $group bool
* @param $wiki bool
+ * @throws MWException
* @return bool|int|string
*/
function getReaderIndex( $group = false, $wiki = false ) {
global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype;
# @todo FIXME: For now, only go through all this for mysql databases
- if ($wgDBtype != 'mysql') {
+ if ( $wgDBtype != 'mysql' ) {
return $this->getWriterIndex();
}
- if ( count( $this->mServers ) == 1 ) {
+ if ( count( $this->mServers ) == 1 ) {
# Skip the load balancing if there's only one server
return 0;
} elseif ( $group === false and $this->mReadIndex >= 0 ) {
@@ -228,7 +210,7 @@ class LoadBalancer {
$nonErrorLoads = $this->mGroupLoads[$group];
} else {
# No loads for this group, return false and the caller can use some other group
- wfDebug( __METHOD__.": no loads for group $group\n" );
+ wfDebug( __METHOD__ . ": no loads for group $group\n" );
wfProfileOut( __METHOD__ );
return false;
}
@@ -256,7 +238,7 @@ class LoadBalancer {
$i = $this->pickRandom( $currentLoads );
} else {
$i = $this->getRandomNonLagged( $currentLoads, $wiki );
- if ( $i === false && count( $currentLoads ) != 0 ) {
+ 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 ' .
@@ -270,16 +252,16 @@ class LoadBalancer {
# 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" );
+ wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false\n" );
wfProfileOut( __METHOD__ );
return false;
}
- wfDebugLog( 'connect', __METHOD__.": Using reader #$i: {$this->mServers[$i]['host']}...\n" );
+ 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" );
+ wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki\n" );
unset( $nonErrorLoads[$i] );
unset( $currentLoads[$i] );
continue;
@@ -318,7 +300,7 @@ class LoadBalancer {
# Some servers must have been overloaded
if ( $overloadedServers == 0 ) {
- throw new MWException( __METHOD__.": unexpectedly found no overloaded servers" );
+ throw new MWException( __METHOD__ . ": unexpectedly found no overloaded servers" );
}
# Back off for a while
# Scale the sleep time by the number of connected threads, to produce a
@@ -341,7 +323,7 @@ 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 && $i !== false ) {
$this->mReadIndex = $i;
}
}
@@ -356,7 +338,7 @@ class LoadBalancer {
*/
function sleep( $t ) {
wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__.": waiting $t us\n" );
+ wfDebug( __METHOD__ . ": waiting $t us\n" );
usleep( $t );
wfProfileOut( __METHOD__ );
return $t;
@@ -390,7 +372,7 @@ class LoadBalancer {
wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
- $this->doWait( $i , true );
+ $this->doWait( $i, true );
}
wfProfileOut( __METHOD__ );
}
@@ -433,15 +415,15 @@ class LoadBalancer {
}
}
- wfDebug( __METHOD__.": Waiting for slave #$index to catch up...\n" );
+ wfDebug( __METHOD__ . ": Waiting for slave #$index to catch up...\n" );
$result = $conn->masterPosWait( $this->mWaitForPos, $this->mWaitTimeout );
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" );
+ wfDebug( __METHOD__ . ": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" );
return false;
} else {
- wfDebug( __METHOD__.": Done\n" );
+ wfDebug( __METHOD__ . ": Done\n" );
return true;
}
}
@@ -451,9 +433,10 @@ class LoadBalancer {
* This is the main entry point for this class.
*
* @param $i Integer: server index
- * @param $groups Array: query groups
- * @param $wiki String: wiki ID
+ * @param array $groups query groups
+ * @param bool|string $wiki Wiki ID
*
+ * @throws MWException
* @return DatabaseBase
*/
public function &getConnection( $i, $groups = array(), $wiki = false ) {
@@ -476,7 +459,7 @@ class LoadBalancer {
$groupIndex = $this->getReaderIndex( $groups, $wiki );
if ( $groupIndex !== false ) {
$serverName = $this->getServerName( $groupIndex );
- wfDebug( __METHOD__.": using server $serverName for group $groups\n" );
+ wfDebug( __METHOD__ . ": using server $serverName for group $groups\n" );
$i = $groupIndex;
}
} else {
@@ -484,7 +467,7 @@ class LoadBalancer {
$groupIndex = $this->getReaderIndex( $group, $wiki );
if ( $groupIndex !== false ) {
$serverName = $this->getServerName( $groupIndex );
- wfDebug( __METHOD__.": using server $serverName for group $group\n" );
+ wfDebug( __METHOD__ . ": using server $serverName for group $group\n" );
$i = $groupIndex;
break;
}
@@ -497,16 +480,16 @@ class LoadBalancer {
# Couldn't find a working server in getReaderIndex()?
if ( $i === false ) {
$this->mLastError = 'No working slave server: ' . $this->mLastError;
- $this->reportConnectionError( $this->mErrorConnection );
wfProfileOut( __METHOD__ );
- return false;
+ return $this->reportConnectionError();
}
}
# Now we have an explicit index into the servers array
$conn = $this->openConnection( $i, $wiki );
if ( !$conn ) {
- $this->reportConnectionError( $this->mErrorConnection );
+ wfProfileOut( __METHOD__ );
+ return $this->reportConnectionError();
}
wfProfileOut( __METHOD__ );
@@ -519,10 +502,11 @@ class LoadBalancer {
* the same number of times as getConnection() to work.
*
* @param DatabaseBase $conn
+ * @throws MWException
*/
public function reuseConnection( $conn ) {
- $serverIndex = $conn->getLBInfo('serverIndex');
- $refCount = $conn->getLBInfo('foreignPoolRefCount');
+ $serverIndex = $conn->getLBInfo( 'serverIndex' );
+ $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
$dbName = $conn->getDBname();
$prefix = $conn->tablePrefix();
if ( strval( $prefix ) !== '' ) {
@@ -531,7 +515,7 @@ class LoadBalancer {
$wiki = $dbName;
}
if ( $serverIndex === null || $refCount === null ) {
- wfDebug( __METHOD__.": this connection was not opened as a foreign connection\n" );
+ wfDebug( __METHOD__ . ": this connection was not opened as a foreign connection\n" );
/**
* This can happen in code like:
* foreach ( $dbs as $db ) {
@@ -545,15 +529,15 @@ class LoadBalancer {
return;
}
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 ) {
$this->mConns['foreignFree'][$serverIndex][$wiki] = $conn;
unset( $this->mConns['foreignUsed'][$serverIndex][$wiki] );
- wfDebug( __METHOD__.": freed connection $serverIndex/$wiki\n" );
+ wfDebug( __METHOD__ . ": freed connection $serverIndex/$wiki\n" );
} else {
- wfDebug( __METHOD__.": reference count for $serverIndex/$wiki reduced to $refCount\n" );
+ wfDebug( __METHOD__ . ": reference count for $serverIndex/$wiki reduced to $refCount\n" );
}
}
@@ -566,7 +550,7 @@ class LoadBalancer {
* error will be available via $this->mErrorConnection.
*
* @param $i Integer server index
- * @param $wiki String wiki ID to open
+ * @param string $wiki wiki ID to open
* @return DatabaseBase
*
* @access private
@@ -575,7 +559,7 @@ class LoadBalancer {
wfProfileIn( __METHOD__ );
if ( $wiki !== false ) {
$conn = $this->openForeignConnection( $i, $wiki );
- wfProfileOut( __METHOD__);
+ wfProfileOut( __METHOD__ );
return $conn;
}
if ( isset( $this->mConns['local'][$i][0] ) ) {
@@ -585,6 +569,7 @@ class LoadBalancer {
$server['serverIndex'] = $i;
$conn = $this->reallyOpenConnection( $server, false );
if ( $conn->isOpen() ) {
+ wfDebug( "Connected to database $i at {$this->mServers[$i]['host']}\n" );
$this->mConns['local'][$i][0] = $conn;
} else {
wfDebug( "Failed to connect to database $i at {$this->mServers[$i]['host']}\n" );
@@ -611,22 +596,22 @@ class LoadBalancer {
* error will be available via $this->mErrorConnection.
*
* @param $i Integer: server index
- * @param $wiki String: wiki ID to open
+ * @param string $wiki wiki ID to open
* @return DatabaseBase
*/
function openForeignConnection( $i, $wiki ) {
- wfProfileIn(__METHOD__);
+ wfProfileIn( __METHOD__ );
list( $dbName, $prefix ) = wfSplitWikiID( $wiki );
if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) {
// Reuse an already-used connection
$conn = $this->mConns['foreignUsed'][$i][$wiki];
- wfDebug( __METHOD__.": reusing connection $i/$wiki\n" );
+ wfDebug( __METHOD__ . ": reusing connection $i/$wiki\n" );
} elseif ( isset( $this->mConns['foreignFree'][$i][$wiki] ) ) {
// Reuse a free connection for the same wiki
$conn = $this->mConns['foreignFree'][$i][$wiki];
unset( $this->mConns['foreignFree'][$i][$wiki] );
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
- wfDebug( __METHOD__.": reusing free connection $i/$wiki\n" );
+ wfDebug( __METHOD__ . ": reusing free connection $i/$wiki\n" );
} elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
// Reuse a connection from another wiki
$conn = reset( $this->mConns['foreignFree'][$i] );
@@ -641,7 +626,7 @@ class LoadBalancer {
$conn->tablePrefix( $prefix );
unset( $this->mConns['foreignFree'][$i][$oldWiki] );
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
- wfDebug( __METHOD__.": reusing free connection from $oldWiki for $wiki\n" );
+ wfDebug( __METHOD__ . ": reusing free connection from $oldWiki for $wiki\n" );
}
} else {
// Open a new connection
@@ -650,13 +635,13 @@ class LoadBalancer {
$server['foreignPoolRefCount'] = 0;
$conn = $this->reallyOpenConnection( $server, $dbName );
if ( !$conn->isOpen() ) {
- wfDebug( __METHOD__.": error opening connection for $i/$wiki\n" );
+ wfDebug( __METHOD__ . ": error opening connection for $i/$wiki\n" );
$this->mErrorConnection = $conn;
$conn = false;
} else {
$conn->tablePrefix( $prefix );
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
- wfDebug( __METHOD__.": opened new connection for $i/$wiki\n" );
+ wfDebug( __METHOD__ . ": opened new connection for $i/$wiki\n" );
}
}
@@ -665,7 +650,7 @@ class LoadBalancer {
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
}
- wfProfileOut(__METHOD__);
+ wfProfileOut( __METHOD__ );
return $conn;
}
@@ -690,6 +675,7 @@ class LoadBalancer {
*
* @param $server
* @param $dbNameOverride bool
+ * @throws MWException
* @return DatabaseBase
*/
function reallyOpenConnection( $server, $dbNameOverride = false ) {
@@ -698,15 +684,11 @@ class LoadBalancer {
'See DefaultSettings.php entry for $wgDBservers.' );
}
- $host = $server['host'];
- $dbname = $server['dbname'];
-
if ( $dbNameOverride !== false ) {
- $server['dbname'] = $dbname = $dbNameOverride;
+ $server['dbname'] = $dbNameOverride;
}
# Create object
- wfDebug( "Connecting to $host $dbname...\n" );
try {
$db = DatabaseBase::factory( $server['type'], $server );
} catch ( DBConnectionError $e ) {
@@ -715,11 +697,6 @@ class LoadBalancer {
$db = $e->db;
}
- if ( $db->isOpen() ) {
- wfDebug( "Connected to $host $dbname.\n" );
- } else {
- wfDebug( "Connection failed to $host $dbname.\n" );
- }
$db->setLBInfo( $server );
if ( isset( $server['fakeSlaveLag'] ) ) {
$db->setFakeSlaveLag( $server['fakeSlaveLag'] );
@@ -731,24 +708,24 @@ class LoadBalancer {
}
/**
- * @param $conn
* @throws DBConnectionError
+ * @return bool
*/
- function reportConnectionError( &$conn ) {
- wfProfileIn( __METHOD__ );
+ private function reportConnectionError() {
+ $conn = $this->mErrorConnection; // The connection which caused the error
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" );
- $conn = new Database;
+
// If all servers were busy, mLastError will contain something sensible
- throw new DBConnectionError( $conn, $this->mLastError );
+ throw new DBConnectionError( null, $this->mLastError );
} else {
$server = $conn->getProperty( 'mServer' );
wfLogDBError( "Connection error: {$this->mLastError} ({$server})\n" );
- $conn->reportConnectionError( "{$this->mLastError} ({$server})" );
+ $conn->reportConnectionError( "{$this->mLastError} ({$server})" ); // throws DBConnectionError
}
- wfProfileOut( __METHOD__ );
+ return false; /* not reached */
}
/**
@@ -909,7 +886,9 @@ class LoadBalancer {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
foreach ( $conns3 as $conn ) {
- $conn->commit( __METHOD__ );
+ if ( $conn->trxLevel() ) {
+ $conn->commit( __METHOD__, 'flush' );
+ }
}
}
}
@@ -926,8 +905,8 @@ class LoadBalancer {
continue;
}
foreach ( $conns2[$masterIndex] as $conn ) {
- if ( $conn->writesOrCallbacksPending() ) {
- $conn->commit( __METHOD__ );
+ if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
+ $conn->commit( __METHOD__, 'flush' );
}
}
}
@@ -954,10 +933,11 @@ class LoadBalancer {
* @return bool
*/
function allowLagged( $mode = null ) {
- if ( $mode === null) {
+ if ( $mode === null ) {
return $this->mAllowLagged;
}
$this->mAllowLagged = $mode;
+ return $this->mAllowLagged;
}
/**
@@ -999,7 +979,7 @@ 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 $wiki string Wiki ID, or false for the default database
+ * @param string $wiki Wiki ID, or false for the default database
*
* @return array ( host, max lag, index of max lagged host )
*/
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 146ac61e..ad7b3b2f 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -37,7 +37,7 @@ interface LoadMonitor {
/**
* Perform pre-connection load ratio adjustment.
* @param $loads array
- * @param $group String: the selected query group
+ * @param string $group the selected query group
* @param $wiki String
*/
function scaleLoads( &$loads, $group = false, $wiki = false );
@@ -159,7 +159,7 @@ class LoadMonitor_MySQL implements LoadMonitor {
$times = array();
foreach ( $serverIndexes as $i ) {
- if ($i == 0) { # Master
+ if ( $i == 0 ) { # Master
$times[$i] = 0;
} elseif ( false !== ( $conn = $this->parent->getAnyOpenConnection( $i ) ) ) {
$times[$i] = $conn->getLag();
@@ -173,7 +173,7 @@ class LoadMonitor_MySQL implements LoadMonitor {
$wgMemc->set( $memcKey, $times, $expiry );
# But don't give the timestamp to the caller
- unset($times['timestamp']);
+ unset( $times['timestamp'] );
$lagTimes = $times;
wfProfileOut( __METHOD__ );
@@ -189,7 +189,7 @@ class LoadMonitor_MySQL implements LoadMonitor {
if ( !$threshold ) {
return 0;
}
- $status = $conn->getMysqlStatus("Thread%");
+ $status = $conn->getMysqlStatus( "Thread%" );
if ( $status['Threads_running'] > $threshold ) {
$server = $conn->getProperty( 'mServer' );
wfLogDBError( "LB backoff from $server - Threads_running = {$status['Threads_running']}\n" );
@@ -199,4 +199,3 @@ class LoadMonitor_MySQL implements LoadMonitor {
}
}
}
-
diff --git a/includes/db/ORMIterator.php b/includes/db/ORMIterator.php
index 090b8932..077eab0f 100644
--- a/includes/db/ORMIterator.php
+++ b/includes/db/ORMIterator.php
@@ -23,9 +23,9 @@
* @file
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
interface ORMIterator extends Iterator {
-} \ No newline at end of file
+}
diff --git a/includes/db/ORMResult.php b/includes/db/ORMResult.php
index 2a5837a1..160033c4 100644
--- a/includes/db/ORMResult.php
+++ b/includes/db/ORMResult.php
@@ -25,7 +25,7 @@
* @file ORMResult.php
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
diff --git a/includes/db/ORMRow.php b/includes/db/ORMRow.php
index 303f3a20..6c1f27ff 100644
--- a/includes/db/ORMRow.php
+++ b/includes/db/ORMRow.php
@@ -27,11 +27,11 @@
* @file ORMRow.php
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-abstract class ORMRow implements IORMRow {
+class ORMRow implements IORMRow {
/**
* The fields of the object.
@@ -120,7 +120,8 @@ abstract class ORMRow implements IORMRow {
$result = $this->table->rawSelectRow(
$this->table->getPrefixedFields( $fields ),
array( $this->table->getPrefixedField( 'id' ) => $this->getId() ),
- array( 'LIMIT' => 1 )
+ array( 'LIMIT' => 1 ),
+ __METHOD__
);
if ( $result !== false ) {
@@ -138,8 +139,9 @@ abstract class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @param string $name
- * @param mixed $default
+ * @param string $name Field name
+ * @param $default mixed: Default value to return when none is found
+ * (default: null)
*
* @throws MWException
* @return mixed
@@ -159,7 +161,7 @@ abstract class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @param string$name
+ * @param $name string
*
* @return mixed
*/
@@ -259,11 +261,18 @@ abstract class ORMRow implements IORMRow {
if ( array_key_exists( $name, $this->fields ) ) {
$value = $this->fields[$name];
+ // Skip null id fields so that the DBMS can set the default.
+ if ( $name === 'id' && is_null ( $value ) ) {
+ continue;
+ }
+
switch ( $type ) {
case 'array':
$value = (array)$value;
+ // fall-through!
case 'blob':
$value = serialize( $value );
+ // fall-through!
}
$values[$this->table->getPrefixedField( $name )] = $value;
@@ -346,7 +355,7 @@ abstract class ORMRow implements IORMRow {
* @return boolean Success indicator
*/
protected function saveExisting( $functionName = null ) {
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->table->getWriteDbConnection();
$success = $dbw->update(
$this->table->getName(),
@@ -355,6 +364,8 @@ abstract class ORMRow implements IORMRow {
is_null( $functionName ) ? __METHOD__ : $functionName
);
+ $this->table->releaseConnection( $dbw );
+
// DatabaseBase::update does not always return true for success as documented...
return $success !== false;
}
@@ -382,13 +393,13 @@ abstract class ORMRow implements IORMRow {
* @return boolean Success indicator
*/
protected function insert( $functionName = null, array $options = null ) {
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->table->getWriteDbConnection();
$success = $dbw->insert(
$this->table->getName(),
$this->getWriteValues(),
is_null( $functionName ) ? __METHOD__ : $functionName,
- is_null( $options ) ? array( 'IGNORE' ) : $options
+ $options
);
// DatabaseBase::insert does not always return true for success as documented...
@@ -398,6 +409,8 @@ abstract class ORMRow implements IORMRow {
$this->setField( 'id', $dbw->insertId() );
}
+ $this->table->releaseConnection( $dbw );
+
return $success;
}
@@ -411,7 +424,7 @@ abstract class ORMRow implements IORMRow {
public function remove() {
$this->beforeRemove();
- $success = $this->table->delete( array( 'id' => $this->getId() ) );
+ $success = $this->table->delete( array( 'id' => $this->getId() ), __METHOD__ );
// DatabaseBase::delete does not always return true for success as documented...
$success = $success !== false;
@@ -446,8 +459,8 @@ abstract class ORMRow implements IORMRow {
}
/**
- * Gets called after successfull removal.
- * Can be overriden to get rid of linked data.
+ * Gets called after successful removal.
+ * Can be overridden to get rid of linked data.
*
* @since 1.20
*/
@@ -501,11 +514,7 @@ abstract class ORMRow implements IORMRow {
$value = (float)$value;
break;
case 'bool':
- if ( is_string( $value ) ) {
- $value = $value !== '0';
- } elseif ( is_int( $value ) ) {
- $value = $value !== 0;
- }
+ $value = (bool)$value;
break;
case 'array':
if ( is_string( $value ) ) {
@@ -557,7 +566,7 @@ abstract class ORMRow implements IORMRow {
$absoluteAmount = abs( $amount );
$isNegative = $amount < 0;
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->table->getWriteDbConnection();
$fullField = $this->table->getPrefixedField( $field );
@@ -572,6 +581,8 @@ abstract class ORMRow implements IORMRow {
$this->setField( $field, $this->getField( $field ) + $amount );
}
+ $this->table->releaseConnection( $dbw );
+
return $success;
}
diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php
index a77074ff..bcbe94a3 100644
--- a/includes/db/ORMTable.php
+++ b/includes/db/ORMTable.php
@@ -19,43 +19,150 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @since 1.20
+ * Non-abstract since 1.21
*
* @file ORMTable.php
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-abstract class ORMTable implements IORMTable {
+class ORMTable extends DBAccessBase implements IORMTable {
/**
- * Gets the db field prefix.
+ * Cache for instances, used by the singleton method.
*
* @since 1.20
+ * @deprecated since 1.21
*
- * @return string
+ * @var ORMTable[]
*/
- protected abstract function getFieldPrefix();
+ protected static $instanceCache = array();
/**
- * Cache for instances, used by the singleton method.
+ * @since 1.21
*
- * @since 1.20
- * @var array of DBTable
+ * @var string
*/
- protected static $instanceCache = array();
+ protected $tableName;
+
+ /**
+ * @since 1.21
+ *
+ * @var string[]
+ */
+ protected $fields = array();
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $fieldPrefix = '';
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $rowClass = 'ORMRow';
/**
- * The database connection to use for read operations.
+ * @since 1.21
+ *
+ * @var array
+ */
+ protected $defaults = array();
+
+ /**
+ * ID of the database connection to use for read operations.
* Can be changed via @see setReadDb.
*
* @since 1.20
+ *
* @var integer DB_ enum
*/
protected $readDb = DB_SLAVE;
/**
+ * Constructor.
+ *
+ * @since 1.21
+ *
+ * @param string $tableName
+ * @param string[] $fields
+ * @param array $defaults
+ * @param string|null $rowClass
+ * @param string $fieldPrefix
+ */
+ public function __construct( $tableName = '', array $fields = array(), array $defaults = array(), $rowClass = null, $fieldPrefix = '' ) {
+ $this->tableName = $tableName;
+ $this->fields = $fields;
+ $this->defaults = $defaults;
+
+ if ( is_string( $rowClass ) ) {
+ $this->rowClass = $rowClass;
+ }
+
+ $this->fieldPrefix = $fieldPrefix;
+ }
+
+ /**
+ * @see IORMTable::getName
+ *
+ * @since 1.21
+ *
+ * @return string
+ * @throws MWException
+ */
+ public function getName() {
+ if ( $this->tableName === '' ) {
+ throw new MWException( 'The table name needs to be set' );
+ }
+
+ return $this->tableName;
+ }
+
+ /**
+ * Gets the db field prefix.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ protected function getFieldPrefix() {
+ return $this->fieldPrefix;
+ }
+
+ /**
+ * @see IORMTable::getRowClass
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getRowClass() {
+ return $this->rowClass;
+ }
+
+ /**
+ * @see ORMTable::getFields
+ *
+ * @since 1.21
+ *
+ * @return array
+ * @throws MWException
+ */
+ public function getFields() {
+ if ( $this->fields === array() ) {
+ throw new MWException( 'The table needs to have one or more fields' );
+ }
+
+ return $this->fields;
+ }
+
+ /**
* Returns a list of default field values.
* field name => field value
*
@@ -64,7 +171,7 @@ abstract class ORMTable implements IORMTable {
* @return array
*/
public function getDefaults() {
- return array();
+ return $this->defaults;
}
/**
@@ -94,8 +201,9 @@ abstract class ORMTable implements IORMTable {
* @return ORMResult
*/
public function select( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
- return new ORMResult( $this, $this->rawSelect( $fields, $conditions, $options, $functionName ) );
+ array $options = array(), $functionName = null ) {
+ $res = $this->rawSelect( $fields, $conditions, $options, $functionName );
+ return new ORMResult( $this, $res );
}
/**
@@ -109,10 +217,11 @@ abstract class ORMTable implements IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return array of self
+ * @return 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();
@@ -130,14 +239,15 @@ abstract class ORMTable 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).
*/
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() );
}
@@ -145,13 +255,39 @@ abstract class ORMTable implements IORMTable {
$fields = (array)$fields;
}
- return wfGetDB( $this->getReadDb() )->select(
+ $dbr = $this->getReadDbConnection();
+ $result = $dbr->select(
$this->getName(),
$this->getPrefixedFields( $fields ),
$this->getPrefixedValues( $conditions ),
is_null( $functionName ) ? __METHOD__ : $functionName,
$options
);
+
+ /* @var Exception $error */
+ $error = null;
+
+ if ( $result === false ) {
+ // Database connection was in "ignoreErrors" mode. We don't like that.
+ // So, we emulate the DBQueryError that should have been thrown.
+ $error = new DBQueryError(
+ $dbr,
+ $dbr->lastError(),
+ $dbr->lastErrno(),
+ $dbr->lastQuery(),
+ is_null( $functionName ) ? __METHOD__ : $functionName
+ );
+ }
+
+ $this->releaseConnection( $dbr );
+
+ if ( $error ) {
+ // Note: construct the error before releasing the connection,
+ // but throw it after.
+ throw $error;
+ }
+
+ return $result;
}
/**
@@ -177,7 +313,7 @@ abstract class ORMTable implements IORMTable {
* @return 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 );
@@ -223,7 +359,7 @@ abstract class ORMTable implements IORMTable {
$objects = $this->select( $fields, $conditions, $options, $functionName );
- return $objects->isEmpty() ? false : $objects->current();
+ return ( !$objects || $objects->isEmpty() ) ? false : $objects->current();
}
/**
@@ -241,15 +377,18 @@ abstract class ORMTable implements IORMTable {
*/
public function rawSelectRow( array $fields, array $conditions = array(),
array $options = array(), $functionName = null ) {
- $dbr = wfGetDB( $this->getReadDb() );
+ $dbr = $this->getReadDbConnection();
- return $dbr->selectRow(
+ $result = $dbr->selectRow(
$this->getName(),
$fields,
$conditions,
is_null( $functionName ) ? __METHOD__ : $functionName,
$options
);
+
+ $this->releaseConnection( $dbr );
+ return $result;
}
/**
@@ -293,6 +432,21 @@ abstract class ORMTable implements IORMTable {
}
/**
+ * Checks if the table exists
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function exists() {
+ $dbr = $this->getReadDbConnection();
+ $exists = $dbr->tableExists( $this->getName() );
+ $this->releaseConnection( $dbr );
+
+ return $exists;
+ }
+
+ /**
* Returns the amount of matching records.
* Condition field names get prefixed.
*
@@ -310,7 +464,8 @@ abstract class ORMTable implements IORMTable {
$res = $this->rawSelectRow(
array( 'rowcount' => 'COUNT(*)' ),
$this->getPrefixedValues( $conditions ),
- $options
+ $options,
+ __METHOD__
);
return $res->rowcount;
@@ -327,13 +482,18 @@ abstract class ORMTable implements IORMTable {
* @return boolean Success indicator
*/
public function delete( array $conditions, $functionName = null ) {
- return wfGetDB( DB_MASTER )->delete(
+ $dbw = $this->getWriteDbConnection();
+
+ $result = $dbw->delete(
$this->getName(),
$conditions === array() ? '*' : $this->getPrefixedValues( $conditions ),
- $functionName
+ is_null( $functionName ) ? __METHOD__ : $functionName
) !== false; // DatabaseBase::delete does not always return true for success as documented...
+
+ $this->releaseConnection( $dbw );
+ return $result;
}
-
+
/**
* Get API parameters for the fields supported by this object.
*
@@ -397,7 +557,7 @@ abstract class ORMTable implements IORMTable {
}
/**
- * Get the database type used for read operations.
+ * Get the database ID used for read operations.
*
* @since 1.20
*
@@ -408,7 +568,7 @@ abstract class ORMTable implements IORMTable {
}
/**
- * Set the database type to use for read operations.
+ * Set the database ID to use for read operations, use DB_XXX constants or an index to the load balancer setup.
*
* @param integer $db
*
@@ -419,6 +579,70 @@ abstract class ORMTable implements IORMTable {
}
/**
+ * Get the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @since 1.20
+ *
+ * @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;
+ }
+
+ /**
+ * 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)
+ *
+ * @since 1.20
+ */
+ public function setTargetWiki( $wiki ) {
+ $this->wiki = $wiki;
+ }
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getReadDbConnection() {
+ return $this->getConnection( $this->getReadDb(), array() );
+ }
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getWriteDbConnection() {
+ return $this->getConnection( DB_MASTER, array() );
+ }
+
+ /**
+ * Releases the lease on the given database connection. This is useful mainly
+ * for connections to a foreign wiki. It does nothing for connections to the local wiki.
+ *
+ * @see LoadBalancer::reuseConnection
+ *
+ * @param DatabaseBase $db the database
+ *
+ * @since 1.20
+ */
+ public function releaseConnection( DatabaseBase $db ) {
+ parent::releaseConnection( $db ); // just make it public
+ }
+
+ /**
* Update the records matching the provided conditions by
* setting the fields that are keys in the $values param to
* their corresponding values.
@@ -431,14 +655,17 @@ abstract class ORMTable implements IORMTable {
* @return boolean Success indicator
*/
public function update( array $values, array $conditions = array() ) {
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->getWriteDbConnection();
- return $dbw->update(
+ $result = $dbw->update(
$this->getName(),
$this->getPrefixedValues( $values ),
$this->getPrefixedValues( $conditions ),
__METHOD__
) !== false; // DatabaseBase::update does not always return true for success as documented...
+
+ $this->releaseConnection( $dbw );
+ return $result;
}
/**
@@ -450,6 +677,7 @@ abstract class ORMTable implements IORMTable {
* @param array $conditions
*/
public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) {
+ $slave = $this->getReadDb();
$this->setReadDb( DB_MASTER );
/**
@@ -461,7 +689,7 @@ abstract class ORMTable implements IORMTable {
$item->save();
}
- $this->setReadDb( DB_SLAVE );
+ $this->setReadDb( $slave );
}
/**
@@ -559,6 +787,7 @@ abstract class ORMTable implements IORMTable {
* Get an instance of this class.
*
* @since 1.20
+ * @deprecated since 1.21
*
* @return IORMTable
*/