* Interface for classes that implement or wrap DatabaseBase
* @ingroup Database
*/
-interface IDatabase {}
+interface IDatabase {
+}
/**
* Database abstraction object
protected $mTablePrefix;
protected $mFlags;
protected $mForeign;
- protected $mTrxLevel = 0;
protected $mErrorCount = 0;
protected $mLBInfo = array();
protected $mFakeSlaveLag = null, $mFakeMaster = false;
protected $delimiter = ';';
+ /**
+ * Either 1 if a transaction is active or 0 otherwise.
+ * The other Trx fields may not be meaningfull if this is 0.
+ *
+ * @var int
+ */
+ protected $mTrxLevel = 0;
+
/**
* Remembers the function name given for starting the most recent transaction via begin().
* Used to provide additional context for error reporting.
}
/**
- * Gets or sets the current transaction level.
+ * Gets the current transaction level.
*
* Historically, transactions were allowed to be "nested". This is no
* longer supported, so this function really only returns a boolean.
*
- * @param int $level An integer (0 or 1), or omitted to leave it unchanged.
* @return int The previous value
*/
- public function trxLevel( $level = null ) {
- return wfSetVar( $this->mTrxLevel, $level );
+ public function trxLevel() {
+ return $this->mTrxLevel;
}
/**
/**
* Clear a flag for this connection
*
- * @param $flag: same as setFlag()'s $flag param
+ * @param $flag : same as setFlag()'s $flag param
*/
public function clearFlag( $flag ) {
global $wgDebugDBTransactions;
/**
* Returns a boolean whether the flag $flag is set for this connection
*
- * @param $flag: same as setFlag()'s $flag param
+ * @param $flag : same as setFlag()'s $flag param
* @return Boolean
*/
public function getFlag( $flag ) {
* not restored on unserialize.
*/
public function __sleep() {
- throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
+ throw new MWException( 'Database serialization may cause problems, since ' .
+ 'the connection is not restored on wakeup.' );
}
/**
* Given a DB type, construct the name of the appropriate child class of
* DatabaseBase. This is designed to replace all of the manual stuff like:
- * $class = 'Database' . ucfirst( strtolower( $dbType ) );
+ * $class = 'Database' . ucfirst( strtolower( $dbType ) );
* as well as validate against the canonical list of DB types we have
*
* This factory function is mostly useful for when you need to connect to a
*/
final public static function factory( $dbType, $p = array() ) {
$canonicalDBTypes = array(
- 'mysql' => array( 'mysqli', 'mysql' ),
+ 'mysql' => array( 'mysqli', 'mysql' ),
'postgres' => array(),
- 'sqlite' => array(),
- 'oracle' => array(),
- 'mssql' => array(),
+ 'sqlite' => array(),
+ 'oracle' => array(),
+ 'mssql' => array(),
);
$driver = false;
'tablePrefix' => isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global',
'foreign' => isset( $p['foreign'] ) ? $p['foreign'] : false
);
+
return new $class( $params );
} else {
return null;
if ( $this->mPHPError ) {
$error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
$error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
+
return $error;
} else {
return false;
$ret = $this->closeConnection();
$this->mConn = false;
+
return $ret;
} else {
return true;
# If DBO_TRX is set, start a transaction
if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' )
- {
+ $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK'
+ ) {
# Avoid establishing transactions for SHOW and SET statements too -
# that would delay transaction initializations to once connection
# is really used by application
# Try reconnecting if the connection was lost
if ( false === $ret && $this->wasErrorReissuable() ) {
# Transaction is gone, like it or not
+ $hadTrx = $this->mTrxLevel; // possible lost transaction
+ wfDebug( "Connection lost, reconnecting...\n" );
$this->mTrxLevel = 0;
- $this->mTrxIdleCallbacks = array(); // cancel
- $this->mTrxPreCommitCallbacks = array(); // cancel
wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->ping() ) {
# Not a database error to lose a transaction after a minute or two
wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
}
- $ret = $this->doQuery( $commentedSql );
+ if ( !$hadTrx ) {
+ # Should be safe to silently retry
+ $ret = $this->doQuery( $commentedSql );
+ }
} else {
wfDebug( "Failed\n" );
}
return $arg;
case '&':
# return $this->addQuotes( file_get_contents( $arg ) );
- throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
+ throw new DBUnexpectedError(
+ $this,
+ '& mode is not implemented. If it\'s really needed, uncomment the line above.'
+ );
default:
- throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
+ throw new DBUnexpectedError(
+ $this,
+ 'Received invalid match. This should never happen!'
+ );
}
}
: $options['HAVING'];
$sql .= ' HAVING ' . $having;
}
+
return $sql;
}
$ob = is_array( $options['ORDER BY'] )
? implode( ',', $options['ORDER BY'] )
: $options['ORDER BY'];
+
return ' ORDER BY ' . $ob;
}
+
return '';
}
* @see DatabaseBase::select()
*/
public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
- $options = array(), $join_conds = array() )
- {
+ $options = array(), $join_conds = array()
+ ) {
if ( is_array( $vars ) ) {
$vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
}
* @return object|bool
*/
public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
- $options = array(), $join_conds = array() )
- {
+ $options = array(), $join_conds = array()
+ ) {
$options = (array)$options;
$options['LIMIT'] = 1;
$res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
* @return Integer: row count
*/
public function estimateRowCount( $table, $vars = '*', $conds = '',
- $fname = __METHOD__, $options = array() )
- {
+ $fname = __METHOD__, $options = array()
+ ) {
$rows = 0;
$res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
* @param $table String name of the table to UPDATE. This will be passed through
* DatabaseBase::tableName().
*
- * @param array $values An array of values to SET. For each array element,
+ * @param array $values An array of values to SET. For each array element,
* the key gives the field name, and the value gives the data
* to set that field to. The data will be quoted by
* DatabaseBase::addQuotes().
return 'CONCAT(' . implode( ',', $stringList ) . ')';
}
+ /**
+ * Build a GROUP_CONCAT or equivalent statement for a query.
+ *
+ * This is useful for combining a field for several rows into a single string.
+ * NULL values will not appear in the output, duplicated values will appear,
+ * and the resulting delimiter-separated values have no defined sort order.
+ * Code using the results may need to use the PHP unique() or sort() methods.
+ *
+ * @param string $delim Glue to bind the results together
+ * @param string|array $table Table name
+ * @param string $field Field name
+ * @param string|array $conds Conditions
+ * @param string|array $join_conds Join conditions
+ * @return String SQL text
+ * @since 1.23
+ */
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = array()
+ ) {
+ $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
+
+ return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
+ }
+
/**
* Change the current database
*
# if your database engine supports a concept similar to MySQL's
# databases you may as well.
$this->mDBname = $db;
+
return true;
}
list( $table ) = $dbDetails;
if ( $wgSharedDB !== null # We have a shared database
&& $this->mForeign == false # We're not working on a foreign database
- && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
+ && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`'
&& in_array( $table, $wgSharedTables ) # A shared table is selected
) {
$database = $wgSharedDB;
}
$retval[] = $this->tableNameWithAlias( $table, $alias );
}
+
return $retval;
}
}
$retval[] = $this->fieldNameWithAlias( $field, $alias );
}
+
return $retval;
}
}
/**
- * LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match
- * containing either string literals that will be escaped or tokens returned by anyChar() or anyString().
- * Alternatively, the function could be provided with an array of aforementioned parameters.
+ * LIKE statement wrapper, receives a variable-length argument list with
+ * parts of pattern to match containing either string literals that will be
+ * escaped or tokens returned by anyChar() or anyString(). Alternatively,
+ * the function could be provided with an array of aforementioned
+ * parameters.
*
- * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns a LIKE clause that searches
- * for subpages of 'My page title'.
- * Alternatively: $pattern = array( 'My_page_title/', $dbr->anyString() ); $query .= $dbr->buildLike( $pattern );
+ * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns
+ * a LIKE clause that searches for subpages of 'My page title'.
+ * Alternatively:
+ * $pattern = array( 'My_page_title/', $dbr->anyString() );
+ * $query .= $dbr->buildLike( $pattern );
*
* @since 1.16
* @return String: fully built LIKE statement
* @throws DBUnexpectedError
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
- $fname = __METHOD__ )
- {
+ $fname = __METHOD__
+ ) {
if ( !$conds ) {
throw new DBUnexpectedError( $this,
'DatabaseBase::deleteJoin() called with empty $conds' );
*/
public function insertSelect( $destTable, $srcTable, $varMap, $conds,
$fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array() )
- {
+ $insertOptions = array(), $selectOptions = array()
+ ) {
$destTable = $this->tableName( $destTable );
if ( is_array( $insertOptions ) ) {
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} ";
*/
public function unionQueries( $sqls, $all ) {
$glue = $all ? ') UNION ALL (' : ') UNION (';
+
return '(' . implode( $glue, $sqls ) . ')';
}
if ( is_array( $cond ) ) {
$cond = $this->makeList( $cond, LIST_AND );
}
+
return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
}
if ( $tries <= 0 ) {
$this->rollback( __METHOD__ );
$this->reportQueryError( $error, $errno, $sql, $fname );
+
return false;
} else {
$this->commit( __METHOD__ );
+
return $retVal;
}
}
if ( $wait > $timeout * 1e6 ) {
wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
wfProfileOut( __METHOD__ );
+
return -1;
} elseif ( $wait > 0 ) {
wfDebug( "Fake slave waiting $wait us\n" );
usleep( $wait );
wfProfileOut( __METHOD__ );
+
return 1;
} else {
wfDebug( "Fake slave up to date ($wait us)\n" );
wfProfileOut( __METHOD__ );
+
return 0;
}
}
if ( !is_null( $this->mFakeSlaveLag ) ) {
$pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
+
return $pos;
} else {
# Stub
$this->clearFlag( DBO_TRX ); // make each query its own transaction
call_user_func( $phpCallback );
$this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
- } catch ( Exception $e ) {}
+ } catch ( Exception $e ) {
+ }
}
} while ( count( $this->mTrxIdleCallbacks ) );
try {
list( $phpCallback ) = $callback;
call_user_func( $phpCallback );
- } catch ( Exception $e ) {}
+ } catch ( Exception $e ) {
+ }
}
} while ( count( $this->mTrxPreCommitCallbacks ) );
*
* @since 1.23
* @param string $fname
+ * @throws DBError
*/
final public function startAtomic( $fname = __METHOD__ ) {
if ( !$this->mTrxLevel ) {
}
/**
- * Begin a transaction. If a transaction is already in progress, that transaction will be committed before the
- * new transaction is started.
+ * Ends an atomic section of SQL statements
*
- * Note that when the DBO_TRX flag is set (which is usually the case for web requests, but not for maintenance scripts),
- * any previous database query will have started a transaction automatically.
+ * Ends the next section of atomic SQL statements and commits the transaction
+ * if necessary.
*
- * Nesting of transactions is not supported. Attempts to nest transactions will cause a warning, unless the current
- * transaction was started automatically because of the DBO_TRX flag.
+ * @since 1.23
+ * @see DatabaseBase::startAtomic
+ * @param string $fname
+ * @throws DBError
+ */
+ final public function endAtomic( $fname = __METHOD__ ) {
+ if ( !$this->mTrxLevel ) {
+ throw new DBUnexpectedError( $this, 'No atomic transaction is open.' );
+ }
+ if ( $this->mTrxAtomicLevels->isEmpty() ||
+ $this->mTrxAtomicLevels->pop() !== $fname
+ ) {
+ throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' );
+ }
+
+ if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) {
+ $this->commit( $fname, 'flush' );
+ }
+ }
+
+ /**
+ * Begin a transaction. If a transaction is already in progress,
+ * that transaction will be committed before the new transaction is started.
+ *
+ * Note that when the DBO_TRX flag is set (which is usually the case for web
+ * requests, but not for maintenance scripts), any previous database query
+ * will have started a transaction automatically.
+ *
+ * Nesting of transactions is not supported. Attempts to nest transactions
+ * will cause a warning, unless the current transaction was started
+ * automatically because of the DBO_TRX flag.
*
* @param $fname string
+ * @throws DBError
*/
final public function begin( $fname = __METHOD__ ) {
global $wgDebugDBTransactions;
$this->mTrxAutomatic = false;
$this->mTrxAutomaticAtomic = false;
$this->mTrxAtomicLevels = new SplStack;
+ $this->mTrxIdleCallbacks = array();
+ $this->mTrxPreCommitCallbacks = array();
}
/**
$this->mTrxLevel = 1;
}
- /**
- * Ends an atomic section of SQL statements
- *
- * Ends the next section of atomic SQL statements and commits the transaction
- * if necessary.
- *
- * @since 1.23
- * @see DatabaseBase::startAtomic
- * @param string $fname
- */
- final public function endAtomic( $fname = __METHOD__ ) {
- if ( $this->mTrxAtomicLevels->isEmpty() ||
- $this->mTrxAtomicLevels->pop() !== $fname
- ) {
- throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' );
- }
-
- if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) {
- $this->commit( $fname, 'flush' );
- }
- }
-
/**
* Commits a transaction previously started using begin().
* If no transaction is in progress, a warning is issued.
* Nesting of transactions is not supported.
*
* @param $fname string
- * @param string $flush Flush flag, set to 'flush' to disable warnings about explicitly committing implicit
- * transactions, or calling commit when no transaction is in progress.
- * This will silently break any ongoing explicit transaction. Only set the flush flag if you are sure
- * that it is safe to ignore these warnings in your context.
+ * @param string $flush Flush flag, set to 'flush' to disable warnings about
+ * explicitly committing implicit transactions, or calling commit when no
+ * transaction is in progress. This will silently break any ongoing
+ * explicit transaction. Only set the flush flag if you are sure that it
+ * is safe to ignore these warnings in your context.
*/
final public function commit( $fname = __METHOD__, $flush = '' ) {
if ( !$this->mTrxAtomicLevels->isEmpty() ) {
// There are still atomic sections open. This cannot be ignored
- throw new DBUnexpectedError( $this, "Attempted to commit transaction while atomic sections are still open" );
+ throw new DBUnexpectedError(
+ $this,
+ "Attempted to commit transaction while atomic sections are still open"
+ );
}
if ( $flush != 'flush' ) {
if ( $this->mTrxDoneWrites ) {
Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
}
- $this->mTrxDoneWrites = false;
$this->runOnTransactionIdleCallbacks();
}
if ( $this->mTrxDoneWrites ) {
Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
}
- $this->mTrxDoneWrites = false;
}
/**
* For caching purposes the list of all views should be stored in
* $this->allViews. The process cache can be cleared with clearViewsCache()
*
- * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
- * @param string $fname Name of calling function
+ * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
+ * @param string $fname Name of calling function
* @throws MWException
* @since 1.22
*/
* @param bool|callable $lineCallback Optional function called before reading each line
* @param bool|callable $resultCallback Optional function called for each MySQL result
* @param bool|string $fname Calling function name or false if name should be
- * generated dynamically using $filename
- * @param bool|callable $inputCallback Callback: Optional function called for each complete line sent
+ * generated dynamically using $filename
+ * @param bool|callable $inputCallback Callback: Optional function called
+ * for each complete line sent
* @throws MWException
* @throws Exception|MWException
* @return bool|string
try {
$error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
- }
- catch ( MWException $e ) {
+ } catch ( MWException $e ) {
fclose( $fp );
throw $e;
}
* @return bool|string
*/
public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = __METHOD__, $inputCallback = false )
- {
+ $fname = __METHOD__, $inputCallback = false
+ ) {
$cmd = '';
while ( !feof( $fp ) ) {
if ( false === $res ) {
$err = $this->lastError();
+
return "Query \"{$cmd}\" failed with error code \"$err\".\n";
}
}
return true;
}
}
+
return false;
}
// replace /*$var*/
$ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
}
+
return $ins;
}
if ( $this->cascadingDeletes() ) {
$sql .= " CASCADE";
}
+
return $this->query( $sql, $fName );
}
$callers = array();
foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
$callers[] = $callbackInfo[1];
-
}
$callers = implode( ', ', $callers );
trigger_error( "DB transaction callbacks still pending (from $callers)." );