* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
*
- * @param $res SQL result object as returned from DatabaseBase::query(), etc.
+ * @param $res ResultWrapper|object as returned from DatabaseBase::query(), etc.
* @return Row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
protected $preparedArgs;
+ protected $htmlErrors;
+
+ protected $delimiter = ';';
+
# ------------------------------------------------------------------------------
# Accessors
# ------------------------------------------------------------------------------
* - 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 ) {
return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
*
* @param $buffer null|bool
*
- * @return The previous value of the flag
+ * @return null|bool The previous value of the flag
*/
function bufferResults( $buffer = null ) {
if ( is_null( $buffer ) ) {
*
* @param $ignoreErrors bool|null
*
- * @return The previous value of the flag.
+ * @return bool The previous value of the flag.
*/
function ignoreErrors( $ignoreErrors = null ) {
return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
* 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 ) {
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 ) {
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 ) {
return wfSetVar( $this->mTablePrefix, $prefix );
}
}
- /**
- * Called by unserialize. Needed to reopen DB connection, which
- * is not saved by serialize.
+ /**
+ * Called by serialize. Throw an exception when DB connection is serialized.
+ * This causes problems on some database engines because the connection is
+ * not restored on unserialize.
*/
- public function __wakeup() {
- if ( $this->isOpen() ) {
- $this->open( $this->mServer, $this->mUser,
- $this->mPassword, $this->mDBname);
- }
+ public function __sleep() {
+ throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
}
/**
* 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__ );
+ 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()
+ * @return DatabaseBase
+ */
+ 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:
* @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
'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
);
$dbType = strtolower( $dbType );
+ $class = 'Database' . ucfirst( $dbType );
- if( in_array( $dbType, $canonicalDBTypes ) ) {
- $class = 'Database' . ucfirst( $dbType );
+ if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
return new $class(
isset( $p['host'] ) ? $p['host'] : false,
isset( $p['user'] ) ? $p['user'] : false,
}
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;
}
}
+ /**
+ * @param $errno
+ * @param $errstr
+ */
protected function connectionErrorHandler( $errno, $errstr ) {
$this->mPHPError = $errstr;
}
*
* @return Bool operation success. true if already closed.
*/
- function close() {
- # Stub, should probably be overridden
- return true;
+ public function close() {
+ $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
*/
* @return bool
*/
function isWriteQuery( $sql ) {
- return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW|\(SELECT)\b/i', $sql );
+ return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|\(SELECT)\b/i', $sql );
}
/**
# 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 and strpos( $sqlstart, "SET " ) !== 0 )
- $this->begin();
+ if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 )
+ $this->begin( __METHOD__ . " ($fname)" );
}
if ( $this->debug() ) {
$sqlx = substr( $commentedSql, 0, 500 );
$sqlx = strtr( $sqlx, "\t\n", ' ' );
- if ( $isMaster ) {
- wfDebug( "Query $cnt (master): $sqlx\n" );
- } else {
- wfDebug( "Query $cnt (slave): $sqlx\n" );
- }
+ $master = $isMaster ? 'master' : 'slave';
+ wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
}
if ( istainted( $sql ) & TC_MYSQL ) {
throw new MWException( 'Tainted query found' );
}
+ $queryId = MWDebug::query( $sql, $fname, $isMaster );
+
# Do the query and handle errors
$ret = $this->doQuery( $commentedSql );
+ MWDebug::queryTime( $queryId );
+
# Try reconnecting if the connection was lost
if ( false === $ret && $this->wasErrorReissuable() ) {
# Transaction is gone, like it or not
/**
* Free a prepared query, generated by prepare().
+ * @param $prepared
*/
function freePrepared( $prepared ) {
/* No-op by default */
* @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',
$options = array() )
* - If the value is an array, an IN(...) clause will be constructed,
* such that the field name may match any of the elements in the
* array. The elements of the array will be quoted.
- * - If the field name ends with "!", this is taken as a flag which
- * inverts the comparison, allowing NOT IN clauses to be constructed,
- * for example: array( 'user_id!' => array( 1, 2, 3 ) )
*
* Note that expressions are often DBMS-dependent in their syntax.
* DBMS-independent wrappers are provided for constructing several types of
* @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() ) {
*
* @param $table string|array Table name
* @param $vars string|array Field names
- * @param $conds|array Conditions
+ * @param $conds array Conditions
* @param $fname string Caller function name
* @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',
$options = array(), $join_conds = array() )
* Query whether a given table exists
*
* @param $table string
+ * @param $fname string
*
* @return bool
*/
- function tableExists( $table ) {
+ function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$old = $this->ignoreErrors( true );
- $res = $this->query( "SELECT 1 FROM $table LIMIT 1", __METHOD__ );
+ $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
$this->ignoreErrors( $old );
return (bool)$res;
}
/**
- * @todo document
* mysql_field_type() wrapper
+ * @param $res
+ * @param $index
+ * @return string
*/
function fieldType( $res, $index ) {
if ( $res instanceof ResultWrapper ) {
* @return string
*/
function quote_ident( $s ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
return $this->addIdentifierQuotes( $s );
}
* @return string
*/
public function escapeLike( $s ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.17' );
return $this->escapeLikeInternal( $s );
}
* Returns an appropriately quoted sequence value for inserting a new row.
* MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
* subclass will return an integer, and save the value for insertId()
+ *
+ * Any implementation of this function should *not* involve reusing
+ * sequence numbers created for rolled-back transactions.
+ * See http://bugs.mysql.com/bug.php?id=30767 for details.
+ * @param $seqName string
+ * @return null
*/
function nextSequenceValue( $seqName ) {
return null;
* which index to pick. Anyway, other databases might have different
* indexes on a given table. So don't bother overriding this unless you're
* MySQL.
+ * @param $index
+ * @return string
*/
function useIndexClause( $index ) {
return '';
* REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE
* statement.
*
- * @param $table Table name
- * @param $rows Rows to insert
- * @param $fname Caller function name
+ * @param $table string Table name
+ * @param $rows array Rows to insert
+ * @param $fname string Caller function name
*
* @return ResultWrapper
*/
" FROM $srcTable $useIndex ";
if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ if ( is_array( $conds ) ) {
+ $conds = $this->makeList( $conds, LIST_AND );
+ }
+ $sql .= " WHERE $conds";
}
$sql .= " $tailOpts";
*
* @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
*/
return "REPLACE({$orig}, {$old}, {$new})";
}
+ /**
+ * Determines how long the server has been up
+ * STUB
+ *
+ * @return int
+ */
+ function getServerUptime() {
+ return 0;
+ }
+
/**
* Determines if the last failure was due to a deadlock
* STUB
return false;
}
+ /**
+ * Determines if the last failure was due to a lock timeout
+ * STUB
+ *
+ * @return bool
+ */
+ function wasLockTimeout() {
+ return false;
+ }
+
/**
* Determines if the last query error was something that should be dealt
* with by pinging the connection and reissuing the query.
* @return bool
*/
function deadlockLoop() {
- $myFname = 'DatabaseBase::deadlockLoop';
- $this->begin();
+ $this->begin( __METHOD__ );
$args = func_get_args();
$function = array_shift( $args );
$oldIgnore = $this->ignoreErrors( true );
$this->ignoreErrors( $oldIgnore );
if ( $tries <= 0 ) {
- $this->rollback( $myFname );
+ $this->rollback( __METHOD__ );
$this->reportQueryError( $error, $errno, $sql, $fname );
return false;
} else {
- $this->commit( $myFname );
+ $this->commit( __METHOD__ );
return $retVal;
}
}
* @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 ) {
- $fname = 'DatabaseBase::masterPosWait';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
if ( !is_null( $this->mFakeSlaveLag ) ) {
$wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
if ( $wait > $timeout * 1e6 ) {
wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return -1;
} elseif ( $wait > 0 ) {
wfDebug( "Fake slave waiting $wait us\n" );
usleep( $wait );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return 1;
} else {
wfDebug( "Fake slave up to date ($wait us)\n" );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return 0;
}
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
# Real waits are implemented in the subclass.
return 0;
/**
* 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' ) {
*
* @param $result bool|ResultWrapper
*
- * @param bool|ResultWrapper
+ * @return bool|ResultWrapper
*/
function resultObject( $result ) {
if ( empty( $result ) ) {
* installations. Most callers should use LoadBalancer::safeGetLag()
* instead.
*
- * @return Database replication lag in seconds
+ * @return int Database replication lag in seconds
*/
function getLag() {
return intval( $this->mFakeSlaveLag );
* don't allow simple quoted strings to be inserted. To insert into such
* a field, pass the data through this function before passing it to
* DatabaseBase::insert().
+ * @param $b string
+ * @return string
*/
function encodeBlob( $b ) {
return $b;
* Some DBMSs return a special placeholder object representing blob fields
* in result objects. Pass the object through this function to return the
* original string.
+ * @param $b string
+ * @return string
*/
function decodeBlob( $b ) {
return $b;
}
/**
- * Override database's default connection timeout. May be useful for very
- * long batch queries such as full-wiki dumps, where a single query reads
- * out over hours or days. May or may not be necessary for non-MySQL
- * databases. For most purposes, leaving it as a no-op should be fine.
+ * Override database's default connection timeout
*
* @param $timeout Integer in seconds
+ * @return void
+ * @deprecated since 1.19; use setSessionOptions()
*/
- public function setTimeout( $timeout ) {}
+ 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
+ * full-wiki dumps, where a single query reads out over
+ * hours or days.
+ *
+ * @param $options Array
+ * @return void
+ */
+ public function setSessionOptions( array $options ) {}
/**
* Read and execute SQL commands from a file.
* @param $resultCallback Callback: Optional function called for each MySQL result
* @param $fname String: Calling function name or false if name should be
* generated dynamically using $filename
+ * @return bool|string
*/
function sourceFile( $filename, $lineCallback = false, $resultCallback = false, $fname = false ) {
wfSuppressWarnings();
* 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 ) {
$this->mSchemaVars = $vars;
* @param $lineCallback Callback: Optional function called before reading each line
* @param $resultCallback Callback: Optional function called for each MySQL result
* @param $fname String: Calling function name
+ * @param $inputCallback Callback: Optional function called for each complete line (ended with ;) sent
+ * @return bool|string
*/
- function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = 'DatabaseBase::sourceStream' )
+ public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
+ $fname = 'DatabaseBase::sourceStream', $inputCallback = false )
{
- $cmd = "";
- $done = false;
- $dollarquote = false;
+ $cmd = '';
- while ( ! feof( $fp ) ) {
+ while ( !feof( $fp ) ) {
if ( $lineCallback ) {
call_user_func( $lineCallback );
}
$line = trim( fgets( $fp ) );
- $sl = strlen( $line ) - 1;
- if ( $sl < 0 ) {
+ if ( $line == '' ) {
continue;
}
continue;
}
- # # Allow dollar quoting for function declarations
- if ( substr( $line, 0, 4 ) == '$mw$' ) {
- if ( $dollarquote ) {
- $dollarquote = false;
- $done = true;
- }
- else {
- $dollarquote = true;
- }
- }
- elseif ( !$dollarquote ) {
- if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
- $done = true;
- $line = substr( $line, 0, $sl );
- }
- }
-
if ( $cmd != '' ) {
$cmd .= ' ';
}
+ $done = $this->streamStatementEnd( $cmd, $line );
+
$cmd .= "$line\n";
- if ( $done ) {
- $cmd = str_replace( ';;', ";", $cmd );
+ if ( $done || feof( $fp ) ) {
$cmd = $this->replaceVars( $cmd );
+ if ( $inputCallback ) {
+ call_user_func( $inputCallback, $cmd );
+ }
$res = $this->query( $cmd, $fname );
if ( $resultCallback ) {
}
$cmd = '';
- $done = false;
}
}
return true;
}
+ /**
+ * Called by sourceStream() to check if we've reached a statement end
+ *
+ * @param $sql String SQL assembled so far
+ * @param $newLine String New line about to be added to $sql
+ * @return Bool Whether $newLine contains end of the statement
+ */
+ public function streamStatementEnd( &$sql, &$newLine ) {
+ if ( $this->delimiter ) {
+ $prev = $newLine;
+ $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
+ if ( $newLine != $prev ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Database independent variable replacement. Replaces a set of variables
* in an SQL statement with their contents as given by $this->getSchemaVars().
/**
* Get schema variables. If none have been set via setSchemaVars(), then
* use some defaults from the current object.
+ *
+ * @return array
*/
protected function getSchemaVars() {
if ( $this->mSchemaVars ) {
* @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
*/
* @param $tableName string
* @param $fName string
* @return bool|ResultWrapper
+ * @since 1.18
*/
public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) {
- if( !$this->tableExists( $tableName ) ) {
+ if( !$this->tableExists( $tableName, $fName ) ) {
return false;
}
$sql = "DROP TABLE " . $this->tableName( $tableName );
}
/**
- * 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 );
}
/**