summaryrefslogtreecommitdiff
path: root/includes/db
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2013-01-18 16:46:04 +0100
committerPierre Schmitz <pierre@archlinux.de>2013-01-18 16:46:04 +0100
commit63601400e476c6cf43d985f3e7b9864681695ed4 (patch)
treef7846203a952e38aaf66989d0a4702779f549962 /includes/db
parent8ff01378c9e0207f9169b81966a51def645b6a51 (diff)
Update to MediaWiki 1.20.2
this update includes: * adjusted Arch Linux skin * updated FluxBBAuthPlugin * patch for https://bugzilla.wikimedia.org/show_bug.cgi?id=44024
Diffstat (limited to 'includes/db')
-rw-r--r--includes/db/CloneDatabase.php1
-rw-r--r--includes/db/Database.php701
-rw-r--r--includes/db/DatabaseError.php27
-rw-r--r--includes/db/DatabaseIbm_db2.php165
-rw-r--r--includes/db/DatabaseMssql.php53
-rw-r--r--includes/db/DatabaseMysql.php159
-rw-r--r--includes/db/DatabaseOracle.php48
-rw-r--r--includes/db/DatabasePostgres.php622
-rw-r--r--includes/db/DatabaseSqlite.php80
-rw-r--r--includes/db/DatabaseUtility.php28
-rw-r--r--includes/db/IORMRow.php275
-rw-r--r--includes/db/IORMTable.php448
-rw-r--r--includes/db/LBFactory.php29
-rw-r--r--includes/db/LBFactory_Multi.php17
-rw-r--r--includes/db/LBFactory_Single.php21
-rw-r--r--includes/db/LoadBalancer.php25
-rw-r--r--includes/db/LoadMonitor.php17
-rw-r--r--includes/db/ORMIterator.php31
-rw-r--r--includes/db/ORMResult.php123
-rw-r--r--includes/db/ORMRow.php663
-rw-r--r--includes/db/ORMTable.php675
21 files changed, 3558 insertions, 650 deletions
diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
index bd0895cf..4e43642f 100644
--- a/includes/db/CloneDatabase.php
+++ b/includes/db/CloneDatabase.php
@@ -20,6 +20,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
+ * @file
* @ingroup Database
*/
diff --git a/includes/db/Database.php b/includes/db/Database.php
index f3e84675..5f10b97d 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -2,10 +2,26 @@
/**
* @defgroup Database Database
*
+ * This file deals with database interface functions
+ * and query specifics/optimisations.
+ *
+ * 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
- * This file deals with database interface functions
- * and query specifics/optimisations
*/
/** Number of times to re-try an operation in case of deadlock */
@@ -209,12 +225,15 @@ abstract class DatabaseBase implements DatabaseType {
protected $mServer, $mUser, $mPassword, $mDBname;
- /**
- * @var DatabaseBase
- */
protected $mConn = null;
protected $mOpened = false;
+ /**
+ * @since 1.20
+ * @var array of Closure
+ */
+ protected $mTrxIdleCallbacks = array();
+
protected $mTablePrefix;
protected $mFlags;
protected $mTrxLevel = 0;
@@ -253,9 +272,9 @@ abstract class DatabaseBase implements DatabaseType {
* - false to disable debugging
* - omitted or null to do nothing
*
- * @return The previous value of the flag
+ * @return bool|null previous value of the flag
*/
- function debug( $debug = null ) {
+ public function debug( $debug = null ) {
return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
}
@@ -279,9 +298,9 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $buffer null|bool
*
- * @return The previous value of the flag
+ * @return null|bool The previous value of the flag
*/
- function bufferResults( $buffer = null ) {
+ public function bufferResults( $buffer = null ) {
if ( is_null( $buffer ) ) {
return !(bool)( $this->mFlags & DBO_NOBUFFER );
} else {
@@ -300,7 +319,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool The previous value of the flag.
*/
- function ignoreErrors( $ignoreErrors = null ) {
+ public function ignoreErrors( $ignoreErrors = null ) {
return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
}
@@ -310,28 +329,28 @@ 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 An integer (0 or 1), or omitted to leave it unchanged.
- * @return The previous value
+ * @param $level int An integer (0 or 1), or omitted to leave it unchanged.
+ * @return int The previous value
*/
- function trxLevel( $level = null ) {
+ public function trxLevel( $level = null ) {
return wfSetVar( $this->mTrxLevel, $level );
}
/**
* Get/set the number of errors logged. Only useful when errors are ignored
- * @param $count The count to set, or omitted to leave it unchanged.
- * @return The error count
+ * @param $count int The count to set, or omitted to leave it unchanged.
+ * @return int The error count
*/
- function errorCount( $count = null ) {
+ public function errorCount( $count = null ) {
return wfSetVar( $this->mErrorCount, $count );
}
/**
* Get/set the table prefix.
- * @param $prefix The table prefix to set, or omitted to leave it unchanged.
- * @return The previous table prefix.
+ * @param $prefix string The table prefix to set, or omitted to leave it unchanged.
+ * @return string The previous table prefix.
*/
- function tablePrefix( $prefix = null ) {
+ public function tablePrefix( $prefix = null ) {
return wfSetVar( $this->mTablePrefix, $prefix );
}
@@ -344,7 +363,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return LoadBalancer|null
*/
- function getLBInfo( $name = null ) {
+ public function getLBInfo( $name = null ) {
if ( is_null( $name ) ) {
return $this->mLBInfo;
} else {
@@ -364,7 +383,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $name
* @param $value
*/
- function setLBInfo( $name, $value = null ) {
+ public function setLBInfo( $name, $value = null ) {
if ( is_null( $value ) ) {
$this->mLBInfo = $name;
} else {
@@ -377,7 +396,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $lag int
*/
- function setFakeSlaveLag( $lag ) {
+ public function setFakeSlaveLag( $lag ) {
$this->mFakeSlaveLag = $lag;
}
@@ -386,7 +405,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $enabled bool
*/
- function setFakeMaster( $enabled = true ) {
+ public function setFakeMaster( $enabled = true ) {
$this->mFakeMaster = $enabled;
}
@@ -395,7 +414,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function cascadingDeletes() {
+ public function cascadingDeletes() {
return false;
}
@@ -404,7 +423,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function cleanupTriggers() {
+ public function cleanupTriggers() {
return false;
}
@@ -414,7 +433,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function strictIPs() {
+ public function strictIPs() {
return false;
}
@@ -423,7 +442,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function realTimestamps() {
+ public function realTimestamps() {
return false;
}
@@ -432,7 +451,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function implicitGroupby() {
+ public function implicitGroupby() {
return true;
}
@@ -442,17 +461,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function implicitOrderby() {
- return true;
- }
-
- /**
- * Returns true if this database requires that SELECT DISTINCT queries require that all
- ORDER BY expressions occur in the SELECT list per the SQL92 standard
- *
- * @return bool
- */
- function standardSelectDistinct() {
+ public function implicitOrderby() {
return true;
}
@@ -462,7 +471,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function searchableIPs() {
+ public function searchableIPs() {
return false;
}
@@ -471,7 +480,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function functionalIndexes() {
+ public function functionalIndexes() {
return false;
}
@@ -479,7 +488,7 @@ abstract class DatabaseBase implements DatabaseType {
* Return the last query that went through DatabaseBase::query()
* @return String
*/
- function lastQuery() {
+ public function lastQuery() {
return $this->mLastQuery;
}
@@ -489,15 +498,25 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function doneWrites() {
+ public function doneWrites() {
return $this->mDoneWrites;
}
/**
+ * Returns true if there is a transaction open with possible write
+ * queries or transaction idle callbacks waiting on it to finish.
+ *
+ * @return bool
+ */
+ public function writesOrCallbacksPending() {
+ return $this->mTrxLevel && ( $this->mDoneWrites || $this->mTrxIdleCallbacks );
+ }
+
+ /**
* Is a connection to the database open?
* @return Boolean
*/
- function isOpen() {
+ public function isOpen() {
return $this->mOpened;
}
@@ -513,8 +532,12 @@ abstract class DatabaseBase implements DatabaseType {
* and removes it in command line mode
* - DBO_PERSISTENT: use persistant database connection
*/
- function setFlag( $flag ) {
+ public function setFlag( $flag ) {
+ global $wgDebugDBTransactions;
$this->mFlags |= $flag;
+ if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) {
+ wfDebug("Implicit transactions are now disabled.\n");
+ }
}
/**
@@ -522,8 +545,12 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $flag: same as setFlag()'s $flag param
*/
- function clearFlag( $flag ) {
+ public function clearFlag( $flag ) {
+ global $wgDebugDBTransactions;
$this->mFlags &= ~$flag;
+ if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
+ wfDebug("Implicit transactions are now disabled.\n");
+ }
}
/**
@@ -532,7 +559,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $flag: same as setFlag()'s $flag param
* @return Boolean
*/
- function getFlag( $flag ) {
+ public function getFlag( $flag ) {
return !!( $this->mFlags & $flag );
}
@@ -543,14 +570,14 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return string
*/
- function getProperty( $name ) {
+ public function getProperty( $name ) {
return $this->$name;
}
/**
* @return string
*/
- function getWikiID() {
+ public function getWikiID() {
if ( $this->mTablePrefix ) {
return "{$this->mDBname}-{$this->mTablePrefix}";
} else {
@@ -588,15 +615,21 @@ abstract class DatabaseBase implements DatabaseType {
function __construct( $server = false, $user = false, $password = false, $dbName = false,
$flags = 0, $tablePrefix = 'get from global'
) {
- global $wgDBprefix, $wgCommandLineMode;
+ global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions;
$this->mFlags = $flags;
if ( $this->mFlags & DBO_DEFAULT ) {
if ( $wgCommandLineMode ) {
$this->mFlags &= ~DBO_TRX;
+ if ( $wgDebugDBTransactions ) {
+ wfDebug("Implicit transaction open disabled.\n");
+ }
} else {
$this->mFlags |= DBO_TRX;
+ if ( $wgDebugDBTransactions ) {
+ wfDebug("Implicit transaction open enabled.\n");
+ }
}
}
@@ -622,35 +655,6 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Same as new DatabaseMysql( ... ), kept for backward compatibility
- * @deprecated since 1.17
- *
- * @param $server
- * @param $user
- * @param $password
- * @param $dbName
- * @param $flags int
- * @return DatabaseMysql
- */
- static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
- wfDeprecated( __METHOD__, '1.17' );
- return new DatabaseMysql( $server, $user, $password, $dbName, $flags );
- }
-
- /**
- * Same as new factory( ... ), kept for backward compatibility
- * @deprecated since 1.18
- * @see Database::factory()
- */
- public final static function newFromType( $dbType, $p = array() ) {
- wfDeprecated( __METHOD__, '1.18' );
- if ( isset( $p['tableprefix'] ) ) {
- $p['tablePrefix'] = $p['tableprefix'];
- }
- return self::factory( $dbType, $p );
- }
-
- /**
* Given a DB type, construct the name of the appropriate child class of
* DatabaseBase. This is designed to replace all of the manual stuff like:
* $class = 'Database' . ucfirst( strtolower( $type ) );
@@ -665,6 +669,8 @@ abstract class DatabaseBase implements DatabaseType {
* @see ForeignDBRepo::getMasterDB()
* @see WebInstaller_DBConnect::execute()
*
+ * @since 1.18
+ *
* @param $dbType String A possible DB type
* @param $p Array An array of options to pass to the constructor.
* Valid options are: host, user, password, dbname, flags, tablePrefix
@@ -707,7 +713,7 @@ abstract class DatabaseBase implements DatabaseType {
}
if ( $this->mPHPError ) {
$error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
- $error = preg_replace( '!^.*?:(.*)$!', '$1', $error );
+ $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
return $error;
} else {
return false;
@@ -728,12 +734,31 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return Bool operation success. true if already closed.
*/
- function close() {
- # Stub, should probably be overridden
- return true;
+ public function close() {
+ if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
+ throw new MWException( "Transaction idle callbacks still pending." );
+ }
+ $this->mOpened = false;
+ if ( $this->mConn ) {
+ if ( $this->trxLevel() ) {
+ $this->commit( __METHOD__ );
+ }
+ $ret = $this->closeConnection();
+ $this->mConn = false;
+ return $ret;
+ } else {
+ return true;
+ }
}
/**
+ * Closes underlying database connection
+ * @since 1.20
+ * @return bool: Whether connection was closed successfully
+ */
+ protected abstract function closeConnection();
+
+ /**
* @param $error String: fallback error message, used if none is given by DB
*/
function reportConnectionError( $error = 'Unknown error' ) {
@@ -762,8 +787,8 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function isWriteQuery( $sql ) {
- return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW|\(SELECT)\b/i', $sql );
+ public function isWriteQuery( $sql ) {
+ return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
}
/**
@@ -833,8 +858,13 @@ abstract class DatabaseBase implements DatabaseType {
# 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 )
+ if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
+ global $wgDebugDBTransactions;
+ if ( $wgDebugDBTransactions ) {
+ wfDebug("Implicit transaction start.\n");
+ }
$this->begin( __METHOD__ . " ($fname)" );
+ }
}
if ( $this->debug() ) {
@@ -863,6 +893,7 @@ abstract class DatabaseBase implements DatabaseType {
if ( false === $ret && $this->wasErrorReissuable() ) {
# Transaction is gone, like it or not
$this->mTrxLevel = 0;
+ $this->mTrxIdleCallbacks = array(); // cancel
wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->ping() ) {
@@ -903,7 +934,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fname String
* @param $tempIgnore Boolean
*/
- function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+ public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
# Ignore errors during error handling to avoid infinite recursion
$ignore = $this->ignoreErrors( true );
++$this->mErrorCount;
@@ -928,16 +959,12 @@ abstract class DatabaseBase implements DatabaseType {
* & = filename; reads the file and inserts as a blob
* (we don't use this though...)
*
- * This function should not be used directly by new code outside of the
- * database classes. The query wrapper functions (select() etc.) should be
- * used instead.
- *
* @param $sql string
* @param $func string
*
* @return array
*/
- function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
+ protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
/* MySQL doesn't support prepared statements (yet), so just
pack up the query for reference. We'll manually replace
the bits later. */
@@ -948,7 +975,7 @@ abstract class DatabaseBase implements DatabaseType {
* Free a prepared query, generated by prepare().
* @param $prepared
*/
- function freePrepared( $prepared ) {
+ protected function freePrepared( $prepared ) {
/* No-op by default */
}
@@ -959,7 +986,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return ResultWrapper
*/
- function execute( $prepared, $args = null ) {
+ public function execute( $prepared, $args = null ) {
if ( !is_array( $args ) ) {
# Pull the var args
$args = func_get_args();
@@ -972,41 +999,13 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Prepare & execute an SQL statement, quoting and inserting arguments
- * in the appropriate places.
+ * For faking prepared SQL statements on DBs that don't support it directly.
*
- * This function should not be used directly by new code outside of the
- * database classes. The query wrapper functions (select() etc.) should be
- * used instead.
- *
- * @param $query String
- * @param $args ...
- *
- * @return ResultWrapper
- */
- function safeQuery( $query, $args = null ) {
- $prepared = $this->prepare( $query, 'DatabaseBase::safeQuery' );
-
- if ( !is_array( $args ) ) {
- # Pull the var args
- $args = func_get_args();
- array_shift( $args );
- }
-
- $retval = $this->execute( $prepared, $args );
- $this->freePrepared( $prepared );
-
- return $retval;
- }
-
- /**
- * 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 SQL
*/
- function fillPrepared( $preparedQuery, $args ) {
+ public function fillPrepared( $preparedQuery, $args ) {
reset( $args );
$this->preparedArgs =& $args;
@@ -1022,7 +1021,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $matches Array
* @return String
*/
- function fillPreparedArg( $matches ) {
+ protected function fillPreparedArg( $matches ) {
switch( $matches[1] ) {
case '\\?': return '?';
case '\\!': return '!';
@@ -1049,32 +1048,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $res Mixed: A SQL result
*/
- function freeResult( $res ) {
- }
-
- /**
- * Simple UPDATE wrapper.
- * Usually throws a DBQueryError on failure.
- * If errors are explicitly ignored, returns success
- *
- * This function exists for historical reasons, DatabaseBase::update() has a more standard
- * calling convention and feature set
- *
- * @param $table string
- * @param $var
- * @param $value
- * @param $cond
- * @param $fname string
- *
- * @return bool
- */
- function set( $table, $var, $value, $cond, $fname = 'DatabaseBase::set' ) {
- $table = $this->tableName( $table );
- $sql = "UPDATE $table SET $var = '" .
- $this->strencode( $value ) . "' WHERE ($cond)";
-
- return (bool)$this->query( $sql, $fname );
- }
+ public function freeResult( $res ) {}
/**
* A SELECT wrapper which returns a single field from a single result row.
@@ -1091,9 +1065,9 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fname string The function name of the caller.
* @param $options string|array The query options. See DatabaseBase::select() for details.
*
- * @return false|mixed The value from the field, or false on failure.
+ * @return bool|mixed The value from the field, or false on failure.
*/
- function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
+ public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
$options = array() )
{
if ( !is_array( $options ) ) {
@@ -1126,7 +1100,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return Array
* @see DatabaseBase::select()
*/
- function makeSelectOptions( $options ) {
+ public function makeSelectOptions( $options ) {
$preLimitTail = $postLimitTail = '';
$startOpts = '';
@@ -1146,7 +1120,10 @@ abstract class DatabaseBase implements DatabaseType {
}
if ( isset( $options['HAVING'] ) ) {
- $preLimitTail .= " HAVING {$options['HAVING']}";
+ $having = is_array( $options['HAVING'] )
+ ? $this->makeList( $options['HAVING'], LIST_AND )
+ : $options['HAVING'];
+ $preLimitTail .= " HAVING {$having}";
}
if ( isset( $options['ORDER BY'] ) ) {
@@ -1245,10 +1222,12 @@ abstract class DatabaseBase implements DatabaseType {
* @param $vars string|array
*
* May be either a field name or an array of field names. The field names
- * here are complete fragments of SQL, for direct inclusion into the SELECT
- * query. Expressions and aliases may be specified as in SQL, for example:
+ * can be complete fragments of SQL, for direct inclusion into the SELECT
+ * query. If an array is given, field aliases can be specified, for example:
+ *
+ * array( 'maxrev' => 'MAX(rev_id)' )
*
- * array( 'MAX(rev_id) AS maxrev' )
+ * This includes an expression with the alias "maxrev" in the query.
*
* If an expression is given, care must be taken to ensure that it is
* DBMS-independent.
@@ -1305,7 +1284,9 @@ abstract class DatabaseBase implements DatabaseType {
* - GROUP BY: May be either an SQL fragment string naming a field or
* expression to group by, or an array of such SQL fragments.
*
- * - HAVING: A string containing a HAVING clause.
+ * - HAVING: May be either an string containing a HAVING clause or an array of
+ * conditions building the HAVING clause. If an array is given, the conditions
+ * constructed from each element are combined with AND.
*
* - ORDER BY: May be either an SQL fragment giving a field name or
* expression to order by, or an array of such SQL fragments.
@@ -1351,7 +1332,7 @@ abstract class DatabaseBase implements DatabaseType {
* DBQueryError exception will be thrown, except if the "ignore errors"
* option was set, in which case false will be returned.
*/
- function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
+ public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
$options = array(), $join_conds = array() ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -1360,7 +1341,9 @@ abstract class DatabaseBase implements DatabaseType {
/**
* The equivalent of DatabaseBase::select() except that the constructed SQL
- * is returned, instead of being immediately executed.
+ * is returned, instead of being immediately executed. This can be useful for
+ * 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
@@ -1369,12 +1352,14 @@ abstract class DatabaseBase implements DatabaseType {
* @param $options string|array Query options
* @param $join_conds string|array Join conditions
*
- * @return SQL query string.
+ * @return string SQL query string.
* @see DatabaseBase::select()
*/
- function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', $options = array(), $join_conds = array() ) {
+ public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
+ $options = array(), $join_conds = array() )
+ {
if ( is_array( $vars ) ) {
- $vars = implode( ',', $vars );
+ $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
}
$options = (array)$options;
@@ -1435,9 +1420,9 @@ abstract class DatabaseBase implements DatabaseType {
* @param $options string|array Query options
* @param $join_conds array|string Join conditions
*
- * @return ResultWrapper|bool
+ * @return object|bool
*/
- function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
+ public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
$options = array(), $join_conds = array() )
{
$options = (array)$options;
@@ -1481,7 +1466,7 @@ abstract class DatabaseBase implements DatabaseType {
$fname = 'DatabaseBase::estimateRowCount', $options = array() )
{
$rows = 0;
- $res = $this->select ( $table, 'COUNT(*) AS rowcount', $conds, $fname, $options );
+ $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
if ( $res ) {
$row = $this->fetchRow( $res );
@@ -1527,7 +1512,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fname String: calling function name (optional)
* @return Boolean: whether $table has filed $field
*/
- function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
+ public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
$info = $this->fieldInfo( $table, $field );
return (bool)$info;
@@ -1544,7 +1529,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool|null
*/
- function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
+ public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
$info = $this->indexInfo( $table, $index, $fname );
if ( is_null( $info ) ) {
return null;
@@ -1561,7 +1546,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function tableExists( $table, $fname = __METHOD__ ) {
+ public function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$old = $this->ignoreErrors( true );
$res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
@@ -1576,7 +1561,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $index
* @return string
*/
- function fieldType( $res, $index ) {
+ public function fieldType( $res, $index ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
@@ -1592,7 +1577,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function indexUnique( $table, $index ) {
+ public function indexUnique( $table, $index ) {
$indexInfo = $this->indexInfo( $table, $index );
if ( !$indexInfo ) {
@@ -1608,7 +1593,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $options array
* @return string
*/
- function makeInsertOptions( $options ) {
+ protected function makeInsertOptions( $options ) {
return implode( ' ', $options );
}
@@ -1645,7 +1630,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
+ public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
# No rows to insert, easy just return now
if ( !count( $a ) ) {
return true;
@@ -1693,7 +1678,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $options Array: The options passed to DatabaseBase::update
* @return string
*/
- function makeUpdateOptions( $options ) {
+ protected function makeUpdateOptions( $options ) {
if ( !is_array( $options ) ) {
$options = array( $options );
}
@@ -1759,7 +1744,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return string
*/
- function makeList( $a, $mode = LIST_COMMA ) {
+ public function makeList( $a, $mode = LIST_COMMA ) {
if ( !is_array( $a ) ) {
throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
}
@@ -1819,12 +1804,12 @@ abstract class DatabaseBase implements DatabaseType {
* The keys on each level may be either integers or strings.
*
* @param $data Array: organized as 2-d
- * array(baseKeyVal => array(subKeyVal => <ignored>, ...), ...)
+ * 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')
* @return Mixed: string SQL fragment, or false if no items in array.
*/
- function makeWhereFrom2d( $data, $baseKey, $subKey ) {
+ public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
$conds = array();
foreach ( $data as $base => $sub ) {
@@ -1844,14 +1829,22 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Bitwise operations
+ * Return aggregated value alias
+ *
+ * @param $valuedata
+ * @param $valuename string
+ *
+ * @return string
*/
+ public function aggregateValue( $valuedata, $valuename = 'value' ) {
+ return $valuename;
+ }
/**
* @param $field
* @return string
*/
- function bitNot( $field ) {
+ public function bitNot( $field ) {
return "(~$field)";
}
@@ -1860,7 +1853,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fieldRight
* @return string
*/
- function bitAnd( $fieldLeft, $fieldRight ) {
+ public function bitAnd( $fieldLeft, $fieldRight ) {
return "($fieldLeft & $fieldRight)";
}
@@ -1869,11 +1862,20 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fieldRight
* @return string
*/
- function bitOr( $fieldLeft, $fieldRight ) {
+ public function bitOr( $fieldLeft, $fieldRight ) {
return "($fieldLeft | $fieldRight)";
}
/**
+ * Build a concatenation list to feed into a SQL query
+ * @param $stringList Array: list of raw SQL expressions; caller is responsible for any quoting
+ * @return String
+ */
+ public function buildConcat( $stringList ) {
+ return 'CONCAT(' . implode( ',', $stringList ) . ')';
+ }
+
+ /**
* Change the current database
*
* @todo Explain what exactly will fail if this is not overridden.
@@ -1882,7 +1884,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool Success or failure
*/
- function selectDB( $db ) {
+ public function selectDB( $db ) {
# Stub. Shouldn't cause serious problems if it's not overridden, but
# if your database engine supports a concept similar to MySQL's
# databases you may as well.
@@ -1893,14 +1895,14 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Get the current DB name
*/
- function getDBname() {
+ public function getDBname() {
return $this->mDBname;
}
/**
* Get the server hostname or IP address
*/
- function getServer() {
+ public function getServer() {
return $this->mServer;
}
@@ -1921,7 +1923,7 @@ abstract class DatabaseBase implements DatabaseType {
* raw - Do not add identifier quotes to the table name
* @return String: full database name
*/
- function tableName( $name, $format = 'quoted' ) {
+ public function tableName( $name, $format = 'quoted' ) {
global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
# Skip the entire process when we have a string quoted on both ends.
# Note that we check the end so that we will still quote any use of
@@ -2067,6 +2069,39 @@ 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)
+ * @return string SQL name for aliased field. Will not alias a field to its own name
+ */
+ public function fieldNameWithAlias( $name, $alias = false ) {
+ if ( !$alias || (string)$alias === (string)$name ) {
+ return $name;
+ } else {
+ return $name . ' AS ' . $alias; //PostgreSQL needs AS
+ }
+ }
+
+ /**
+ * Gets an array of aliased field names
+ *
+ * @param $fields array( [alias] => field )
+ * @return array of strings, see fieldNameWithAlias()
+ */
+ public function fieldNamesWithAlias( $fields ) {
+ $retval = array();
+ foreach ( $fields as $alias => $field ) {
+ if ( is_numeric( $alias ) ) {
+ $alias = $field;
+ }
+ $retval[] = $this->fieldNameWithAlias( $field, $alias );
+ }
+ return $retval;
+ }
+
+ /**
* Get the aliased table name clause for a FROM clause
* which might have a JOIN and/or USE INDEX clause
*
@@ -2134,7 +2169,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return string
*/
- function indexName( $index ) {
+ protected function indexName( $index ) {
// Backwards-compatibility hack
$renamed = array(
'ar_usertext_timestamp' => 'usertext_timestamp',
@@ -2157,7 +2192,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return string
*/
- function addQuotes( $s ) {
+ public function addQuotes( $s ) {
if ( $s === null ) {
return 'NULL';
} else {
@@ -2196,36 +2231,6 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Backwards compatibility, identifier quoting originated in DatabasePostgres
- * which used quote_ident which does not follow our naming conventions
- * was renamed to addIdentifierQuotes.
- * @deprecated since 1.18 use addIdentifierQuotes
- *
- * @param $s string
- *
- * @return string
- */
- function quote_ident( $s ) {
- wfDeprecated( __METHOD__, '1.18' );
- return $this->addIdentifierQuotes( $s );
- }
-
- /**
- * Escape string for safe LIKE usage.
- * WARNING: you should almost never use this function directly,
- * instead use buildLike() that escapes everything automatically
- * @deprecated since 1.17, warnings in 1.17, removed in ???
- *
- * @param $s string
- *
- * @return string
- */
- public function escapeLike( $s ) {
- wfDeprecated( __METHOD__, '1.17' );
- return $this->escapeLikeInternal( $s );
- }
-
- /**
* @param $s string
* @return string
*/
@@ -2249,7 +2254,7 @@ abstract class DatabaseBase implements DatabaseType {
* @since 1.16
* @return String: fully built LIKE statement
*/
- function buildLike() {
+ public function buildLike() {
$params = func_get_args();
if ( count( $params ) > 0 && is_array( $params[0] ) ) {
@@ -2274,7 +2279,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return LikeMatch
*/
- function anyChar() {
+ public function anyChar() {
return new LikeMatch( '_' );
}
@@ -2283,7 +2288,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return LikeMatch
*/
- function anyString() {
+ public function anyString() {
return new LikeMatch( '%' );
}
@@ -2298,7 +2303,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $seqName string
* @return null
*/
- function nextSequenceValue( $seqName ) {
+ public function nextSequenceValue( $seqName ) {
return null;
}
@@ -2312,7 +2317,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $index
* @return string
*/
- function useIndexClause( $index ) {
+ public function useIndexClause( $index ) {
return '';
}
@@ -2338,7 +2343,7 @@ abstract class DatabaseBase implements DatabaseType {
* a field name or an array of field names
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
*/
- function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
+ public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
$quotedTable = $this->tableName( $table );
if ( count( $rows ) == 0 ) {
@@ -2439,7 +2444,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fname String: Calling function name (use __METHOD__) for
* logs/profiling
*/
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+ public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
$fname = 'DatabaseBase::deleteJoin' )
{
if ( !$conds ) {
@@ -2466,7 +2471,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return int
*/
- function textFieldSize( $table, $field ) {
+ public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
$res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
@@ -2491,7 +2496,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return string Returns the text of the low priority option if it is
* supported, or a blank string otherwise
*/
- function lowPriorityOption() {
+ public function lowPriorityOption() {
return '';
}
@@ -2505,7 +2510,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
+ public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
if ( !$conds ) {
throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
}
@@ -2546,7 +2551,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return ResultWrapper
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds,
+ public function insertSelect( $destTable, $srcTable, $varMap, $conds,
$fname = 'DatabaseBase::insertSelect',
$insertOptions = array(), $selectOptions = array() )
{
@@ -2592,35 +2597,24 @@ abstract class DatabaseBase implements DatabaseType {
* If the result of the query is not ordered, then the rows to be returned
* are theoretically arbitrary.
*
- * $sql is expected to be a SELECT, if that makes a difference. For
- * UPDATE, limitResultForUpdate should be used.
+ * $sql is expected to be a SELECT, if that makes a difference.
*
* The version provided by default works in MySQL and SQLite. It will very
* likely need to be overridden for most other DBMSes.
*
* @param $sql String SQL query we will append the limit too
* @param $limit Integer the SQL limit
- * @param $offset Integer|false the SQL offset (default false)
+ * @param $offset Integer|bool the SQL offset (default false)
*
* @return string
*/
- function limitResult( $sql, $limit, $offset = false ) {
+ public function limitResult( $sql, $limit, $offset = false ) {
if ( !is_numeric( $limit ) ) {
throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
}
-
return "$sql LIMIT "
- . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
- . "{$limit} ";
- }
-
- /**
- * @param $sql
- * @param $num
- * @return string
- */
- function limitResultForUpdate( $sql, $num ) {
- return $this->limitResult( $sql, $num, 0 );
+ . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
+ . "{$limit} ";
}
/**
@@ -2628,7 +2622,7 @@ abstract class DatabaseBase implements DatabaseType {
* within the UNION construct.
* @return Boolean
*/
- function unionSupportsOrderAndLimit() {
+ public function unionSupportsOrderAndLimit() {
return true; // True for almost every DB supported
}
@@ -2640,7 +2634,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $all Boolean: use UNION ALL
* @return String: SQL fragment
*/
- function unionQueries( $sqls, $all ) {
+ public function unionQueries( $sqls, $all ) {
$glue = $all ? ') UNION ALL (' : ') UNION (';
return '(' . implode( $glue, $sqls ) . ')';
}
@@ -2649,12 +2643,15 @@ 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: SQL expression which will result in a boolean value
+ * @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
* @return String: SQL fragment
*/
- function conditional( $cond, $trueVal, $falseVal ) {
+ public function conditional( $cond, $trueVal, $falseVal ) {
+ if ( is_array( $cond ) ) {
+ $cond = $this->makeList( $cond, LIST_AND );
+ }
return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
}
@@ -2668,7 +2665,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return string
*/
- function strreplace( $orig, $old, $new ) {
+ public function strreplace( $orig, $old, $new ) {
return "REPLACE({$orig}, {$old}, {$new})";
}
@@ -2678,7 +2675,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return int
*/
- function getServerUptime() {
+ public function getServerUptime() {
return 0;
}
@@ -2688,7 +2685,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function wasDeadlock() {
+ public function wasDeadlock() {
return false;
}
@@ -2698,7 +2695,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function wasLockTimeout() {
+ public function wasLockTimeout() {
return false;
}
@@ -2709,7 +2706,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function wasErrorReissuable() {
+ public function wasErrorReissuable() {
return false;
}
@@ -2719,7 +2716,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function wasReadOnlyError() {
+ public function wasReadOnlyError() {
return false;
}
@@ -2741,8 +2738,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- function deadlockLoop() {
-
+ public function deadlockLoop() {
$this->begin( __METHOD__ );
$args = func_get_args();
$function = array_shift( $args );
@@ -2790,11 +2786,11 @@ abstract class DatabaseBase implements DatabaseType {
* @param $timeout Integer: the maximum number of seconds to wait for
* synchronisation
*
- * @return An integer: zero if the slave was past that position already,
+ * @return integer: zero if the slave was past that position already,
* greater than zero if we waited for some period of time, less than
* zero if we timed out.
*/
- function masterPosWait( DBMasterPos $pos, $timeout ) {
+ public function masterPosWait( DBMasterPos $pos, $timeout ) {
wfProfileIn( __METHOD__ );
if ( !is_null( $this->mFakeSlaveLag ) ) {
@@ -2827,7 +2823,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return DBMasterPos, or false if this is not a slave.
*/
- function getSlavePos() {
+ public function getSlavePos() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
$pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
@@ -2843,7 +2839,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return DBMasterPos, or false if this is not a master
*/
- function getMasterPos() {
+ public function getMasterPos() {
if ( $this->mFakeMaster ) {
return new MySQLMasterPos( 'fake', microtime( true ) );
} else {
@@ -2852,11 +2848,65 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Begin a transaction, committing any previously open transaction
+ * Run an anonymous function as soon as there is no transaction pending.
+ * If there is a transaction and it is rolled back, then the callback is cancelled.
+ * Callbacks must commit any transactions that they begin.
+ *
+ * This is useful for updates to different systems or separate transactions are needed.
+ *
+ * @param Closure $callback
+ * @return void
+ */
+ final public function onTransactionIdle( Closure $callback ) {
+ if ( $this->mTrxLevel ) {
+ $this->mTrxIdleCallbacks[] = $callback;
+ } else {
+ $callback();
+ }
+ }
+
+ /**
+ * Actually run the "on transaction idle" callbacks
+ */
+ protected function runOnTransactionIdleCallbacks() {
+ $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
+
+ $e = null; // last exception
+ do { // callbacks may add callbacks :)
+ $callbacks = $this->mTrxIdleCallbacks;
+ $this->mTrxIdleCallbacks = array(); // recursion guard
+ foreach ( $callbacks as $callback ) {
+ try {
+ $this->clearFlag( DBO_TRX ); // make each query its own transaction
+ $callback();
+ $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
+ } catch ( Exception $e ) {}
+ }
+ } while ( count( $this->mTrxIdleCallbacks ) );
+
+ if ( $e instanceof Exception ) {
+ throw $e; // re-throw any last exception
+ }
+ }
+
+ /**
+ * Begin a transaction
*
* @param $fname string
*/
- function begin( $fname = 'DatabaseBase::begin' ) {
+ final public function begin( $fname = 'DatabaseBase::begin' ) {
+ if ( $this->mTrxLevel ) { // implicit commit
+ $this->doCommit( $fname );
+ $this->runOnTransactionIdleCallbacks();
+ }
+ $this->doBegin( $fname );
+ }
+
+ /**
+ * @see DatabaseBase::begin()
+ * @param type $fname
+ */
+ protected function doBegin( $fname ) {
$this->query( 'BEGIN', $fname );
$this->mTrxLevel = 1;
}
@@ -2866,7 +2916,16 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $fname string
*/
- function commit( $fname = 'DatabaseBase::commit' ) {
+ final public function commit( $fname = 'DatabaseBase::commit' ) {
+ $this->doCommit( $fname );
+ $this->runOnTransactionIdleCallbacks();
+ }
+
+ /**
+ * @see DatabaseBase::commit()
+ * @param type $fname
+ */
+ protected function doCommit( $fname ) {
if ( $this->mTrxLevel ) {
$this->query( 'COMMIT', $fname );
$this->mTrxLevel = 0;
@@ -2879,7 +2938,16 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $fname string
*/
- function rollback( $fname = 'DatabaseBase::rollback' ) {
+ final public function rollback( $fname = 'DatabaseBase::rollback' ) {
+ $this->doRollback( $fname );
+ $this->mTrxIdleCallbacks = array(); // cancel
+ }
+
+ /**
+ * @see DatabaseBase::rollback()
+ * @param type $fname
+ */
+ protected function doRollback( $fname ) {
if ( $this->mTrxLevel ) {
$this->query( 'ROLLBACK', $fname, true );
$this->mTrxLevel = 0;
@@ -2900,7 +2968,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fname String: calling function name
* @return Boolean: true if operation was successful
*/
- function duplicateTableStructure( $oldName, $newName, $temporary = false,
+ public function duplicateTableStructure( $oldName, $newName, $temporary = false,
$fname = 'DatabaseBase::duplicateTableStructure' )
{
throw new MWException(
@@ -2910,7 +2978,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* List all tables on the database
*
- * @param $prefix Only show tables with this prefix, e.g. mw_
+ * @param $prefix string Only show tables with this prefix, e.g. mw_
* @param $fname String: calling function name
*/
function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
@@ -2928,7 +2996,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return string
*/
- function timestamp( $ts = 0 ) {
+ public function timestamp( $ts = 0 ) {
return wfTimestamp( TS_MW, $ts );
}
@@ -2945,7 +3013,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return string
*/
- function timestampOrNull( $ts = null ) {
+ public function timestampOrNull( $ts = null ) {
if ( is_null( $ts ) ) {
return null;
} else {
@@ -2968,7 +3036,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool|ResultWrapper
*/
- function resultObject( $result ) {
+ public function resultObject( $result ) {
if ( empty( $result ) ) {
return false;
} elseif ( $result instanceof ResultWrapper ) {
@@ -2982,23 +3050,11 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Return aggregated value alias
- *
- * @param $valuedata
- * @param $valuename string
- *
- * @return string
- */
- function aggregateValue ( $valuedata, $valuename = 'value' ) {
- return $valuename;
- }
-
- /**
* Ping the server and try to reconnect if it there is no connection
*
* @return bool Success or failure
*/
- function ping() {
+ public function ping() {
# Stub. Not essential to override.
return true;
}
@@ -3010,9 +3066,9 @@ abstract class DatabaseBase implements DatabaseType {
* installations. Most callers should use LoadBalancer::safeGetLag()
* instead.
*
- * @return Database replication lag in seconds
+ * @return int Database replication lag in seconds
*/
- function getLag() {
+ public function getLag() {
return intval( $this->mFakeSlaveLag );
}
@@ -3033,7 +3089,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $b string
* @return string
*/
- function encodeBlob( $b ) {
+ public function encodeBlob( $b ) {
return $b;
}
@@ -3044,23 +3100,11 @@ abstract class DatabaseBase implements DatabaseType {
* @param $b string
* @return string
*/
- function decodeBlob( $b ) {
+ public function decodeBlob( $b ) {
return $b;
}
/**
- * Override database's default connection timeout
- *
- * @param $timeout Integer in seconds
- * @return void
- * @deprecated since 1.19; use setSessionOptions()
- */
- public function setTimeout( $timeout ) {
- wfDeprecated( __METHOD__, '1.19' );
- $this->setSessionOptions( array( 'connTimeout' => $timeout ) );
- }
-
- /**
* Override database's default behavior. $options include:
* 'connTimeout' : Set the connection timeout value in seconds.
* May be useful for very long batch queries such as
@@ -3085,7 +3129,9 @@ abstract class DatabaseBase implements DatabaseType {
* generated dynamically using $filename
* @return bool|string
*/
- function sourceFile( $filename, $lineCallback = false, $resultCallback = false, $fname = false ) {
+ public function sourceFile(
+ $filename, $lineCallback = false, $resultCallback = false, $fname = false
+ ) {
wfSuppressWarnings();
$fp = fopen( $filename, 'r' );
wfRestoreWarnings();
@@ -3135,9 +3181,9 @@ 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 False, or array mapping variable name to value.
+ * @param $vars bool|array mapping variable name to value.
*/
- function setSchemaVars( $vars ) {
+ public function setSchemaVars( $vars ) {
$this->mSchemaVars = $vars;
}
@@ -3323,12 +3369,15 @@ 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
- * @return String
+ * 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
+ * @return Boolean
+ * @since 1.20
*/
- function buildConcat( $stringList ) {
- return 'CONCAT(' . implode( ',', $stringList ) . ')';
+ public function lockIsFree( $lockName, $method ) {
+ return true;
}
/**
@@ -3352,7 +3401,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $lockName String: Name of lock to release
* @param $method String: Name of method calling us
*
- * @return Returns 1 if the lock was released, 0 if the lock was not established
+ * @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
* lock did not exist
*/
@@ -3425,17 +3474,28 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Encode an expiry time
+ * Encode an expiry time into the DBMS dependent format
*
* @param $expiry String: timestamp for expiry, or the 'infinity' string
* @return String
*/
public function encodeExpiry( $expiry ) {
- if ( $expiry == '' || $expiry == $this->getInfinity() ) {
- return $this->getInfinity();
- } else {
- return $this->timestamp( $expiry );
- }
+ return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
+ ? $this->getInfinity()
+ : $this->timestamp( $expiry );
+ }
+
+ /**
+ * Decode an expiry time into a DBMS independent format
+ *
+ * @param $expiry String: DB timestamp field value for expiry
+ * @param $format integer: TS_* constant, defaults to TS_MW
+ * @return String
+ */
+ public function decodeExpiry( $expiry, $format = TS_MW ) {
+ return ( $expiry == '' || $expiry == $this->getInfinity() )
+ ? 'infinity'
+ : wfTimestamp( $format, $expiry );
}
/**
@@ -3450,4 +3510,17 @@ abstract class DatabaseBase implements DatabaseType {
public function setBigSelects( $value = true ) {
// no-op
}
+
+ /**
+ * @since 1.19
+ */
+ public function __toString() {
+ return (string)$this->mConn;
+ }
+
+ public function __destruct() {
+ if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
+ trigger_error( "Transaction idle callbacks still pending." );
+ }
+ }
}
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
index 836d7814..a53a6747 100644
--- a/includes/db/DatabaseError.php
+++ b/includes/db/DatabaseError.php
@@ -1,4 +1,25 @@
<?php
+/**
+ * This file contains database error 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
+ * 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
+ */
/**
* Database error base class
@@ -189,7 +210,7 @@ class DBConnectionError extends DBError {
* @return string
*/
function searchForm() {
- global $wgSitename, $wgServer, $wgRequest;
+ global $wgSitename, $wgCanonicalServer, $wgRequest;
$usegoogle = htmlspecialchars( $this->msg( 'dberr-usegoogle', 'You can try searching via Google in the meantime.' ) );
$outofdate = htmlspecialchars( $this->msg( 'dberr-outofdate', 'Note that their indexes of our content may be out of date.' ) );
@@ -197,7 +218,7 @@ class DBConnectionError extends DBError {
$search = htmlspecialchars( $wgRequest->getVal( 'search' ) );
- $server = htmlspecialchars( $wgServer );
+ $server = htmlspecialchars( $wgCanonicalServer );
$sitename = htmlspecialchars( $wgSitename );
$trygoogle = <<<EOT
@@ -297,7 +318,7 @@ class DBQueryError extends DBError {
$fname = $this->fname;
$error = $this->error;
}
- return wfMsg( $msg, $sql, $fname, $this->errno, $error );
+ return wfMessage( $msg )->rawParams( $sql, $fname, $this->errno, $error )->text();
} else {
return parent::getContentMessage( $html );
}
diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php
index fed3b12e..f1f6dfca 100644
--- a/includes/db/DatabaseIbm_db2.php
+++ b/includes/db/DatabaseIbm_db2.php
@@ -2,7 +2,22 @@
/**
* This is the IBM DB2 database abstraction layer.
* See maintenance/ibm_db2/README for development notes
- * and other specific information
+ * 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
@@ -122,7 +137,7 @@ class IBM_DB2Result{
/**
* Construct and initialize a wrapper for DB2 query results
- * @param $db Database
+ * @param $db DatabaseBase
* @param $result Object
* @param $num_rows Integer
* @param $sql String
@@ -130,21 +145,21 @@ class IBM_DB2Result{
*/
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++ ) {
@@ -155,11 +170,11 @@ class IBM_DB2Result{
else {
return false;
}
-
+
$this->columns[$i] = strtolower( $name );
}
}
-
+
$this->sql = $sql;
}
@@ -187,14 +202,14 @@ class IBM_DB2Result{
* @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 )
+ 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;
@@ -210,9 +225,9 @@ class IBM_DB2Result{
* @throws DBUnexpectedError
*/
public function fetchRow(){
- if ( $this->result
- && $this->num_rows > 0
- && $this->current_pos >= 0
+ if ( $this->result
+ && $this->num_rows > 0
+ && $this->current_pos >= 0
&& $this->current_pos < $this->num_rows )
{
if ( $this->loadedLines <= $this->current_pos ) {
@@ -227,7 +242,7 @@ class IBM_DB2Result{
if ( $this->loadedLines > $this->current_pos ){
return $this->resultSet[$this->current_pos++];
}
-
+
}
return false;
}
@@ -313,6 +328,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* Returns true if this database supports (and uses) cascading deletes
+ * @return bool
*/
function cascadingDeletes() {
return true;
@@ -321,6 +337,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* Returns true if this database supports (and uses) triggers (e.g. on the
* page table)
+ * @return bool
*/
function cleanupTriggers() {
return true;
@@ -330,6 +347,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* 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;
@@ -337,13 +355,15 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* 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;
@@ -353,6 +373,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* 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;
@@ -361,6 +382,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* 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;
@@ -368,6 +390,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* Returns true if this database can use functional indexes
+ * @return bool
*/
function functionalIndexes() {
return true;
@@ -375,6 +398,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* Returns a unique string representing the wiki on the server
+ * @return string
*/
public function getWikiID() {
if( $this->mSchema ) {
@@ -392,7 +416,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
return 'ibm_db2';
}
- /**
+ /**
* Returns the database connection object
* @return Object
*/
@@ -472,7 +496,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* @param $user String
* @param $password String
* @param $dbName String: database name
- * @return a fresh connection
+ * @return DatabaseBase a fresh connection
*/
public function open( $server, $user, $password, $dbName ) {
wfProfileIn( __METHOD__ );
@@ -546,32 +570,26 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* Closes a database connection, if it is open
* Returns success, true if already closed
+ * @return bool
*/
- public function close() {
- $this->mOpened = false;
- if ( $this->mConn ) {
- if ( $this->trxLevel() > 0 ) {
- $this->commit();
- }
- return db2_close( $this->mConn );
- } else {
- return true;
- }
+ 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();
+ //$this->rollback( __METHOD__ );
return $connerr;
}
$stmterr = db2_stmt_errormsg();
if ( $stmterr ) {
- //$this->rollback();
+ //$this->rollback( __METHOD__ );
return $stmterr;
}
@@ -667,7 +685,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
*
- * @param $res SQL result object as returned from Database::query(), etc.
+ * @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
*/
@@ -689,8 +707,8 @@ class DatabaseIbm_db2 extends DatabaseBase {
* Fetch the next row from the given result object, in associative array
* form. Fields are retrieved with $row['fieldname'].
*
- * @param $res SQL result object as returned from Database::query(), etc.
- * @return DB2 row object
+ * @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 ) {
@@ -715,7 +733,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* Doesn't escape numbers
*
* @param $s String: string to escape
- * @return escaped string
+ * @return string escaped string
*/
public function addQuotes( $s ) {
//$this->installPrint( "DB2::addQuotes( $s )\n" );
@@ -758,7 +776,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* Alias for addQuotes()
* @param $s String: string to escape
- * @return escaped string
+ * @return string escaped string
*/
public function strencode( $s ) {
// Bloody useless function
@@ -780,16 +798,16 @@ class DatabaseIbm_db2 extends DatabaseBase {
protected function applySchema() {
if ( !( $this->mSchemaSet ) ) {
$this->mSchemaSet = true;
- $this->begin();
+ $this->begin( __METHOD__ );
$this->doQuery( "SET SCHEMA = $this->mSchema" );
- $this->commit();
+ $this->commit( __METHOD__ );
}
}
/**
* Start a transaction (mandatory)
*/
- public function begin( $fname = 'DatabaseIbm_db2::begin' ) {
+ protected function doBegin( $fname = 'DatabaseIbm_db2::begin' ) {
// BEGIN is implicit for DB2
// However, it requires that AutoCommit be off.
@@ -805,7 +823,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* End a transaction
* Must have a preceding begin()
*/
- public function commit( $fname = 'DatabaseIbm_db2::commit' ) {
+ protected function doCommit( $fname = 'DatabaseIbm_db2::commit' ) {
db2_commit( $this->mConn );
// Some MediaWiki code is still transaction-less (?).
@@ -819,7 +837,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* Cancel a transaction
*/
- public function rollback( $fname = 'DatabaseIbm_db2::rollback' ) {
+ protected function doRollback( $fname = 'DatabaseIbm_db2::rollback' ) {
db2_rollback( $this->mConn );
// turn auto-commit back on
// not sure if this is appropriate
@@ -836,6 +854,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* 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 ) ) {
@@ -873,6 +892,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* @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 ) ) {
@@ -904,7 +924,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* Generates a timestamp in an insertable format
*
- * @param $ts timestamp
+ * @param $ts string timestamp
* @return String: timestamp value
*/
public function timestamp( $ts = 0 ) {
@@ -915,7 +935,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* 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 next value in that sequence
+ * @return int next value in that sequence
*/
public function nextSequenceValue( $seqName ) {
// Not using sequences in the primary schema to allow for easier migration
@@ -934,7 +954,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* This must be called after nextSequenceVal
- * @return Last sequence value used as a primary key
+ * @return int Last sequence value used as a primary key
*/
public function insertId() {
return $this->mInsertId;
@@ -1003,7 +1023,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
$res = true;
// If we are not in a transaction, we need to be for savepoint trickery
if ( !$this->mTrxLevel ) {
- $this->begin();
+ $this->begin( __METHOD__ );
}
$sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES ';
@@ -1018,7 +1038,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
$stmt = $this->prepare( $sql );
// start a transaction/enter transaction mode
- $this->begin();
+ $this->begin( __METHOD__ );
if ( !$ignore ) {
//$first = true;
@@ -1071,7 +1091,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
$this->mAffectedRows = $numrowsinserted;
}
// commit either way
- $this->commit();
+ $this->commit( __METHOD__ );
$this->freePrepared( $stmt );
return $res;
@@ -1121,11 +1141,11 @@ class DatabaseIbm_db2 extends DatabaseBase {
* UPDATE wrapper, takes a condition array and a SET array
*
* @param $table String: The table to UPDATE
- * @param $values An array of values to SET
- * @param $conds An array of conditions ( WHERE ). Use '*' to update all rows.
+ * @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 An array of UPDATE options, can be one or
+ * @param $options array An array of UPDATE options, can be one or
* more of IGNORE, LOW_PRIORITY
* @return Boolean
*/
@@ -1153,6 +1173,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* DELETE query wrapper
*
* Use $conds == "*" to delete all rows
+ * @return bool|\ResultWrapper
*/
public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
if ( !$conds ) {
@@ -1206,7 +1227,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* Moves the row pointer of the result set
* @param $res Object: result set
* @param $row Integer: row number
- * @return success or failure
+ * @return bool success or failure
*/
public function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
@@ -1279,11 +1300,11 @@ class DatabaseIbm_db2 extends DatabaseBase {
* @param $conds Array or string, condition(s) for WHERE
* @param $fname String: calling function name (use __METHOD__)
* for logs/profiling
- * @param $options Associative array of options
+ * @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 Associative array of table join conditions (optional)
+ * @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
@@ -1320,10 +1341,10 @@ class DatabaseIbm_db2 extends DatabaseBase {
$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 ) );
}
@@ -1333,7 +1354,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
*
* @private
*
- * @param $options Associative array of options to be turned into
+ * @param $options array Associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return Array
*/
@@ -1412,7 +1433,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
// db2_ping() doesn't exist
// Emulate
$this->close();
- $this->mConn = $this->openUncataloged( $this->mDBName, $this->mUser,
+ $this->openUncataloged( $this->mDBName, $this->mUser,
$this->mPassword, $this->mServer, $this->mPort );
return false;
@@ -1420,14 +1441,6 @@ class DatabaseIbm_db2 extends DatabaseBase {
######################################
# Unimplemented and not applicable
######################################
- /**
- * Not implemented
- * @return string $sql
- */
- public function limitResultForUpdate( $sql, $num ) {
- $this->installPrint( 'Not implemented for DB2: limitResultForUpdate()' );
- return $sql;
- }
/**
* Only useful with fake prepare like in base Database class
@@ -1502,7 +1515,7 @@ SQL;
* Verifies that an index was created as unique
* @param $table String: table name
* @param $index String: index name
- * @param $fname function name for profiling
+ * @param $fname string function name for profiling
* @return Bool
*/
public function indexUnique ( $table, $index,
@@ -1636,25 +1649,6 @@ SQL;
}
/**
- * Prepare & execute an SQL statement, quoting and inserting arguments
- * in the appropriate places.
- * @param $query String
- * @param $args ...
- */
- public function safeQuery( $query, $args = null ) {
- // copied verbatim from Database.php
- $prepared = $this->prepare( $query, 'DB2::safeQuery' );
- if( !is_array( $args ) ) {
- # Pull the var args
- $args = func_get_args();
- array_shift( $args );
- }
- $retval = $this->execute( $prepared, $args );
- $this->freePrepared( $prepared );
- return $retval;
- }
-
- /**
* For faking prepared SQL statements on DBs that don't support
* it directly.
* @param $preparedQuery String: a 'preparable' SQL statement
@@ -1674,6 +1668,7 @@ SQL;
/**
* Switches module between regular and install modes
+ * @return string
*/
public function setMode( $mode ) {
$old = $this->mMode;
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 38be4cbb..914ab408 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -2,6 +2,21 @@
/**
* This is the MS SQL Server Native database abstraction layer.
*
+ * 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 Joel Penner <a-joelpe at microsoft dot com>
@@ -46,6 +61,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Usually aborts on failure
+ * @return bool|DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
# Test for driver support, to avoid suppressed fatal error
@@ -107,14 +123,10 @@ class DatabaseMssql extends DatabaseBase {
/**
* Closes a database connection, if it is open
* Returns success, true if already closed
+ * @return bool
*/
- function close() {
- $this->mOpened = false;
- if ( $this->mConn ) {
- return sqlsrv_close( $this->mConn );
- } else {
- return true;
- }
+ protected function closeConnection() {
+ return sqlsrv_close( $this->mConn );
}
protected function doQuery( $sql ) {
@@ -226,6 +238,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* This must be called after nextSequenceVal
+ * @return null
*/
function insertId() {
return $this->mInsertId;
@@ -310,6 +323,7 @@ class DatabaseMssql extends DatabaseBase {
* This is not necessarily an accurate estimate, so use sparingly
* Returns -1 if count cannot be found
* Takes same arguments as Database::select()
+ * @return int
*/
function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseMssql::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;// http://msdn2.microsoft.com/en-us/library/aa259203.aspx
@@ -326,6 +340,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
+ * @return array|bool|null
*/
function indexInfo( $table, $index, $fname = 'DatabaseMssql::indexExists' ) {
# This does not return the same info as MYSQL would, but that's OK because MediaWiki never uses the
@@ -365,6 +380,7 @@ class DatabaseMssql extends DatabaseBase {
*
* Usually aborts on failure
* If errors are explicitly ignored, returns success
+ * @return bool
*/
function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) {
# No rows to insert, easy just return now
@@ -494,6 +510,7 @@ 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
*/
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect',
$insertOptions = array(), $selectOptions = array() ) {
@@ -511,6 +528,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Return the next in a sequence, save the value for retrieval via insertId()
+ * @return
*/
function nextSequenceValue( $seqName ) {
if ( !$this->tableExists( 'sequence_' . $seqName ) ) {
@@ -527,6 +545,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Return the current value of a sequence. Assumes it has ben nextval'ed in this session.
+ * @return
*/
function currentSequenceValue( $seqName ) {
$ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" );
@@ -559,6 +578,7 @@ class DatabaseMssql extends DatabaseBase {
* $sql string SQL query we will append the limit too
* $limit integer the SQL limit
* $offset integer the SQL offset (default false)
+ * @return mixed|string
*/
function limitResult( $sql, $limit, $offset = false ) {
if ( $offset === false || $offset == 0 ) {
@@ -600,14 +620,6 @@ class DatabaseMssql extends DatabaseBase {
return $sql;
}
- // MSSQL does support this, but documentation is too thin to make a generalized
- // function for this. Apparently UPDATE TOP (N) works, but the sort order
- // may not be what we're expecting so the top n results may be a random selection.
- // TODO: Implement properly.
- function limitResultForUpdate( $sql, $num ) {
- return $sql;
- }
-
function timestamp( $ts = 0 ) {
return wfTimestamp( TS_ISO_8601, $ts );
}
@@ -647,6 +659,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Query whether a given column exists in the mediawiki schema
+ * @return bool
*/
function fieldExists( $table, $field, $fname = 'DatabaseMssql::fieldExists' ) {
$table = $this->tableName( $table );
@@ -681,7 +694,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Begin a transaction, committing any previously open transaction
*/
- function begin( $fname = 'DatabaseMssql::begin' ) {
+ protected function doBegin( $fname = 'DatabaseMssql::begin' ) {
sqlsrv_begin_transaction( $this->mConn );
$this->mTrxLevel = 1;
}
@@ -689,7 +702,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* End a transaction
*/
- function commit( $fname = 'DatabaseMssql::commit' ) {
+ protected function doCommit( $fname = 'DatabaseMssql::commit' ) {
sqlsrv_commit( $this->mConn );
$this->mTrxLevel = 0;
}
@@ -698,7 +711,7 @@ class DatabaseMssql extends DatabaseBase {
* Rollback a transaction.
* No-op on non-transactional databases.
*/
- function rollback( $fname = 'DatabaseMssql::rollback' ) {
+ protected function doRollback( $fname = 'DatabaseMssql::rollback' ) {
sqlsrv_rollback( $this->mConn );
$this->mTrxLevel = 0;
}
@@ -707,6 +720,7 @@ class DatabaseMssql extends DatabaseBase {
* Escapes a identifier for use inm SQL.
* Throws an exception if it is invalid.
* Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
+ * @return string
*/
private function escapeIdentifier( $identifier ) {
if ( strlen( $identifier ) == 0 ) {
@@ -795,6 +809,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* @private
+ * @return string
*/
function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
$ret = array();
@@ -893,6 +908,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Get the type of the DBMS, as it appears in $wgDBtype.
+ * @return string
*/
function getType(){
return 'mssql';
@@ -909,6 +925,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Since MSSQL doesn't recognize the infinity keyword, set date manually.
* @todo Remove magic date
+ * @return string
*/
public function getInfinity() {
return '3000-01-31 00:00:00.000';
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index c179b724..7f389da9 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -2,6 +2,21 @@
/**
* This is the MySQL database abstraction layer.
*
+ * 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
*/
@@ -44,7 +59,7 @@ class DatabaseMysql extends DatabaseBase {
* @throws DBConnectionError
*/
function open( $server, $user, $password, $dbName ) {
- global $wgAllDBsAreLocalhost;
+ global $wgAllDBsAreLocalhost, $wgDBmysql5, $wgSQLMode;
wfProfileIn( __METHOD__ );
# Load mysql.so if we don't have it
@@ -68,7 +83,15 @@ class DatabaseMysql extends DatabaseBase {
$this->mPassword = $password;
$this->mDBname = $dbName;
- wfProfileIn("dbconnect-$server");
+ $connFlags = 0;
+ if ( $this->mFlags & DBO_SSL ) {
+ $connFlags |= MYSQL_CLIENT_SSL;
+ }
+ if ( $this->mFlags & DBO_COMPRESS ) {
+ $connFlags |= MYSQL_CLIENT_COMPRESS;
+ }
+
+ wfProfileIn( "dbconnect-$server" );
# The kernel's default SYN retransmission period is far too slow for us,
# so we use a short timeout plus a manual retry. Retrying means that a small
@@ -85,85 +108,71 @@ class DatabaseMysql extends DatabaseBase {
usleep( 1000 );
}
if ( $this->mFlags & DBO_PERSISTENT ) {
- $this->mConn = mysql_pconnect( $realServer, $user, $password );
+ $this->mConn = mysql_pconnect( $realServer, $user, $password, $connFlags );
} else {
# Create a new connection...
- $this->mConn = mysql_connect( $realServer, $user, $password, true );
+ $this->mConn = mysql_connect( $realServer, $user, $password, true, $connFlags );
}
#if ( $this->mConn === false ) {
#$iplus = $i + 1;
#wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
#}
}
- $phpError = $this->restoreErrorHandler();
+ $error = $this->restoreErrorHandler();
+
+ wfProfileOut( "dbconnect-$server" );
+
# Always log connection errors
if ( !$this->mConn ) {
- $error = $this->lastError();
if ( !$error ) {
- $error = $phpError;
+ $error = $this->lastError();
}
wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
- wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, User: $user, Password: " .
- substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" );
- }
+ wfDebug( "DB connection error\n" .
+ "Server: $server, User: $user, Password: " .
+ substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
- wfProfileOut("dbconnect-$server");
+ wfProfileOut( __METHOD__ );
+ $this->reportConnectionError( $error );
+ }
- if ( $dbName != '' && $this->mConn !== false ) {
+ if ( $dbName != '' ) {
wfSuppressWarnings();
$success = mysql_select_db( $dbName, $this->mConn );
wfRestoreWarnings();
if ( !$success ) {
- $error = "Error selecting database $dbName on server {$this->mServer} " .
- "from client host " . wfHostname() . "\n";
- wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n");
- wfDebug( $error );
- }
- } else {
- # Delay USE query
- $success = (bool)$this->mConn;
- }
+ wfLogDBError( "Error selecting database $dbName on server {$this->mServer}\n" );
+ wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
+ "from client host " . wfHostname() . "\n" );
- if ( $success ) {
- // Tell the server we're communicating with it in UTF-8.
- // This may engage various charset conversions.
- global $wgDBmysql5;
- if( $wgDBmysql5 ) {
- $this->query( 'SET NAMES utf8', __METHOD__ );
- } else {
- $this->query( 'SET NAMES binary', __METHOD__ );
- }
- // Set SQL mode, default is turning them all off, can be overridden or skipped with null
- global $wgSQLMode;
- if ( is_string( $wgSQLMode ) ) {
- $mode = $this->addQuotes( $wgSQLMode );
- $this->query( "SET sql_mode = $mode", __METHOD__ );
+ wfProfileOut( __METHOD__ );
+ $this->reportConnectionError( "Error selecting database $dbName" );
}
+ }
- // Turn off strict mode if it is on
+ // Tell the server we're communicating with it in UTF-8.
+ // This may engage various charset conversions.
+ if( $wgDBmysql5 ) {
+ $this->query( 'SET NAMES utf8', __METHOD__ );
} else {
- $this->reportConnectionError( $phpError );
+ $this->query( 'SET NAMES binary', __METHOD__ );
+ }
+ // Set SQL mode, default is turning them all off, can be overridden or skipped with null
+ if ( is_string( $wgSQLMode ) ) {
+ $mode = $this->addQuotes( $wgSQLMode );
+ $this->query( "SET sql_mode = $mode", __METHOD__ );
}
- $this->mOpened = $success;
+ $this->mOpened = true;
wfProfileOut( __METHOD__ );
- return $success;
+ return true;
}
/**
* @return bool
*/
- function close() {
- $this->mOpened = false;
- if ( $this->mConn ) {
- if ( $this->trxLevel() ) {
- $this->commit();
- }
- return mysql_close( $this->mConn );
- } else {
- return true;
- }
+ protected function closeConnection() {
+ return mysql_close( $this->mConn );
}
/**
@@ -194,7 +203,13 @@ class DatabaseMysql extends DatabaseBase {
wfSuppressWarnings();
$row = mysql_fetch_object( $res );
wfRestoreWarnings();
- if( $this->lastErrno() ) {
+
+ $errno = $this->lastErrno();
+ // 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.
+ if( $errno == 2000 || $errno == 2013 ) {
throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
}
return $row;
@@ -212,7 +227,13 @@ class DatabaseMysql extends DatabaseBase {
wfSuppressWarnings();
$row = mysql_fetch_array( $res );
wfRestoreWarnings();
- if ( $this->lastErrno() ) {
+
+ $errno = $this->lastErrno();
+ // 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.
+ if( $errno == 2000 || $errno == 2013 ) {
throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
}
return $row;
@@ -385,7 +406,7 @@ class DatabaseMysql extends DatabaseBase {
* @param $table string
* @param $index string
* @param $fname string
- * @return false|array
+ * @return bool|array|null False or null on failure
*/
function indexInfo( $table, $index, $fname = 'DatabaseMysql::indexInfo' ) {
# SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
@@ -558,7 +579,7 @@ class DatabaseMysql extends DatabaseBase {
# Commit any open transactions
if ( $this->mTrxLevel ) {
- $this->commit();
+ $this->commit( __METHOD__ );
}
if ( !is_null( $this->mFakeSlaveLag ) ) {
@@ -585,7 +606,7 @@ class DatabaseMysql extends DatabaseBase {
/**
* Get the position of the master from SHOW SLAVE STATUS
*
- * @return MySQLMasterPos|false
+ * @return MySQLMasterPos|bool
*/
function getSlavePos() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
@@ -606,7 +627,7 @@ class DatabaseMysql extends DatabaseBase {
/**
* Get the position of the master from SHOW MASTER STATUS
*
- * @return MySQLMasterPos|false
+ * @return MySQLMasterPos|bool
*/
function getMasterPos() {
if ( $this->mFakeMaster ) {
@@ -653,13 +674,6 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * @return bool
- */
- function standardSelectDistinct() {
- return false;
- }
-
- /**
* @param $options array
*/
public function setSessionOptions( array $options ) {
@@ -680,6 +694,21 @@ 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
+ * @return Boolean
+ * @since 1.20
+ */
+ public function lockIsFree( $lockName, $method ) {
+ $lockName = $this->addQuotes( $lockName );
+ $result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
+ $row = $this->fetchObject( $result );
+ return ( $row->lockstatus == 1 );
+ }
+
+ /**
* @param $lockName string
* @param $method string
* @param $timeout int
@@ -708,7 +737,7 @@ class DatabaseMysql extends DatabaseBase {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
$row = $this->fetchObject( $result );
- return $row->lockstatus;
+ return ( $row->lockstatus == 1 );
}
/**
@@ -860,7 +889,7 @@ class DatabaseMysql extends DatabaseBase {
/**
* List all tables on the database
*
- * @param $prefix Only show tables with this prefix, e.g. mw_
+ * @param $prefix string Only show tables with this prefix, e.g. mw_
* @param $fname String: calling function name
* @return array
*/
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 855fc831..7d8884fb 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -2,6 +2,21 @@
/**
* This is the Oracle database abstraction layer.
*
+ * 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
*/
@@ -226,6 +241,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* Usually aborts on failure
+ * @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
if ( !function_exists( 'oci_connect' ) ) {
@@ -285,17 +301,10 @@ class DatabaseOracle extends DatabaseBase {
/**
* Closes a database connection, if it is open
* Returns success, true if already closed
+ * @return bool
*/
- function close() {
- $this->mOpened = false;
- if ( $this->mConn ) {
- if ( $this->mTrxLevel ) {
- $this->commit();
- }
- return oci_close( $this->mConn );
- } else {
- return true;
- }
+ protected function closeConnection() {
+ return oci_close( $this->mConn );
}
function execFlags() {
@@ -401,6 +410,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* This must be called after nextSequenceVal
+ * @return null
*/
function insertId() {
return $this->mInsertId;
@@ -439,6 +449,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
+ * @return bool
*/
function indexInfo( $table, $index, $fname = 'DatabaseOracle::indexExists' ) {
return false;
@@ -679,6 +690,7 @@ class DatabaseOracle extends DatabaseBase {
}
/**
* Return the next in a sequence, save the value for retrieval via insertId()
+ * @return null
*/
function nextSequenceValue( $seqName ) {
$res = $this->query( "SELECT $seqName.nextval FROM dual" );
@@ -689,6 +701,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* Return sequence_name if table has a sequence
+ * @return bool
*/
private function getSequenceData( $table ) {
if ( $this->sequenceData == null ) {
@@ -797,7 +810,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* Return aggregated value function call
*/
- function aggregateValue ( $valuedata, $valuename = 'value' ) {
+ public function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
}
@@ -836,6 +849,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* Query whether a given index exists
+ * @return bool
*/
function indexExists( $table, $index, $fname = 'DatabaseOracle::indexExists' ) {
$table = $this->tableName( $table );
@@ -855,6 +869,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* Query whether a given table exists (in the given schema, or the default mw one if not given)
+ * @return int
*/
function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
@@ -940,12 +955,12 @@ class DatabaseOracle extends DatabaseBase {
return $this->fieldInfoMulti ($table, $field);
}
- function begin( $fname = 'DatabaseOracle::begin' ) {
+ protected function doBegin( $fname = 'DatabaseOracle::begin' ) {
$this->mTrxLevel = 1;
$this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
}
- function commit( $fname = 'DatabaseOracle::commit' ) {
+ protected function doCommit( $fname = 'DatabaseOracle::commit' ) {
if ( $this->mTrxLevel ) {
$ret = oci_commit( $this->mConn );
if ( !$ret ) {
@@ -956,7 +971,7 @@ class DatabaseOracle extends DatabaseBase {
}
}
- function rollback( $fname = 'DatabaseOracle::rollback' ) {
+ protected function doRollback( $fname = 'DatabaseOracle::rollback' ) {
if ( $this->mTrxLevel ) {
oci_rollback( $this->mConn );
$this->mTrxLevel = 0;
@@ -964,11 +979,6 @@ class DatabaseOracle extends DatabaseBase {
}
}
- /* Not even sure why this is used in the main codebase... */
- function limitResultForUpdate( $sql, $num ) {
- return $sql;
- }
-
/* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */
function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
$fname = 'DatabaseOracle::sourceStream', $inputCallback = false ) {
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index 98cf3c75..457bf384 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -2,12 +2,28 @@
/**
* This is the Postgres database abstraction layer.
*
+ * 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
*/
class PostgresField implements Field {
- private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname;
+ private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname,
+ $has_default, $default;
/**
* @param $db DatabaseBase
@@ -16,11 +32,11 @@ class PostgresField implements Field {
* @return null|PostgresField
*/
static function fromText( $db, $table, $field ) {
- global $wgDBmwschema;
-
$q = <<<SQL
SELECT
- attnotnull, attlen, COALESCE(conname, '') AS conname,
+ attnotnull, attlen, conname AS conname,
+ atthasdef,
+ adsrc,
COALESCE(condeferred, 'f') AS deferred,
COALESCE(condeferrable, 'f') AS deferrable,
CASE WHEN typname = 'int2' THEN 'smallint'
@@ -33,6 +49,7 @@ JOIN pg_namespace n ON (n.oid = c.relnamespace)
JOIN pg_attribute a ON (a.attrelid = c.oid)
JOIN pg_type t ON (t.oid = a.atttypid)
LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
+LEFT JOIN pg_attrdef d on c.oid=d.adrelid and a.attnum=d.adnum
WHERE relkind = 'r'
AND nspname=%s
AND relname=%s
@@ -42,7 +59,7 @@ SQL;
$table = $db->tableName( $table, 'raw' );
$res = $db->query(
sprintf( $q,
- $db->addQuotes( $wgDBmwschema ),
+ $db->addQuotes( $db->getCoreSchema() ),
$db->addQuotes( $table ),
$db->addQuotes( $field )
)
@@ -60,6 +77,8 @@ SQL;
$n->deferrable = ( $row->deferrable == 't' );
$n->deferred = ( $row->deferred == 't' );
$n->conname = $row->conname;
+ $n->has_default = ( $row->atthasdef === 't' );
+ $n->default = $row->adsrc;
return $n;
}
@@ -94,7 +113,169 @@ SQL;
function conname() {
return $this->conname;
}
+ /**
+ * @since 1.19
+ */
+ function defaultValue() {
+ if( $this->has_default ) {
+ return $this->default;
+ } else {
+ return false;
+ }
+ }
+
+}
+
+/**
+ * Used to debug transaction processing
+ * Only used if $wgDebugDBTransactions is true
+ *
+ * @since 1.19
+ * @ingroup Database
+ */
+class PostgresTransactionState {
+
+ static $WATCHED = array(
+ array(
+ "desc" => "%s: Connection state changed from %s -> %s\n",
+ "states" => array(
+ PGSQL_CONNECTION_OK => "OK",
+ PGSQL_CONNECTION_BAD => "BAD"
+ )
+ ),
+ array(
+ "desc" => "%s: Transaction state changed from %s -> %s\n",
+ "states" => array(
+ PGSQL_TRANSACTION_IDLE => "IDLE",
+ PGSQL_TRANSACTION_ACTIVE => "ACTIVE",
+ PGSQL_TRANSACTION_INTRANS => "TRANS",
+ PGSQL_TRANSACTION_INERROR => "ERROR",
+ PGSQL_TRANSACTION_UNKNOWN => "UNKNOWN"
+ )
+ )
+ );
+
+ public function __construct( $conn ) {
+ $this->mConn = $conn;
+ $this->update();
+ $this->mCurrentState = $this->mNewState;
+ }
+
+ public function update() {
+ $this->mNewState = array(
+ pg_connection_status( $this->mConn ),
+ pg_transaction_status( $this->mConn )
+ );
+ }
+
+ public function check() {
+ global $wgDebugDBTransactions;
+ $this->update();
+ if ( $wgDebugDBTransactions ) {
+ if ( $this->mCurrentState !== $this->mNewState ) {
+ $old = reset( $this->mCurrentState );
+ $new = reset( $this->mNewState );
+ foreach ( self::$WATCHED as $watched ) {
+ if ($old !== $new) {
+ $this->log_changed($old, $new, $watched);
+ }
+ $old = next( $this->mCurrentState );
+ $new = next( $this->mNewState );
+
+ }
+ }
+ }
+ $this->mCurrentState = $this->mNewState;
+ }
+
+ protected function describe_changed( $status, $desc_table ) {
+ if( isset( $desc_table[$status] ) ) {
+ return $desc_table[$status];
+ } else {
+ return "STATUS " . $status;
+ }
+ }
+ protected function log_changed( $old, $new, $watched ) {
+ wfDebug(sprintf($watched["desc"],
+ $this->mConn,
+ $this->describe_changed( $old, $watched["states"] ),
+ $this->describe_changed( $new, $watched["states"] ))
+ );
+ }
+}
+
+/**
+ * Manage savepoints within a transaction
+ * @ingroup Database
+ * @since 1.19
+ */
+class SavepointPostgres {
+ /**
+ * Establish a savepoint within a transaction
+ */
+ protected $dbw;
+ protected $id;
+ protected $didbegin;
+
+ public function __construct ($dbw, $id) {
+ $this->dbw = $dbw;
+ $this->id = $id;
+ $this->didbegin = false;
+ /* If we are not in a transaction, we need to be for savepoint trickery */
+ if ( !$dbw->trxLevel() ) {
+ $dbw->begin( "FOR SAVEPOINT" );
+ $this->didbegin = true;
+ }
+ }
+
+ public function __destruct() {
+ if ( $this->didbegin ) {
+ $this->dbw->rollback();
+ }
+ }
+
+ public function commit() {
+ if ( $this->didbegin ) {
+ $this->dbw->commit();
+ }
+ }
+
+ protected function query( $keyword, $msg_ok, $msg_failed ) {
+ global $wgDebugDBTransactions;
+ if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
+ if ( $wgDebugDBTransactions ) {
+ wfDebug( sprintf ($msg_ok, $this->id ) );
+ }
+ } else {
+ wfDebug( sprintf ($msg_failed, $this->id ) );
+ }
+ }
+
+ public function savepoint() {
+ $this->query("SAVEPOINT",
+ "Transaction state: savepoint \"%s\" established.\n",
+ "Transaction state: establishment of savepoint \"%s\" FAILED.\n"
+ );
+ }
+
+ public function 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",
+ "Transaction state: savepoint \"%s\" rolled back.\n",
+ "Transaction state: rollback of savepoint \"%s\" FAILED.\n"
+ );
+ }
+
+ public function __toString() {
+ return (string)$this->id;
+ }
}
/**
@@ -136,15 +317,15 @@ class DatabasePostgres extends DatabaseBase {
}
function hasConstraint( $name ) {
- global $wgDBmwschema;
$SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" .
- pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $wgDBmwschema ) ."'";
+ pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $this->getCoreSchema() ) ."'";
$res = $this->doQuery( $SQL );
return $this->numRows( $res );
}
/**
* Usually aborts on failure
+ * @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
# Test for Postgres support, to avoid suppressed fatal error
@@ -158,7 +339,6 @@ class DatabasePostgres extends DatabaseBase {
return;
}
- $this->close();
$this->mServer = $server;
$port = $wgDBport;
$this->mUser = $user;
@@ -176,10 +356,14 @@ class DatabasePostgres extends DatabaseBase {
if ( $port != false && $port != '' ) {
$connectVars['port'] = $port;
}
- $connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
+ if ( $this->mFlags & DBO_SSL ) {
+ $connectVars['sslmode'] = 1;
+ }
+ $this->connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
+ $this->close();
$this->installErrorHandler();
- $this->mConn = pg_connect( $connectString );
+ $this->mConn = pg_connect( $this->connectString );
$phpError = $this->restoreErrorHandler();
if ( !$this->mConn ) {
@@ -190,6 +374,7 @@ class DatabasePostgres extends DatabaseBase {
}
$this->mOpened = true;
+ $this->mTransactionState = new PostgresTransactionState( $this->mConn );
global $wgCommandLineMode;
# If called from the command-line (e.g. importDump), only show errors
@@ -203,12 +388,7 @@ class DatabasePostgres extends DatabaseBase {
$this->query( "SET standard_conforming_strings = on", __METHOD__ );
global $wgDBmwschema;
- if ( $this->schemaExists( $wgDBmwschema ) ) {
- $safeschema = $this->addIdentifierQuotes( $wgDBmwschema );
- $this->doQuery( "SET search_path = $safeschema" );
- } else {
- $this->doQuery( "SET search_path = public" );
- }
+ $this->determineCoreSchema( $wgDBmwschema );
return $this->mConn;
}
@@ -237,25 +417,62 @@ class DatabasePostgres extends DatabaseBase {
/**
* Closes a database connection, if it is open
* Returns success, true if already closed
+ * @return bool
*/
- function close() {
- $this->mOpened = false;
- if ( $this->mConn ) {
- return pg_close( $this->mConn );
- } else {
- return true;
- }
+ protected function closeConnection() {
+ return pg_close( $this->mConn );
}
- protected function doQuery( $sql ) {
+ public function doQuery( $sql ) {
if ( function_exists( 'mb_convert_encoding' ) ) {
$sql = mb_convert_encoding( $sql, 'UTF-8' );
}
- $this->mLastResult = pg_query( $this->mConn, $sql );
- $this->mAffectedRows = null; // use pg_affected_rows(mLastResult)
+ $this->mTransactionState->check();
+ if( pg_send_query( $this->mConn, $sql ) === false ) {
+ throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
+ }
+ $this->mLastResult = pg_get_result( $this->mConn );
+ $this->mTransactionState->check();
+ $this->mAffectedRows = null;
+ if ( pg_result_error( $this->mLastResult ) ) {
+ return false;
+ }
return $this->mLastResult;
}
+ protected function dumpError () {
+ $diags = array( PGSQL_DIAG_SEVERITY,
+ PGSQL_DIAG_SQLSTATE,
+ PGSQL_DIAG_MESSAGE_PRIMARY,
+ PGSQL_DIAG_MESSAGE_DETAIL,
+ PGSQL_DIAG_MESSAGE_HINT,
+ PGSQL_DIAG_STATEMENT_POSITION,
+ PGSQL_DIAG_INTERNAL_POSITION,
+ PGSQL_DIAG_INTERNAL_QUERY,
+ PGSQL_DIAG_CONTEXT,
+ PGSQL_DIAG_SOURCE_FILE,
+ PGSQL_DIAG_SOURCE_LINE,
+ PGSQL_DIAG_SOURCE_FUNCTION );
+ foreach ( $diags as $d ) {
+ wfDebug( sprintf("PgSQL ERROR(%d): %s\n", $d, pg_result_error_field( $this->mLastResult, $d ) ) );
+ }
+ }
+
+ function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+ /* Transaction stays in the ERROR state until rolledback */
+ if ( $tempIgnore ) {
+ /* Check for constraint violation */
+ if ( $errno === '23505' ) {
+ parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
+ return;
+ }
+ }
+ /* Don't ignore serious errors */
+ $this->rollback( __METHOD__ );
+ parent::reportQueryError( $error, $errno, $sql, $fname, false );
+ }
+
+
function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) {
return $this->query( $sql, $fname, true );
}
@@ -331,6 +548,7 @@ class DatabasePostgres extends DatabaseBase {
/**
* This must be called after nextSequenceVal
+ * @return null
*/
function insertId() {
return $this->mInsertId;
@@ -345,13 +563,21 @@ class DatabasePostgres extends DatabaseBase {
function lastError() {
if ( $this->mConn ) {
- return pg_last_error();
+ if ( $this->mLastResult ) {
+ return pg_result_error( $this->mLastResult );
+ } else {
+ return pg_last_error();
+ }
} else {
return 'No database connection';
}
}
function lastErrno() {
- return pg_last_error() ? 1 : 0;
+ if ( $this->mLastResult ) {
+ return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
+ } else {
+ return false;
+ }
}
function affectedRows() {
@@ -371,6 +597,7 @@ class DatabasePostgres extends DatabaseBase {
* This is not necessarily an accurate estimate, so use sparingly
* Returns -1 if count cannot be found
* Takes same arguments as Database::select()
+ * @return int
*/
function estimateRowCount( $table, $vars = '*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
@@ -389,6 +616,7 @@ class DatabasePostgres extends DatabaseBase {
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
+ * @return bool|null
*/
function indexInfo( $table, $index, $fname = 'DatabasePostgres::indexInfo' ) {
$sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
@@ -404,6 +632,68 @@ class DatabasePostgres extends DatabaseBase {
return false;
}
+ /**
+ * Returns is of attributes used in index
+ *
+ * @since 1.19
+ * @return Array
+ */
+ function indexAttributes ( $index, $schema = false ) {
+ if ( $schema === false )
+ $schema = $this->getCoreSchema();
+ /*
+ * A subquery would be not needed if we didn't care about the order
+ * of attributes, but we do
+ */
+ $sql = <<<__INDEXATTR__
+
+ SELECT opcname,
+ attname,
+ i.indoption[s.g] as option,
+ pg_am.amname
+ FROM
+ (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
+ FROM
+ pg_index isub
+ JOIN pg_class cis
+ ON cis.oid=isub.indexrelid
+ JOIN pg_namespace ns
+ ON cis.relnamespace = ns.oid
+ WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
+ pg_attribute,
+ pg_opclass opcls,
+ pg_am,
+ pg_class ci
+ JOIN pg_index i
+ ON ci.oid=i.indexrelid
+ JOIN pg_class ct
+ ON ct.oid = i.indrelid
+ JOIN pg_namespace n
+ ON ci.relnamespace = n.oid
+ WHERE
+ ci.relname='$index' AND n.nspname='$schema'
+ AND attrelid = ct.oid
+ AND i.indkey[s.g] = attnum
+ AND i.indclass[s.g] = opcls.oid
+ AND pg_am.oid = opcls.opcmethod
+__INDEXATTR__;
+ $res = $this->query($sql, __METHOD__);
+ $a = array();
+ if ( $res ) {
+ foreach ( $res as $row ) {
+ $a[] = array(
+ $row->attname,
+ $row->opcname,
+ $row->amname,
+ $row->option);
+ }
+ } else {
+ return null;
+ }
+ return $a;
+ }
+
+
function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
$sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
" AND indexdef LIKE 'CREATE UNIQUE%(" .
@@ -455,15 +745,9 @@ class DatabasePostgres extends DatabaseBase {
}
// If IGNORE is set, we use savepoints to emulate mysql's behavior
- $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
-
- // If we are not in a transaction, we need to be for savepoint trickery
- $didbegin = 0;
- if ( $ignore ) {
- if ( !$this->mTrxLevel ) {
- $this->begin();
- $didbegin = 1;
- }
+ $savepoint = null;
+ if ( in_array( 'IGNORE', $options ) ) {
+ $savepoint = new SavepointPostgres( $this, 'mw' );
$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
@@ -473,7 +757,7 @@ class DatabasePostgres extends DatabaseBase {
$sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
if ( $multi ) {
- if ( $this->numeric_version >= 8.2 && !$ignore ) {
+ if ( $this->numeric_version >= 8.2 && !$savepoint ) {
$first = true;
foreach ( $args as $row ) {
if ( $first ) {
@@ -483,7 +767,7 @@ class DatabasePostgres extends DatabaseBase {
}
$sql .= '(' . $this->makeList( $row ) . ')';
}
- $res = (bool)$this->query( $sql, $fname, $ignore );
+ $res = (bool)$this->query( $sql, $fname, $savepoint );
} else {
$res = true;
$origsql = $sql;
@@ -491,18 +775,18 @@ class DatabasePostgres extends DatabaseBase {
$tempsql = $origsql;
$tempsql .= '(' . $this->makeList( $row ) . ')';
- if ( $ignore ) {
- pg_query( $this->mConn, "SAVEPOINT $ignore" );
+ if ( $savepoint ) {
+ $savepoint->savepoint();
}
- $tempres = (bool)$this->query( $tempsql, $fname, $ignore );
+ $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
- if ( $ignore ) {
+ if ( $savepoint ) {
$bar = pg_last_error();
if ( $bar != false ) {
- pg_query( $this->mConn, "ROLLBACK TO $ignore" );
+ $savepoint->rollback();
} else {
- pg_query( $this->mConn, "RELEASE $ignore" );
+ $savepoint->release();
$numrowsinserted++;
}
}
@@ -516,27 +800,25 @@ class DatabasePostgres extends DatabaseBase {
}
} else {
// Not multi, just a lone insert
- if ( $ignore ) {
- pg_query($this->mConn, "SAVEPOINT $ignore");
+ if ( $savepoint ) {
+ $savepoint->savepoint();
}
$sql .= '(' . $this->makeList( $args ) . ')';
- $res = (bool)$this->query( $sql, $fname, $ignore );
- if ( $ignore ) {
+ $res = (bool)$this->query( $sql, $fname, $savepoint );
+ if ( $savepoint ) {
$bar = pg_last_error();
if ( $bar != false ) {
- pg_query( $this->mConn, "ROLLBACK TO $ignore" );
+ $savepoint->rollback();
} else {
- pg_query( $this->mConn, "RELEASE $ignore" );
+ $savepoint->release();
$numrowsinserted++;
}
}
}
- if ( $ignore ) {
+ if ( $savepoint ) {
$olde = error_reporting( $olde );
- if ( $didbegin ) {
- $this->commit();
- }
+ $savepoint->commit();
// Set the affected row count for the whole operation
$this->mAffectedRows = $numrowsinserted;
@@ -555,18 +837,29 @@ class DatabasePostgres extends DatabaseBase {
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
* @todo FIXME: Implement this a little better (seperate select/insert)?
+ * @return bool
*/
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect',
$insertOptions = array(), $selectOptions = array() )
{
$destTable = $this->tableName( $destTable );
- // If IGNORE is set, we use savepoints to emulate mysql's behavior
- $ignore = in_array( 'IGNORE', $insertOptions ) ? 'mw' : '';
+ if( !is_array( $insertOptions ) ) {
+ $insertOptions = array( $insertOptions );
+ }
- if( is_array( $insertOptions ) ) {
- $insertOptions = implode( ' ', $insertOptions ); // FIXME: This is unused
+ /*
+ * If IGNORE is set, we use savepoints to emulate mysql's behavior
+ * Ignore LOW PRIORITY option, since it is MySQL-specific
+ */
+ $savepoint = null;
+ if ( in_array( 'IGNORE', $insertOptions ) ) {
+ $savepoint = new SavepointPostgres( $this, 'mw' );
+ $olde = error_reporting( 0 );
+ $numrowsinserted = 0;
+ $savepoint->savepoint();
}
+
if( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
}
@@ -577,18 +870,6 @@ class DatabasePostgres extends DatabaseBase {
$srcTable = $this->tableName( $srcTable );
}
- // If we are not in a transaction, we need to be for savepoint trickery
- $didbegin = 0;
- if ( $ignore ) {
- if( !$this->mTrxLevel ) {
- $this->begin();
- $didbegin = 1;
- }
- $olde = error_reporting( 0 );
- $numrowsinserted = 0;
- pg_query( $this->mConn, "SAVEPOINT $ignore");
- }
-
$sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
" SELECT $startOpts " . implode( ',', $varMap ) .
" FROM $srcTable $useIndex";
@@ -599,19 +880,17 @@ class DatabasePostgres extends DatabaseBase {
$sql .= " $tailOpts";
- $res = (bool)$this->query( $sql, $fname, $ignore );
- if( $ignore ) {
+ $res = (bool)$this->query( $sql, $fname, $savepoint );
+ if( $savepoint ) {
$bar = pg_last_error();
if( $bar != false ) {
- pg_query( $this->mConn, "ROLLBACK TO $ignore" );
+ $savepoint->rollback();
} else {
- pg_query( $this->mConn, "RELEASE $ignore" );
+ $savepoint->release();
$numrowsinserted++;
}
$olde = error_reporting( $olde );
- if( $didbegin ) {
- $this->commit();
- }
+ $savepoint->commit();
// Set the affected row count for the whole operation
$this->mAffectedRows = $numrowsinserted;
@@ -642,6 +921,7 @@ class DatabasePostgres extends DatabaseBase {
/**
* Return the next in a sequence, save the value for retrieval via insertId()
+ * @return null
*/
function nextSequenceValue( $seqName ) {
$safeseq = str_replace( "'", "''", $seqName );
@@ -653,6 +933,7 @@ class DatabasePostgres extends DatabaseBase {
/**
* Return the current value of a sequence. Assumes it has been nextval'ed in this session.
+ * @return
*/
function currentSequenceValue( $seqName ) {
$safeseq = str_replace( "'", "''", $seqName );
@@ -694,10 +975,8 @@ class DatabasePostgres extends DatabaseBase {
}
function listTables( $prefix = null, $fname = 'DatabasePostgres::listTables' ) {
- global $wgDBmwschema;
- $eschema = $this->addQuotes( $wgDBmwschema );
+ $eschema = $this->addQuotes( $this->getCoreSchema() );
$result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
-
$endArray = array();
foreach( $result as $table ) {
@@ -715,10 +994,54 @@ class DatabasePostgres extends DatabaseBase {
return wfTimestamp( TS_POSTGRES, $ts );
}
+ /*
+ * Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12
+ * to http://www.php.net/manual/en/ref.pgsql.php
+ *
+ * Parsing a postgres array can be a tricky problem, he's my
+ * take on this, it handles multi-dimensional arrays plus
+ * escaping using a nasty regexp to determine the limits of each
+ * data-item.
+ *
+ * This should really be handled by PHP PostgreSQL module
+ *
+ * @since 1.19
+ * @param $text string: postgreql array returned in a text form like {a,b}
+ * @param $output string
+ * @param $limit int
+ * @param $offset int
+ * @return string
+ */
+ function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
+ if( false === $limit ) {
+ $limit = strlen( $text )-1;
+ $output = array();
+ }
+ if( '{}' == $text ) {
+ return $output;
+ }
+ do {
+ if ( '{' != $text{$offset} ) {
+ preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
+ $text, $match, 0, $offset );
+ $offset += strlen( $match[0] );
+ $output[] = ( '"' != $match[1]{0}
+ ? $match[1]
+ : stripcslashes( substr( $match[1], 1, -1 ) ) );
+ if ( '},' == $match[3] ) {
+ return $output;
+ }
+ } else {
+ $offset = $this->pg_array_parse( $text, $output, $limit, $offset+1 );
+ }
+ } while ( $limit > $offset );
+ return $output;
+ }
+
/**
* Return aggregated value function call
*/
- function aggregateValue( $valuedata, $valuename = 'value' ) {
+ public function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
}
@@ -729,6 +1052,115 @@ class DatabasePostgres extends DatabaseBase {
return '[http://www.postgresql.org/ PostgreSQL]';
}
+
+ /**
+ * Return current schema (executes SELECT current_schema())
+ * Needs transaction
+ *
+ * @since 1.19
+ * @return string return default schema for the current session
+ */
+ function getCurrentSchema() {
+ $res = $this->query( "SELECT current_schema()", __METHOD__);
+ $row = $this->fetchRow( $res );
+ return $row[0];
+ }
+
+ /**
+ * Return list of schemas which are accessible without schema name
+ * This is list does not contain magic keywords like "$user"
+ * Needs transaction
+ *
+ * @seealso getSearchPath()
+ * @seealso setSearchPath()
+ * @since 1.19
+ * @return array list of actual schemas for the current sesson
+ */
+ function getSchemas() {
+ $res = $this->query( "SELECT current_schemas(false)", __METHOD__);
+ $row = $this->fetchRow( $res );
+ $schemas = array();
+ /* PHP pgsql support does not support array type, "{a,b}" string is returned */
+ return $this->pg_array_parse($row[0], $schemas);
+ }
+
+ /**
+ * Return search patch for schemas
+ * This is different from getSchemas() since it contain magic keywords
+ * (like "$user").
+ * Needs transaction
+ *
+ * @since 1.19
+ * @return array how to search for table names schemas for the current user
+ */
+ function getSearchPath() {
+ $res = $this->query( "SHOW search_path", __METHOD__);
+ $row = $this->fetchRow( $res );
+ /* PostgreSQL returns SHOW values as strings */
+ return explode(",", $row[0]);
+ }
+
+ /**
+ * Update search_path, values should already be sanitized
+ * Values may contain magic keywords like "$user"
+ * @since 1.19
+ *
+ * @param $search_path array list of schemas to be searched by default
+ */
+ function setSearchPath( $search_path ) {
+ $this->query( "SET search_path = " . implode(", ", $search_path) );
+ }
+
+ /**
+ * Determine default schema for MediaWiki core
+ * Adjust this session schema search path if desired schema exists
+ * and is not alread there.
+ *
+ * We need to have name of the core schema stored to be able
+ * to query database metadata.
+ *
+ * This will be also called by the installer after the schema is created
+ *
+ * @since 1.19
+ * @param $desired_schema string
+ */
+ function determineCoreSchema( $desired_schema ) {
+ $this->begin( __METHOD__ );
+ if ( $this->schemaExists( $desired_schema ) ) {
+ if ( in_array( $desired_schema, $this->getSchemas() ) ) {
+ $this->mCoreSchema = $desired_schema;
+ wfDebug("Schema \"" . $desired_schema . "\" already in the search path\n");
+ } else {
+ /**
+ * Prepend our schema (e.g. 'mediawiki') in front
+ * of the search path
+ * Fixes bug 15816
+ */
+ $search_path = $this->getSearchPath();
+ array_unshift( $search_path,
+ $this->addIdentifierQuotes( $desired_schema ));
+ $this->setSearchPath( $search_path );
+ $this->mCoreSchema = $desired_schema;
+ 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");
+ }
+ /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
+ $this->commit( __METHOD__ );
+ }
+
+ /**
+ * Return schema name fore core MediaWiki tables
+ *
+ * @since 1.19
+ * @return string core schema name
+ */
+ function getCoreSchema() {
+ return $this->mCoreSchema;
+ }
+
/**
* @return string Version information from the database
*/
@@ -752,14 +1184,14 @@ class DatabasePostgres extends DatabaseBase {
/**
* Query whether a given relation exists (in the given schema, or the
* default mw one if not given)
+ * @return bool
*/
function relationExists( $table, $types, $schema = false ) {
- global $wgDBmwschema;
if ( !is_array( $types ) ) {
$types = array( $types );
}
if ( !$schema ) {
- $schema = $wgDBmwschema;
+ $schema = $this->getCoreSchema();
}
$table = $this->realTableName( $table, 'raw' );
$etable = $this->addQuotes( $table );
@@ -775,6 +1207,7 @@ class DatabasePostgres extends DatabaseBase {
/**
* For backward compatibility, this function checks both tables and
* views.
+ * @return bool
*/
function tableExists( $table, $fname = __METHOD__, $schema = false ) {
return $this->relationExists( $table, array( 'r', 'v' ), $schema );
@@ -785,8 +1218,6 @@ class DatabasePostgres extends DatabaseBase {
}
function triggerExists( $table, $trigger ) {
- global $wgDBmwschema;
-
$q = <<<SQL
SELECT 1 FROM pg_class, pg_namespace, pg_trigger
WHERE relnamespace=pg_namespace.oid AND relkind='r'
@@ -796,7 +1227,7 @@ SQL;
$res = $this->query(
sprintf(
$q,
- $this->addQuotes( $wgDBmwschema ),
+ $this->addQuotes( $this->getCoreSchema() ),
$this->addQuotes( $table ),
$this->addQuotes( $trigger )
)
@@ -809,22 +1240,20 @@ SQL;
}
function ruleExists( $table, $rule ) {
- global $wgDBmwschema;
$exists = $this->selectField( 'pg_rules', 'rulename',
array(
'rulename' => $rule,
'tablename' => $table,
- 'schemaname' => $wgDBmwschema
+ 'schemaname' => $this->getCoreSchema()
)
);
return $exists === $rule;
}
function constraintExists( $table, $constraint ) {
- global $wgDBmwschema;
$SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints ".
"WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
- $this->addQuotes( $wgDBmwschema ),
+ $this->addQuotes( $this->getCoreSchema() ),
$this->addQuotes( $table ),
$this->addQuotes( $constraint )
);
@@ -838,6 +1267,7 @@ SQL;
/**
* Query whether a given schema exists. Returns true if it does, false if it doesn't.
+ * @return bool
*/
function schemaExists( $schema ) {
$exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
@@ -847,6 +1277,7 @@ SQL;
/**
* Returns true if a given role (i.e. user) exists, false otherwise.
+ * @return bool
*/
function roleExists( $roleName ) {
$exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
@@ -860,6 +1291,7 @@ SQL;
/**
* pg_field_type() wrapper
+ * @return string
*/
function fieldType( $res, $index ) {
if ( $res instanceof ResultWrapper ) {
@@ -868,11 +1300,6 @@ SQL;
return pg_field_type( $res, $index );
}
- /* Not even sure why this is used in the main codebase... */
- function limitResultForUpdate( $sql, $num ) {
- return $sql;
- }
-
/**
* @param $b
* @return Blob
@@ -979,9 +1406,6 @@ SQL;
if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
$postLimitTail .= ' FOR UPDATE';
}
- if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
- $postLimitTail .= ' LOCK IN SHARE MODE';
- }
if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
$startOpts .= 'DISTINCT';
}
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index b2eb1c6b..f1e553d7 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -3,6 +3,21 @@
* This is the SQLite database abstraction layer.
* See maintenance/sqlite/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
*/
@@ -88,7 +103,7 @@ class DatabaseSqlite extends DatabaseBase {
*
* @param $fileName string
*
- * @return PDO|false SQL connection or false if failed
+ * @return PDO|bool SQL connection or false if failed
*/
function openFile( $fileName ) {
$this->mDatabaseFile = $fileName;
@@ -115,16 +130,11 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * Close an SQLite database
- *
+ * Does not actually close the connection, just destroys the reference for GC to do its work
* @return bool
*/
- function close() {
- $this->mOpened = false;
- if ( is_object( $this->mConn ) ) {
- if ( $this->trxLevel() ) $this->commit();
- $this->mConn = null;
- }
+ protected function closeConnection() {
+ $this->mConn = null;
return true;
}
@@ -140,7 +150,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Check if the searchindext table is FTS enabled.
- * @return false if not enabled.
+ * @return bool False if not enabled.
*/
function checkForEnabledSearch() {
if ( self::$fulltextEnabled === null ) {
@@ -166,7 +176,7 @@ class DatabaseSqlite extends DatabaseBase {
}
$cachedResult = false;
$table = 'dummy_search_test';
-
+
$db = new DatabaseSqliteStandalone( ':memory:' );
if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
@@ -303,7 +313,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* @param $res ResultWrapper
- * @param $n
+ * @param $n
* @return bool
*/
function fieldName( $res, $n ) {
@@ -347,7 +357,8 @@ class DatabaseSqlite extends DatabaseBase {
* @return int
*/
function insertId() {
- return $this->mConn->lastInsertId();
+ // PDO::lastInsertId yields a string :(
+ return intval( $this->mConn->lastInsertId() );
}
/**
@@ -497,6 +508,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Based on generic method (parent) with some prior SQLite-sepcific adjustments
+ * @return bool
*/
function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) {
if ( !count( $a ) ) {
@@ -610,14 +622,16 @@ class DatabaseSqlite extends DatabaseBase {
* @return string User-friendly database information
*/
public function getServerInfo() {
- return wfMsg( self::getFulltextSearchModule() ? 'sqlite-has-fts' : 'sqlite-no-fts', $this->getServerVersion() );
+ return wfMessage( self::getFulltextSearchModule() ? 'sqlite-has-fts' : 'sqlite-no-fts', $this->getServerVersion() )->text();
}
/**
* Get information about a given field
* Returns false if the field does not exist.
*
- * @return SQLiteField|false
+ * @param $table string
+ * @param $field string
+ * @return SQLiteField|bool False on failure
*/
function fieldInfo( $table, $field ) {
$tableName = $this->tableName( $table );
@@ -631,15 +645,15 @@ class DatabaseSqlite extends DatabaseBase {
return false;
}
- function begin( $fname = '' ) {
+ protected function doBegin( $fname = '' ) {
if ( $this->mTrxLevel == 1 ) {
- $this->commit();
+ $this->commit( __METHOD__ );
}
$this->mConn->beginTransaction();
$this->mTrxLevel = 1;
}
- function commit( $fname = '' ) {
+ protected function doCommit( $fname = '' ) {
if ( $this->mTrxLevel == 0 ) {
return;
}
@@ -647,7 +661,7 @@ class DatabaseSqlite extends DatabaseBase {
$this->mTrxLevel = 0;
}
- function rollback( $fname = '' ) {
+ protected function doRollback( $fname = '' ) {
if ( $this->mTrxLevel == 0 ) {
return;
}
@@ -656,15 +670,6 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $sql
- * @param $num
- * @return string
- */
- function limitResultForUpdate( $sql, $num ) {
- return $this->limitResult( $sql, $num );
- }
-
- /**
* @param $s string
* @return string
*/
@@ -723,6 +728,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* No-op version of deadlockLoop
+ * @return mixed
*/
public function deadlockLoop( /*...*/ ) {
$args = func_get_args();
@@ -812,12 +818,12 @@ class DatabaseSqlite extends DatabaseBase {
}
return $this->query( $sql, $fname );
}
-
-
+
+
/**
* List all tables on the database
*
- * @param $prefix Only show tables with this prefix, e.g. mw_
+ * @param $prefix string Only show tables with this prefix, e.g. mw_
* @param $fname String: calling function name
*
* @return array
@@ -828,21 +834,21 @@ class DatabaseSqlite extends DatabaseBase {
'name',
"type='table'"
);
-
+
$endArray = array();
-
- foreach( $result as $table ) {
+
+ foreach( $result as $table ) {
$vars = get_object_vars($table);
$table = array_pop( $vars );
-
+
if( !$prefix || strpos( $table, $prefix ) === 0 ) {
if ( strpos( $table, 'sqlite_' ) !== 0 ) {
$endArray[] = $table;
}
-
+
}
}
-
+
return $endArray;
}
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
index 0ea713c8..c846788d 100644
--- a/includes/db/DatabaseUtility.php
+++ b/includes/db/DatabaseUtility.php
@@ -1,5 +1,27 @@
<?php
/**
+ * This file contains database-related utiliy 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
+ * 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
+ */
+
+/**
* Utility class.
* @ingroup Database
*/
@@ -220,7 +242,11 @@ class FakeResultWrapper extends ResultWrapper {
$this->currentRow = false;
}
$this->pos++;
- return $this->currentRow;
+ if ( is_object( $this->currentRow ) ) {
+ return get_object_vars( $this->currentRow );
+ } else {
+ return $this->currentRow;
+ }
}
function seek( $row ) {
diff --git a/includes/db/IORMRow.php b/includes/db/IORMRow.php
new file mode 100644
index 00000000..e99ba6cc
--- /dev/null
+++ b/includes/db/IORMRow.php
@@ -0,0 +1,275 @@
+<?php
+/**
+ * Interface for representing objects that are stored in some DB table.
+ * This is basically an ORM-like wrapper around rows in database tables that
+ * aims to be both simple and very flexible. It is centered around an associative
+ * array of fields and various methods to do common interaction with the database.
+ *
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * 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
+ *
+ * @since 1.20
+ *
+ * @file
+ * @ingroup ORM
+ *
+ * @licence GNU GPL v2 or later
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+
+interface IORMRow {
+
+
+ /**
+ * Constructor.
+ *
+ * @since 1.20
+ *
+ * @param IORMTable $table
+ * @param array|null $fields
+ * @param boolean $loadDefaults
+ */
+ public function __construct( IORMTable $table, $fields = null, $loadDefaults = false );
+
+ /**
+ * Load the specified fields from the database.
+ *
+ * @since 1.20
+ *
+ * @param array|null $fields
+ * @param boolean $override
+ * @param boolean $skipLoaded
+ *
+ * @return bool Success indicator
+ */
+ public function loadFields( $fields = null, $override = true, $skipLoaded = false );
+
+ /**
+ * Gets the value of a field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @throws MWException
+ * @return mixed
+ */
+ public function getField( $name, $default = null );
+
+ /**
+ * Gets the value of a field but first loads it if not done so already.
+ *
+ * @since 1.20
+ *
+ * @param string$name
+ *
+ * @return mixed
+ */
+ public function loadAndGetField( $name );
+
+ /**
+ * Remove a field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ */
+ public function removeField( $name );
+
+ /**
+ * Returns the objects database id.
+ *
+ * @since 1.20
+ *
+ * @return integer|null
+ */
+ public function getId();
+
+ /**
+ * Sets the objects database id.
+ *
+ * @since 1.20
+ *
+ * @param integer|null $id
+ */
+ public function setId( $id );
+
+ /**
+ * Gets if a certain field is set.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ *
+ * @return boolean
+ */
+ public function hasField( $name );
+
+ /**
+ * Gets if the id field is set.
+ *
+ * @since 1.20
+ *
+ * @return boolean
+ */
+ public function hasIdField();
+
+ /**
+ * Sets multiple fields.
+ *
+ * @since 1.20
+ *
+ * @param array $fields The fields to set
+ * @param boolean $override Override already set fields with the provided values?
+ */
+ public function setFields( array $fields, $override = true );
+
+ /**
+ * Serializes the object to an associative array which
+ * can then easily be converted into JSON or similar.
+ *
+ * @since 1.20
+ *
+ * @param null|array $fields
+ * @param boolean $incNullId
+ *
+ * @return array
+ */
+ public function toArray( $fields = null, $incNullId = false );
+
+ /**
+ * Load the default values, via getDefaults.
+ *
+ * @since 1.20
+ *
+ * @param boolean $override
+ */
+ public function loadDefaults( $override = true );
+
+ /**
+ * Writes the answer to the database, either updating it
+ * when it already exists, or inserting it when it doesn't.
+ *
+ * @since 1.20
+ *
+ * @param string|null $functionName
+ *
+ * @return boolean Success indicator
+ */
+ public function save( $functionName = null );
+
+ /**
+ * Removes the object from the database.
+ *
+ * @since 1.20
+ *
+ * @return boolean Success indicator
+ */
+ public function remove();
+
+ /**
+ * Return the names and values of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFields();
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getSetFieldNames();
+
+ /**
+ * Sets the value of a field.
+ * Strings can be provided for other types,
+ * so this method can be called from unserialization handlers.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ * @param mixed $value
+ *
+ * @throws MWException
+ */
+ public function setField( $name, $value );
+
+ /**
+ * Add an amount (can be negative) to the specified field (needs to be numeric).
+ * TODO: most off this stuff makes more sense in the table class
+ *
+ * @since 1.20
+ *
+ * @param string $field
+ * @param integer $amount
+ *
+ * @return boolean Success indicator
+ */
+ public function addToField( $field, $amount );
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldNames();
+
+ /**
+ * Computes and updates the values of the summary fields.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $summaryFields
+ */
+ public function loadSummaryFields( $summaryFields = null );
+
+ /**
+ * Sets the value for the @see $updateSummaries field.
+ *
+ * @since 1.20
+ *
+ * @param boolean $update
+ */
+ public function setUpdateSummaries( $update );
+
+ /**
+ * Sets the value for the @see $inSummaryMode field.
+ *
+ * @since 1.20
+ *
+ * @param boolean $summaryMode
+ */
+ public function setSummaryMode( $summaryMode );
+
+ /**
+ * Returns the table this IORMRow is a row in.
+ *
+ * @since 1.20
+ *
+ * @return IORMTable
+ */
+ public function getTable();
+
+} \ No newline at end of file
diff --git a/includes/db/IORMTable.php b/includes/db/IORMTable.php
new file mode 100644
index 00000000..99413f99
--- /dev/null
+++ b/includes/db/IORMTable.php
@@ -0,0 +1,448 @@
+<?php
+/**
+ * Interface for objects representing a single database table.
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * 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
+ *
+ * @since 1.20
+ *
+ * @file
+ * @ingroup ORM
+ *
+ * @licence GNU GPL v2 or later
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+
+interface IORMTable {
+
+ /**
+ * Returns the name of the database table objects of this type are stored in.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Returns the name of a IORMRow implementing class that
+ * represents single rows in this table.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ public function getRowClass();
+
+ /**
+ * Returns an array with the fields and their types this object contains.
+ * This corresponds directly to the fields in the database, without prefix.
+ *
+ * field name => type
+ *
+ * Allowed types:
+ * * id
+ * * str
+ * * int
+ * * float
+ * * bool
+ * * array
+ * * blob
+ *
+ * TODO: get rid of the id field. Every row instance needs to have
+ * one so this is just causing hassle at various locations by requiring an extra check for field name.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFields();
+
+ /**
+ * Returns a list of default field values.
+ * field name => field value
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getDefaults();
+
+ /**
+ * Returns a list of the summary fields.
+ * These are fields that cache computed values, such as the amount of linked objects of $type.
+ * This is relevant as one might not want to do actions such as log changes when these get updated.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getSummaryFields();
+
+ /**
+ * Selects the the specified fields of the records matching the provided
+ * conditions and returns them as DBDataObject. Field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return ORMResult
+ */
+ public function select( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Selects the the specified fields of the records matching the provided
+ * conditions and returns them as DBDataObject. Field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return array of self
+ */
+ public function selectObjects( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Do the actual select.
+ *
+ * @since 1.20
+ *
+ * @param null|string|array $fields
+ * @param array $conditions
+ * @param array $options
+ * @param null|string $functionName
+ *
+ * @return ResultWrapper
+ */
+ public function rawSelect( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Selects the the specified fields of the records matching the provided
+ * conditions and returns them as associative arrays.
+ * Provided field names get prefixed.
+ * Returned field names will not have a prefix.
+ *
+ * When $collapse is true:
+ * If one field is selected, each item in the result array will be this field.
+ * If two fields are selected, each item in the result array will have as key
+ * the first field and as value the second field.
+ * If more then two fields are selected, each item will be an associative array.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param string|null $functionName
+ *
+ * @return array of array
+ */
+ public function selectFields( $fields = null, array $conditions = array(),
+ array $options = array(), $collapse = true, $functionName = null );
+
+ /**
+ * Selects the the specified fields of the first matching record.
+ * Field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return IORMRow|bool False on failure
+ */
+ public function selectRow( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Selects the the specified fields of the records matching the provided
+ * conditions. Field names do NOT get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return ResultWrapper
+ */
+ public function rawSelectRow( array $fields, array $conditions = array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Selects the the specified fields of the first record matching the provided
+ * conditions and returns it as an associative array, or false when nothing matches.
+ * This method makes use of selectFields and expects the same parameters and
+ * returns the same results (if there are any, if there are none, this method returns false).
+ * @see IORMTable::selectFields
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param string|null $functionName
+ *
+ * @return mixed|array|bool False on failure
+ */
+ public function selectFieldsRow( $fields = null, array $conditions = array(),
+ array $options = array(), $collapse = true, $functionName = null );
+
+ /**
+ * Returns if there is at least one record matching the provided conditions.
+ * Condition field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ *
+ * @return boolean
+ */
+ public function has( array $conditions = array() );
+
+ /**
+ * Returns the amount of matching records.
+ * Condition field names get prefixed.
+ *
+ * Note that this can be expensive on large tables.
+ * In such cases you might want to use DatabaseBase::estimateRowCount instead.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ * @param array $options
+ *
+ * @return integer
+ */
+ public function count( array $conditions = array(), array $options = array() );
+
+ /**
+ * Removes the object from the database.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ * @param string|null $functionName
+ *
+ * @return boolean Success indicator
+ */
+ public function delete( array $conditions, $functionName = null );
+
+ /**
+ * Get API parameters for the fields supported by this object.
+ *
+ * @since 1.20
+ *
+ * @param boolean $requireParams
+ * @param boolean $setDefaults
+ *
+ * @return array
+ */
+ public function getAPIParams( $requireParams = false, $setDefaults = false );
+
+ /**
+ * Returns an array with the fields and their descriptions.
+ *
+ * field name => field description
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldDescriptions();
+
+ /**
+ * Get the database type used for read operations.
+ *
+ * @since 1.20
+ *
+ * @return integer DB_ enum
+ */
+ public function getReadDb();
+
+ /**
+ * Set the database type to use for read operations.
+ *
+ * @param integer $db
+ *
+ * @since 1.20
+ */
+ public function setReadDb( $db );
+
+ /**
+ * Update the records matching the provided conditions by
+ * setting the fields that are keys in the $values param to
+ * their corresponding values.
+ *
+ * @since 1.20
+ *
+ * @param array $values
+ * @param array $conditions
+ *
+ * @return boolean Success indicator
+ */
+ public function update( array $values, array $conditions = array() );
+
+ /**
+ * Computes the values of the summary fields of the objects matching the provided conditions.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $summaryFields
+ * @param array $conditions
+ */
+ public function updateSummaryFields( $summaryFields = null, array $conditions = array() );
+
+ /**
+ * Takes in an associative array with field names as keys and
+ * their values as value. The field names are prefixed with the
+ * db field prefix.
+ *
+ * @since 1.20
+ *
+ * @param array $values
+ *
+ * @return array
+ */
+ public function getPrefixedValues( array $values );
+
+ /**
+ * Takes in a field or array of fields and returns an
+ * array with their prefixed versions, ready for db usage.
+ *
+ * @since 1.20
+ *
+ * @param array|string $fields
+ *
+ * @return array
+ */
+ public function getPrefixedFields( array $fields );
+
+ /**
+ * Takes in a field and returns an it's prefixed version, ready for db usage.
+ *
+ * @since 1.20
+ *
+ * @param string|array $field
+ *
+ * @return string
+ */
+ public function getPrefixedField( $field );
+
+ /**
+ * Takes an array of field names with prefix and returns the unprefixed equivalent.
+ *
+ * @since 1.20
+ *
+ * @param array $fieldNames
+ *
+ * @return array
+ */
+ public function unprefixFieldNames( array $fieldNames );
+
+ /**
+ * Takes a field name with prefix and returns the unprefixed equivalent.
+ *
+ * @since 1.20
+ *
+ * @param string $fieldName
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @since 1.20
+ *
+ * @param stdClass $result
+ *
+ * @return array
+ */
+ public function getFieldsFromDBResult( stdClass $result );
+
+ /**
+ * Get a new instance of the class from a database result.
+ *
+ * @since 1.20
+ *
+ * @param stdClass $result
+ *
+ * @return IORMRow
+ */
+ public function newRowFromDBResult( stdClass $result );
+
+ /**
+ * Get a new instance of the class from an array.
+ *
+ * @since 1.20
+ *
+ * @param array $data
+ * @param boolean $loadDefaults
+ *
+ * @return IORMRow
+ */
+ public function newRow( array $data, $loadDefaults = false );
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldNames();
+
+ /**
+ * Gets if the object can take a certain field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ *
+ * @return boolean
+ */
+ public function canHaveField( $name );
+
+}
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index dec6ae16..e82c54ba 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -1,6 +1,21 @@
<?php
/**
- * Generator of database load balancing objects
+ * Generator of database load balancing objects.
+ *
+ * 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
@@ -176,6 +191,16 @@ class LBFactory_Simple extends LBFactory {
$servers = $wgDBservers;
} else {
global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
+ global $wgDBssl, $wgDBcompress;
+
+ $flags = ( $wgDebugDumpSql ? DBO_DEBUG : 0 ) | DBO_DEFAULT;
+ if ( $wgDBssl ) {
+ $flags |= DBO_SSL;
+ }
+ if ( $wgDBcompress ) {
+ $flags |= DBO_COMPRESS;
+ }
+
$servers = array(array(
'host' => $wgDBserver,
'user' => $wgDBuser,
@@ -183,7 +208,7 @@ class LBFactory_Simple extends LBFactory {
'dbname' => $wgDBname,
'type' => $wgDBtype,
'load' => 1,
- 'flags' => ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT
+ 'flags' => $flags
));
}
diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactory_Multi.php
index b7977a21..6008813b 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactory_Multi.php
@@ -1,6 +1,21 @@
<?php
/**
- * Advanced generator of database load balancing objects for wiki farms
+ * Advanced generator of database load balancing objects for wiki farms.
+ *
+ * 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
diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactory_Single.php
index f80aa4bc..4b165b2a 100644
--- a/includes/db/LBFactory_Single.php
+++ b/includes/db/LBFactory_Single.php
@@ -1,4 +1,25 @@
<?php
+/**
+ * Simple generator of database connections that always returns the same object.
+ *
+ * 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
+ */
/**
* An LBFactory class that always returns a single database object.
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index e96c6720..0e455e0c 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -1,6 +1,21 @@
<?php
/**
- * Database load balancing
+ * Database load balancing.
+ *
+ * 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
@@ -91,7 +106,7 @@ class LoadBalancer {
/**
* Get or set arbitrary data used by the parent object, usually an LBFactory
* @param $x
- * @return \Mixed
+ * @return Mixed
*/
function parentInfo( $x = null ) {
return wfSetVar( $this->mParentInfo, $x );
@@ -385,7 +400,7 @@ class LoadBalancer {
* Returns false if there is no connection open
*
* @param $i int
- * @return DatabaseBase|false
+ * @return DatabaseBase|bool False on failure
*/
function getAnyOpenConnection( $i ) {
foreach ( $this->mConns as $conns ) {
@@ -894,7 +909,7 @@ class LoadBalancer {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
foreach ( $conns3 as $conn ) {
- $conn->commit();
+ $conn->commit( __METHOD__ );
}
}
}
@@ -911,7 +926,7 @@ class LoadBalancer {
continue;
}
foreach ( $conns2[$masterIndex] as $conn ) {
- if ( $conn->doneWrites() ) {
+ if ( $conn->writesOrCallbacksPending() ) {
$conn->commit( __METHOD__ );
}
}
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 16a0343f..146ac61e 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -1,6 +1,21 @@
<?php
/**
- * Database load monitoring
+ * Database load monitoring.
+ *
+ * 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
diff --git a/includes/db/ORMIterator.php b/includes/db/ORMIterator.php
new file mode 100644
index 00000000..090b8932
--- /dev/null
+++ b/includes/db/ORMIterator.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Interface for Iterators containing IORMRows.
+ *
+ * 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
+ *
+ * @since 1.20
+ *
+ * @file
+ * @ingroup ORM
+ *
+ * @licence 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
new file mode 100644
index 00000000..2a5837a1
--- /dev/null
+++ b/includes/db/ORMResult.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * ORMIterator that takes a ResultWrapper object returned from
+ * a select operation returning IORMRow objects (ie IORMTable::select).
+ *
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * 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
+ *
+ * @since 1.20
+ *
+ * @file ORMResult.php
+ * @ingroup ORM
+ *
+ * @licence GNU GPL v2 or later
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+
+class ORMResult implements ORMIterator {
+
+ /**
+ * @var ResultWrapper
+ */
+ protected $res;
+
+ /**
+ * @var integer
+ */
+ protected $key;
+
+ /**
+ * @var IORMRow
+ */
+ protected $current;
+
+ /**
+ * @var IORMTable
+ */
+ protected $table;
+
+ /**
+ * @param IORMTable $table
+ * @param ResultWrapper $res
+ */
+ public function __construct( IORMTable $table, ResultWrapper $res ) {
+ $this->table = $table;
+ $this->res = $res;
+ $this->key = 0;
+ $this->setCurrent( $this->res->current() );
+ }
+
+ /**
+ * @param $row
+ */
+ protected function setCurrent( $row ) {
+ if ( $row === false ) {
+ $this->current = false;
+ } else {
+ $this->current = $this->table->newRowFromDBResult( $row );
+ }
+ }
+
+ /**
+ * @return integer
+ */
+ public function count() {
+ return $this->res->numRows();
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isEmpty() {
+ return $this->res->numRows() === 0;
+ }
+
+ /**
+ * @return IORMRow
+ */
+ public function current() {
+ return $this->current;
+ }
+
+ /**
+ * @return integer
+ */
+ public function key() {
+ return $this->key;
+ }
+
+ public function next() {
+ $row = $this->res->next();
+ $this->setCurrent( $row );
+ $this->key++;
+ }
+
+ public function rewind() {
+ $this->res->rewind();
+ $this->key = 0;
+ $this->setCurrent( $this->res->current() );
+ }
+
+ /**
+ * @return boolean
+ */
+ public function valid() {
+ return $this->current !== false;
+ }
+
+}
diff --git a/includes/db/ORMRow.php b/includes/db/ORMRow.php
new file mode 100644
index 00000000..303f3a20
--- /dev/null
+++ b/includes/db/ORMRow.php
@@ -0,0 +1,663 @@
+<?php
+/**
+ * Abstract base class for representing objects that are stored in some DB table.
+ * This is basically an ORM-like wrapper around rows in database tables that
+ * aims to be both simple and very flexible. It is centered around an associative
+ * array of fields and various methods to do common interaction with the database.
+ *
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * 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
+ *
+ * @since 1.20
+ *
+ * @file ORMRow.php
+ * @ingroup ORM
+ *
+ * @licence GNU GPL v2 or later
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+
+abstract class ORMRow implements IORMRow {
+
+ /**
+ * The fields of the object.
+ * field name (w/o prefix) => value
+ *
+ * @since 1.20
+ * @var array
+ */
+ protected $fields = array( 'id' => null );
+
+ /**
+ * @since 1.20
+ * @var ORMTable
+ */
+ protected $table;
+
+ /**
+ * If the object should update summaries of linked items when changed.
+ * For example, update the course_count field in universities when a course in courses is deleted.
+ * Settings this to false can prevent needless updating work in situations
+ * such as deleting a university, which will then delete all it's courses.
+ *
+ * @since 1.20
+ * @var bool
+ */
+ protected $updateSummaries = true;
+
+ /**
+ * Indicates if the object is in summary mode.
+ * This mode indicates that only summary fields got updated,
+ * which allows for optimizations.
+ *
+ * @since 1.20
+ * @var bool
+ */
+ protected $inSummaryMode = false;
+
+ /**
+ * Constructor.
+ *
+ * @since 1.20
+ *
+ * @param IORMTable $table
+ * @param array|null $fields
+ * @param boolean $loadDefaults
+ */
+ public function __construct( IORMTable $table, $fields = null, $loadDefaults = false ) {
+ $this->table = $table;
+
+ if ( !is_array( $fields ) ) {
+ $fields = array();
+ }
+
+ if ( $loadDefaults ) {
+ $fields = array_merge( $this->table->getDefaults(), $fields );
+ }
+
+ $this->setFields( $fields );
+ }
+
+ /**
+ * Load the specified fields from the database.
+ *
+ * @since 1.20
+ *
+ * @param array|null $fields
+ * @param boolean $override
+ * @param boolean $skipLoaded
+ *
+ * @return bool Success indicator
+ */
+ public function loadFields( $fields = null, $override = true, $skipLoaded = false ) {
+ if ( is_null( $this->getId() ) ) {
+ return false;
+ }
+
+ if ( is_null( $fields ) ) {
+ $fields = array_keys( $this->table->getFields() );
+ }
+
+ if ( $skipLoaded ) {
+ $fields = array_diff( $fields, array_keys( $this->fields ) );
+ }
+
+ if ( !empty( $fields ) ) {
+ $result = $this->table->rawSelectRow(
+ $this->table->getPrefixedFields( $fields ),
+ array( $this->table->getPrefixedField( 'id' ) => $this->getId() ),
+ array( 'LIMIT' => 1 )
+ );
+
+ if ( $result !== false ) {
+ $this->setFields( $this->table->getFieldsFromDBResult( $result ), $override );
+ return true;
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the value of a field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @throws MWException
+ * @return mixed
+ */
+ public function getField( $name, $default = null ) {
+ if ( $this->hasField( $name ) ) {
+ return $this->fields[$name];
+ } elseif ( !is_null( $default ) ) {
+ return $default;
+ } else {
+ throw new MWException( 'Attempted to get not-set field ' . $name );
+ }
+ }
+
+ /**
+ * Gets the value of a field but first loads it if not done so already.
+ *
+ * @since 1.20
+ *
+ * @param string$name
+ *
+ * @return mixed
+ */
+ public function loadAndGetField( $name ) {
+ if ( !$this->hasField( $name ) ) {
+ $this->loadFields( array( $name ) );
+ }
+
+ return $this->getField( $name );
+ }
+
+ /**
+ * Remove a field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ */
+ public function removeField( $name ) {
+ unset( $this->fields[$name] );
+ }
+
+ /**
+ * Returns the objects database id.
+ *
+ * @since 1.20
+ *
+ * @return integer|null
+ */
+ public function getId() {
+ return $this->getField( 'id' );
+ }
+
+ /**
+ * Sets the objects database id.
+ *
+ * @since 1.20
+ *
+ * @param integer|null $id
+ */
+ public function setId( $id ) {
+ $this->setField( 'id', $id );
+ }
+
+ /**
+ * Gets if a certain field is set.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ *
+ * @return boolean
+ */
+ public function hasField( $name ) {
+ return array_key_exists( $name, $this->fields );
+ }
+
+ /**
+ * Gets if the id field is set.
+ *
+ * @since 1.20
+ *
+ * @return boolean
+ */
+ public function hasIdField() {
+ return $this->hasField( 'id' )
+ && !is_null( $this->getField( 'id' ) );
+ }
+
+ /**
+ * Sets multiple fields.
+ *
+ * @since 1.20
+ *
+ * @param array $fields The fields to set
+ * @param boolean $override Override already set fields with the provided values?
+ */
+ public function setFields( array $fields, $override = true ) {
+ foreach ( $fields as $name => $value ) {
+ if ( $override || !$this->hasField( $name ) ) {
+ $this->setField( $name, $value );
+ }
+ }
+ }
+
+ /**
+ * Gets the fields => values to write to the table.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ protected function getWriteValues() {
+ $values = array();
+
+ foreach ( $this->table->getFields() as $name => $type ) {
+ if ( array_key_exists( $name, $this->fields ) ) {
+ $value = $this->fields[$name];
+
+ switch ( $type ) {
+ case 'array':
+ $value = (array)$value;
+ case 'blob':
+ $value = serialize( $value );
+ }
+
+ $values[$this->table->getPrefixedField( $name )] = $value;
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Serializes the object to an associative array which
+ * can then easily be converted into JSON or similar.
+ *
+ * @since 1.20
+ *
+ * @param null|array $fields
+ * @param boolean $incNullId
+ *
+ * @return array
+ */
+ public function toArray( $fields = null, $incNullId = false ) {
+ $data = array();
+ $setFields = array();
+
+ if ( !is_array( $fields ) ) {
+ $setFields = $this->getSetFieldNames();
+ } else {
+ foreach ( $fields as $field ) {
+ if ( $this->hasField( $field ) ) {
+ $setFields[] = $field;
+ }
+ }
+ }
+
+ foreach ( $setFields as $field ) {
+ if ( $incNullId || $field != 'id' || $this->hasIdField() ) {
+ $data[$field] = $this->getField( $field );
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Load the default values, via getDefaults.
+ *
+ * @since 1.20
+ *
+ * @param boolean $override
+ */
+ public function loadDefaults( $override = true ) {
+ $this->setFields( $this->table->getDefaults(), $override );
+ }
+
+ /**
+ * Writes the answer to the database, either updating it
+ * when it already exists, or inserting it when it doesn't.
+ *
+ * @since 1.20
+ *
+ * @param string|null $functionName
+ *
+ * @return boolean Success indicator
+ */
+ public function save( $functionName = null ) {
+ if ( $this->hasIdField() ) {
+ return $this->saveExisting( $functionName );
+ } else {
+ return $this->insert( $functionName );
+ }
+ }
+
+ /**
+ * Updates the object in the database.
+ *
+ * @since 1.20
+ *
+ * @param string|null $functionName
+ *
+ * @return boolean Success indicator
+ */
+ protected function saveExisting( $functionName = null ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $success = $dbw->update(
+ $this->table->getName(),
+ $this->getWriteValues(),
+ $this->table->getPrefixedValues( $this->getUpdateConditions() ),
+ is_null( $functionName ) ? __METHOD__ : $functionName
+ );
+
+ // DatabaseBase::update does not always return true for success as documented...
+ return $success !== false;
+ }
+
+ /**
+ * Returns the WHERE considtions needed to identify this object so
+ * it can be updated.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ protected function getUpdateConditions() {
+ return array( 'id' => $this->getId() );
+ }
+
+ /**
+ * Inserts the object into the database.
+ *
+ * @since 1.20
+ *
+ * @param string|null $functionName
+ * @param array|null $options
+ *
+ * @return boolean Success indicator
+ */
+ protected function insert( $functionName = null, array $options = null ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $success = $dbw->insert(
+ $this->table->getName(),
+ $this->getWriteValues(),
+ is_null( $functionName ) ? __METHOD__ : $functionName,
+ is_null( $options ) ? array( 'IGNORE' ) : $options
+ );
+
+ // DatabaseBase::insert does not always return true for success as documented...
+ $success = $success !== false;
+
+ if ( $success ) {
+ $this->setField( 'id', $dbw->insertId() );
+ }
+
+ return $success;
+ }
+
+ /**
+ * Removes the object from the database.
+ *
+ * @since 1.20
+ *
+ * @return boolean Success indicator
+ */
+ public function remove() {
+ $this->beforeRemove();
+
+ $success = $this->table->delete( array( 'id' => $this->getId() ) );
+
+ // DatabaseBase::delete does not always return true for success as documented...
+ $success = $success !== false;
+
+ if ( $success ) {
+ $this->onRemoved();
+ }
+
+ return $success;
+ }
+
+ /**
+ * Gets called before an object is removed from the database.
+ *
+ * @since 1.20
+ */
+ protected function beforeRemove() {
+ $this->loadFields( $this->getBeforeRemoveFields(), false, true );
+ }
+
+ /**
+ * Before removal of an object happens, @see beforeRemove gets called.
+ * This method loads the fields of which the names have been returned by this one (or all fields if null is returned).
+ * This allows for loading info needed after removal to get rid of linked data and the like.
+ *
+ * @since 1.20
+ *
+ * @return array|null
+ */
+ protected function getBeforeRemoveFields() {
+ return array();
+ }
+
+ /**
+ * Gets called after successfull removal.
+ * Can be overriden to get rid of linked data.
+ *
+ * @since 1.20
+ */
+ protected function onRemoved() {
+ $this->setField( 'id', null );
+ }
+
+ /**
+ * Return the names and values of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFields() {
+ return $this->fields;
+ }
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getSetFieldNames() {
+ return array_keys( $this->fields );
+ }
+
+ /**
+ * Sets the value of a field.
+ * Strings can be provided for other types,
+ * so this method can be called from unserialization handlers.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ * @param mixed $value
+ *
+ * @throws MWException
+ */
+ public function setField( $name, $value ) {
+ $fields = $this->table->getFields();
+
+ if ( array_key_exists( $name, $fields ) ) {
+ switch ( $fields[$name] ) {
+ case 'int':
+ $value = (int)$value;
+ break;
+ case 'float':
+ $value = (float)$value;
+ break;
+ case 'bool':
+ if ( is_string( $value ) ) {
+ $value = $value !== '0';
+ } elseif ( is_int( $value ) ) {
+ $value = $value !== 0;
+ }
+ break;
+ case 'array':
+ if ( is_string( $value ) ) {
+ $value = unserialize( $value );
+ }
+
+ if ( !is_array( $value ) ) {
+ $value = array();
+ }
+ break;
+ case 'blob':
+ if ( is_string( $value ) ) {
+ $value = unserialize( $value );
+ }
+ break;
+ case 'id':
+ if ( is_string( $value ) ) {
+ $value = (int)$value;
+ }
+ break;
+ }
+
+ $this->fields[$name] = $value;
+ } else {
+ throw new MWException( 'Attempted to set unknown field ' . $name );
+ }
+ }
+
+ /**
+ * Add an amount (can be negative) to the specified field (needs to be numeric).
+ * TODO: most off this stuff makes more sense in the table class
+ *
+ * @since 1.20
+ *
+ * @param string $field
+ * @param integer $amount
+ *
+ * @return boolean Success indicator
+ */
+ public function addToField( $field, $amount ) {
+ if ( $amount == 0 ) {
+ return true;
+ }
+
+ if ( !$this->hasIdField() ) {
+ return false;
+ }
+
+ $absoluteAmount = abs( $amount );
+ $isNegative = $amount < 0;
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $fullField = $this->table->getPrefixedField( $field );
+
+ $success = $dbw->update(
+ $this->table->getName(),
+ array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ),
+ array( $this->table->getPrefixedField( 'id' ) => $this->getId() ),
+ __METHOD__
+ );
+
+ if ( $success && $this->hasField( $field ) ) {
+ $this->setField( $field, $this->getField( $field ) + $amount );
+ }
+
+ return $success;
+ }
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldNames() {
+ return array_keys( $this->table->getFields() );
+ }
+
+ /**
+ * Computes and updates the values of the summary fields.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $summaryFields
+ */
+ public function loadSummaryFields( $summaryFields = null ) {
+
+ }
+
+ /**
+ * Sets the value for the @see $updateSummaries field.
+ *
+ * @since 1.20
+ *
+ * @param boolean $update
+ */
+ public function setUpdateSummaries( $update ) {
+ $this->updateSummaries = $update;
+ }
+
+ /**
+ * Sets the value for the @see $inSummaryMode field.
+ *
+ * @since 1.20
+ *
+ * @param boolean $summaryMode
+ */
+ public function setSummaryMode( $summaryMode ) {
+ $this->inSummaryMode = $summaryMode;
+ }
+
+ /**
+ * Return if any fields got changed.
+ *
+ * @since 1.20
+ *
+ * @param IORMRow $object
+ * @param boolean|array $excludeSummaryFields
+ * When set to true, summary field changes are ignored.
+ * Can also be an array of fields to ignore.
+ *
+ * @return boolean
+ */
+ protected function fieldsChanged( IORMRow $object, $excludeSummaryFields = false ) {
+ $exclusionFields = array();
+
+ if ( $excludeSummaryFields !== false ) {
+ $exclusionFields = is_array( $excludeSummaryFields ) ? $excludeSummaryFields : $this->table->getSummaryFields();
+ }
+
+ foreach ( $this->fields as $name => $value ) {
+ $excluded = $excludeSummaryFields && in_array( $name, $exclusionFields );
+
+ if ( !$excluded && $object->getField( $name ) !== $value ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the table this IORMRow is a row in.
+ *
+ * @since 1.20
+ *
+ * @return IORMTable
+ */
+ public function getTable() {
+ return $this->table;
+ }
+
+}
diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php
new file mode 100644
index 00000000..a77074ff
--- /dev/null
+++ b/includes/db/ORMTable.php
@@ -0,0 +1,675 @@
+<?php
+/**
+ * Abstract base class for representing a single database table.
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * 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
+ *
+ * @since 1.20
+ *
+ * @file ORMTable.php
+ * @ingroup ORM
+ *
+ * @licence GNU GPL v2 or later
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+
+abstract class ORMTable implements IORMTable {
+
+ /**
+ * Gets the db field prefix.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ protected abstract function getFieldPrefix();
+
+ /**
+ * Cache for instances, used by the singleton method.
+ *
+ * @since 1.20
+ * @var array of DBTable
+ */
+ protected static $instanceCache = array();
+
+ /**
+ * 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;
+
+ /**
+ * Returns a list of default field values.
+ * field name => field value
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getDefaults() {
+ return array();
+ }
+
+ /**
+ * Returns a list of the summary fields.
+ * These are fields that cache computed values, such as the amount of linked objects of $type.
+ * This is relevant as one might not want to do actions such as log changes when these get updated.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getSummaryFields() {
+ return array();
+ }
+
+ /**
+ * Selects the the specified fields of the records matching the provided
+ * conditions and returns them as DBDataObject. Field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @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 ) );
+ }
+
+ /**
+ * Selects the the specified fields of the records matching the provided
+ * conditions and returns them as DBDataObject. Field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return array of self
+ */
+ public function selectObjects( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null ) {
+ $result = $this->selectFields( $fields, $conditions, $options, false, $functionName );
+
+ $objects = array();
+
+ foreach ( $result as $record ) {
+ $objects[] = $this->newRow( $record );
+ }
+
+ return $objects;
+ }
+
+ /**
+ * Do the actual select.
+ *
+ * @since 1.20
+ *
+ * @param null|string|array $fields
+ * @param array $conditions
+ * @param array $options
+ * @param null|string $functionName
+ *
+ * @return ResultWrapper
+ */
+ public function rawSelect( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null ) {
+ if ( is_null( $fields ) ) {
+ $fields = array_keys( $this->getFields() );
+ }
+ else {
+ $fields = (array)$fields;
+ }
+
+ return wfGetDB( $this->getReadDb() )->select(
+ $this->getName(),
+ $this->getPrefixedFields( $fields ),
+ $this->getPrefixedValues( $conditions ),
+ is_null( $functionName ) ? __METHOD__ : $functionName,
+ $options
+ );
+ }
+
+ /**
+ * Selects the the specified fields of the records matching the provided
+ * conditions and returns them as associative arrays.
+ * Provided field names get prefixed.
+ * Returned field names will not have a prefix.
+ *
+ * When $collapse is true:
+ * If one field is selected, each item in the result array will be this field.
+ * If two fields are selected, each item in the result array will have as key
+ * the first field and as value the second field.
+ * If more then two fields are selected, each item will be an associative array.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param string|null $functionName
+ *
+ * @return array of array
+ */
+ public function selectFields( $fields = null, array $conditions = array(),
+ array $options = array(), $collapse = true, $functionName = null ) {
+ $objects = array();
+
+ $result = $this->rawSelect( $fields, $conditions, $options, $functionName );
+
+ foreach ( $result as $record ) {
+ $objects[] = $this->getFieldsFromDBResult( $record );
+ }
+
+ if ( $collapse ) {
+ if ( count( $fields ) === 1 ) {
+ $objects = array_map( 'array_shift', $objects );
+ }
+ elseif ( count( $fields ) === 2 ) {
+ $o = array();
+
+ foreach ( $objects as $object ) {
+ $o[array_shift( $object )] = array_shift( $object );
+ }
+
+ $objects = $o;
+ }
+ }
+
+ return $objects;
+ }
+
+ /**
+ * Selects the the specified fields of the first matching record.
+ * Field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return IORMRow|bool False on failure
+ */
+ public function selectRow( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null ) {
+ $options['LIMIT'] = 1;
+
+ $objects = $this->select( $fields, $conditions, $options, $functionName );
+
+ return $objects->isEmpty() ? false : $objects->current();
+ }
+
+ /**
+ * Selects the the specified fields of the records matching the provided
+ * conditions. Field names do NOT get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return ResultWrapper
+ */
+ public function rawSelectRow( array $fields, array $conditions = array(),
+ array $options = array(), $functionName = null ) {
+ $dbr = wfGetDB( $this->getReadDb() );
+
+ return $dbr->selectRow(
+ $this->getName(),
+ $fields,
+ $conditions,
+ is_null( $functionName ) ? __METHOD__ : $functionName,
+ $options
+ );
+ }
+
+ /**
+ * Selects the the specified fields of the first record matching the provided
+ * conditions and returns it as an associative array, or false when nothing matches.
+ * This method makes use of selectFields and expects the same parameters and
+ * returns the same results (if there are any, if there are none, this method returns false).
+ * @see ORMTable::selectFields
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param string|null $functionName
+ *
+ * @return mixed|array|bool False on failure
+ */
+ public function selectFieldsRow( $fields = null, array $conditions = array(),
+ array $options = array(), $collapse = true, $functionName = null ) {
+ $options['LIMIT'] = 1;
+
+ $objects = $this->selectFields( $fields, $conditions, $options, $collapse, $functionName );
+
+ return empty( $objects ) ? false : $objects[0];
+ }
+
+ /**
+ * Returns if there is at least one record matching the provided conditions.
+ * Condition field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ *
+ * @return boolean
+ */
+ public function has( array $conditions = array() ) {
+ return $this->selectRow( array( 'id' ), $conditions ) !== false;
+ }
+
+ /**
+ * Returns the amount of matching records.
+ * Condition field names get prefixed.
+ *
+ * Note that this can be expensive on large tables.
+ * In such cases you might want to use DatabaseBase::estimateRowCount instead.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ * @param array $options
+ *
+ * @return integer
+ */
+ public function count( array $conditions = array(), array $options = array() ) {
+ $res = $this->rawSelectRow(
+ array( 'rowcount' => 'COUNT(*)' ),
+ $this->getPrefixedValues( $conditions ),
+ $options
+ );
+
+ return $res->rowcount;
+ }
+
+ /**
+ * Removes the object from the database.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ * @param string|null $functionName
+ *
+ * @return boolean Success indicator
+ */
+ public function delete( array $conditions, $functionName = null ) {
+ return wfGetDB( DB_MASTER )->delete(
+ $this->getName(),
+ $conditions === array() ? '*' : $this->getPrefixedValues( $conditions ),
+ $functionName
+ ) !== false; // DatabaseBase::delete does not always return true for success as documented...
+ }
+
+ /**
+ * Get API parameters for the fields supported by this object.
+ *
+ * @since 1.20
+ *
+ * @param boolean $requireParams
+ * @param boolean $setDefaults
+ *
+ * @return array
+ */
+ public function getAPIParams( $requireParams = false, $setDefaults = false ) {
+ $typeMap = array(
+ 'id' => 'integer',
+ 'int' => 'integer',
+ 'float' => 'NULL',
+ 'str' => 'string',
+ 'bool' => 'integer',
+ 'array' => 'string',
+ 'blob' => 'string',
+ );
+
+ $params = array();
+ $defaults = $this->getDefaults();
+
+ foreach ( $this->getFields() as $field => $type ) {
+ if ( $field == 'id' ) {
+ continue;
+ }
+
+ $hasDefault = array_key_exists( $field, $defaults );
+
+ $params[$field] = array(
+ ApiBase::PARAM_TYPE => $typeMap[$type],
+ ApiBase::PARAM_REQUIRED => $requireParams && !$hasDefault
+ );
+
+ if ( $type == 'array' ) {
+ $params[$field][ApiBase::PARAM_ISMULTI] = true;
+ }
+
+ if ( $setDefaults && $hasDefault ) {
+ $default = is_array( $defaults[$field] ) ? implode( '|', $defaults[$field] ) : $defaults[$field];
+ $params[$field][ApiBase::PARAM_DFLT] = $default;
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Returns an array with the fields and their descriptions.
+ *
+ * field name => field description
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldDescriptions() {
+ return array();
+ }
+
+ /**
+ * Get the database type used for read operations.
+ *
+ * @since 1.20
+ *
+ * @return integer DB_ enum
+ */
+ public function getReadDb() {
+ return $this->readDb;
+ }
+
+ /**
+ * Set the database type to use for read operations.
+ *
+ * @param integer $db
+ *
+ * @since 1.20
+ */
+ public function setReadDb( $db ) {
+ $this->readDb = $db;
+ }
+
+ /**
+ * Update the records matching the provided conditions by
+ * setting the fields that are keys in the $values param to
+ * their corresponding values.
+ *
+ * @since 1.20
+ *
+ * @param array $values
+ * @param array $conditions
+ *
+ * @return boolean Success indicator
+ */
+ public function update( array $values, array $conditions = array() ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ return $dbw->update(
+ $this->getName(),
+ $this->getPrefixedValues( $values ),
+ $this->getPrefixedValues( $conditions ),
+ __METHOD__
+ ) !== false; // DatabaseBase::update does not always return true for success as documented...
+ }
+
+ /**
+ * Computes the values of the summary fields of the objects matching the provided conditions.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $summaryFields
+ * @param array $conditions
+ */
+ public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) {
+ $this->setReadDb( DB_MASTER );
+
+ /**
+ * @var IORMRow $item
+ */
+ foreach ( $this->select( null, $conditions ) as $item ) {
+ $item->loadSummaryFields( $summaryFields );
+ $item->setSummaryMode( true );
+ $item->save();
+ }
+
+ $this->setReadDb( DB_SLAVE );
+ }
+
+ /**
+ * Takes in an associative array with field names as keys and
+ * their values as value. The field names are prefixed with the
+ * db field prefix.
+ *
+ * @since 1.20
+ *
+ * @param array $values
+ *
+ * @return array
+ */
+ public function getPrefixedValues( array $values ) {
+ $prefixedValues = array();
+
+ foreach ( $values as $field => $value ) {
+ if ( is_integer( $field ) ) {
+ if ( is_array( $value ) ) {
+ $field = $value[0];
+ $value = $value[1];
+ }
+ else {
+ $value = explode( ' ', $value, 2 );
+ $value[0] = $this->getPrefixedField( $value[0] );
+ $prefixedValues[] = implode( ' ', $value );
+ continue;
+ }
+ }
+
+ $prefixedValues[$this->getPrefixedField( $field )] = $value;
+ }
+
+ return $prefixedValues;
+ }
+
+ /**
+ * Takes in a field or array of fields and returns an
+ * array with their prefixed versions, ready for db usage.
+ *
+ * @since 1.20
+ *
+ * @param array|string $fields
+ *
+ * @return array
+ */
+ public function getPrefixedFields( array $fields ) {
+ foreach ( $fields as &$field ) {
+ $field = $this->getPrefixedField( $field );
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Takes in a field and returns an it's prefixed version, ready for db usage.
+ *
+ * @since 1.20
+ *
+ * @param string|array $field
+ *
+ * @return string
+ */
+ public function getPrefixedField( $field ) {
+ return $this->getFieldPrefix() . $field;
+ }
+
+ /**
+ * Takes an array of field names with prefix and returns the unprefixed equivalent.
+ *
+ * @since 1.20
+ *
+ * @param array $fieldNames
+ *
+ * @return array
+ */
+ public function unprefixFieldNames( array $fieldNames ) {
+ return array_map( array( $this, 'unprefixFieldName' ), $fieldNames );
+ }
+
+ /**
+ * Takes a field name with prefix and returns the unprefixed equivalent.
+ *
+ * @since 1.20
+ *
+ * @param string $fieldName
+ *
+ * @return string
+ */
+ public function unprefixFieldName( $fieldName ) {
+ return substr( $fieldName, strlen( $this->getFieldPrefix() ) );
+ }
+
+ /**
+ * Get an instance of this class.
+ *
+ * @since 1.20
+ *
+ * @return IORMTable
+ */
+ public static function singleton() {
+ $class = get_called_class();
+
+ if ( !array_key_exists( $class, self::$instanceCache ) ) {
+ self::$instanceCache[$class] = new $class;
+ }
+
+ return self::$instanceCache[$class];
+ }
+
+ /**
+ * Get an array with fields from a database result,
+ * that can be fed directly to the constructor or
+ * to setFields.
+ *
+ * @since 1.20
+ *
+ * @param stdClass $result
+ *
+ * @return array
+ */
+ public function getFieldsFromDBResult( stdClass $result ) {
+ $result = (array)$result;
+ return array_combine(
+ $this->unprefixFieldNames( array_keys( $result ) ),
+ array_values( $result )
+ );
+ }
+
+ /**
+ * @see ORMTable::newRowFromFromDBResult
+ *
+ * @deprecated use newRowFromDBResult instead
+ * @since 1.20
+ *
+ * @param stdClass $result
+ *
+ * @return IORMRow
+ */
+ public function newFromDBResult( stdClass $result ) {
+ return self::newRowFromDBResult( $result );
+ }
+
+ /**
+ * Get a new instance of the class from a database result.
+ *
+ * @since 1.20
+ *
+ * @param stdClass $result
+ *
+ * @return IORMRow
+ */
+ public function newRowFromDBResult( stdClass $result ) {
+ return $this->newRow( $this->getFieldsFromDBResult( $result ) );
+ }
+
+ /**
+ * @see ORMTable::newRow
+ *
+ * @deprecated use newRow instead
+ * @since 1.20
+ *
+ * @param array $data
+ * @param boolean $loadDefaults
+ *
+ * @return IORMRow
+ */
+ public function newFromArray( array $data, $loadDefaults = false ) {
+ return static::newRow( $data, $loadDefaults );
+ }
+
+ /**
+ * Get a new instance of the class from an array.
+ *
+ * @since 1.20
+ *
+ * @param array $data
+ * @param boolean $loadDefaults
+ *
+ * @return IORMRow
+ */
+ public function newRow( array $data, $loadDefaults = false ) {
+ $class = $this->getRowClass();
+ return new $class( $this, $data, $loadDefaults );
+ }
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldNames() {
+ return array_keys( $this->getFields() );
+ }
+
+ /**
+ * Gets if the object can take a certain field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ *
+ * @return boolean
+ */
+ public function canHaveField( $name ) {
+ return array_key_exists( $name, $this->getFields() );
+ }
+
+}