*/
private $mTrxDoneWrites = false;
+ /**
+ * Record if the current transaction was started implicitly due to DBO_TRX being set.
+ *
+ * @var Bool
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxAutomatic = false;
+
# ------------------------------------------------------------------------------
# Accessors
# ------------------------------------------------------------------------------
return $this->getServerVersion();
}
+ /**
+ * @return string: command delimiter used by this database engine
+ */
+ public function getDelimiter() {
+ return $this->delimiter;
+ }
+
/**
* Boolean, controls output of large amounts of debug information.
* @param $debug bool|null
$this->mOpened = false;
if ( $this->mConn ) {
if ( $this->trxLevel() ) {
- $this->commit( __METHOD__ );
+ if ( !$this->mTrxAutomatic ) {
+ wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
+ " performing implicit commit before closing connection!" );
+ }
+
+ $this->commit( __METHOD__, 'flush' );
}
+
$ret = $this->closeConnection();
$this->mConn = false;
return $ret;
/**
* @param $error String: fallback error message, used if none is given by DB
+ * @throws DBConnectionError
*/
function reportConnectionError( $error = 'Unknown error' ) {
$myError = $this->lastError();
* comment (you can use __METHOD__ or add some extra info)
* @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
* maybe best to catch the exception instead?
+ * @throws MWException
* @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object
* for a successful read query, or false on failure if $tempIgnore set
- * @throws DBQueryError Thrown when the database returns an error of any kind
*/
public function query( $sql, $fname = '', $tempIgnore = false ) {
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
wfDebug("Implicit transaction start.\n");
}
$this->begin( __METHOD__ . " ($fname)" );
+ $this->mTrxAutomatic = true;
}
}
if ( false === $ret && $this->wasErrorReissuable() ) {
# Transaction is gone, like it or not
$this->mTrxLevel = 0;
+ $this->trxIdleCallbacks = array(); // cancel
wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->ping() ) {
* @param $sql String
* @param $fname String
* @param $tempIgnore Boolean
+ * @throws DBQueryError
*/
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
# Ignore errors during error handling to avoid infinite recursion
* while we're doing this.
*
* @param $matches Array
+ * @throws DBUnexpectedError
* @return String
*/
protected function fillPreparedArg( $matches ) {
/**
* Makes an encoded list of strings from an array
* @param $a Array containing the data
- * @param $mode int Constant
+ * @param int $mode Constant
* - LIST_COMMA: comma separated, no field names
* - LIST_AND: ANDed WHERE clause (without the WHERE). See
* the documentation for $conds in DatabaseBase::select().
* - LIST_SET: comma separated with field names, like a SET clause
* - LIST_NAMES: comma separated field names
*
+ * @throws MWException|DBUnexpectedError
* @return string
*/
public function makeList( $a, $mode = LIST_COMMA ) {
$list .= "$value";
} elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
if ( count( $value ) == 0 ) {
- throw new MWException( __METHOD__ . ': empty input' );
+ throw new MWException( __METHOD__ . ": empty input for field $field" );
} elseif ( count( $value ) == 1 ) {
// Special-case single values, as IN isn't terribly efficient
// Don't necessarily assume the single key is 0; we don't
* ANDed together in the WHERE clause
* @param $fname String: Calling function name (use __METHOD__) for
* logs/profiling
+ * @throws DBUnexpectedError
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
$fname = 'DatabaseBase::deleteJoin' )
* the format. Use $conds == "*" to delete all rows
* @param $fname String name of the calling function
*
+ * @throws DBUnexpectedError
* @return bool
*/
public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
* @param $limit Integer the SQL limit
* @param $offset Integer|bool the SQL offset (default false)
*
+ * @throws DBUnexpectedError
* @return string
*/
public function limitResult( $sql, $limit, $offset = false ) {
* 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 warnings if DBO_TRX is not set
- * or the extsting transaction contained write operations.
+ * 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
*/
global $wgDebugDBTransactions;
if ( $this->mTrxLevel ) { // implicit commit
- if ( $this->mTrxDoneWrites || ( $this->mFlags & DBO_TRX ) === 0 ) {
- // In theory, we should always warn about nesting BEGIN statements.
- // However, it is sometimes hard to avoid so we only warn if:
- //
- // a) the transaction has done writes. This gives warnings about bad transactions
- // that could cause partial writes but not about read queries seeing more
- // than one DB snapshot (when in REPEATABLE-READ) due to nested BEGINs.
- //
- // b) the DBO_TRX flag is not set. Explicit transactions should always be properly
- // started and comitted.
+ if ( !$this->mTrxAutomatic ) {
+ // We want to warn about inadvertently nested begin/commit pairs, but not about auto-committing
+ // implicit transactions that were started by query() because DBO_TRX was set.
+
wfWarn( "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
" performing implicit commit!" );
} else {
- if ( $wgDebugDBTransactions ) {
- wfDebug( "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
+ // if the transaction was automatic and has done write operations,
+ // log it if $wgDebugDBTransactions is enabled.
+
+ if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
+ wfDebug( "$fname: Automatic transaction with writes in progress (from {$this->mTrxFname}), " .
" performing implicit commit!\n" );
}
}
$this->doBegin( $fname );
$this->mTrxFname = $fname;
$this->mTrxDoneWrites = false;
+ $this->mTrxAutomatic = false;
}
/**
* Nesting of transactions is not supported.
*
* @param $fname string
- */
- final public function commit( $fname = 'DatabaseBase::commit' ) {
- if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to commit, something got out of sync!" );
+ * @param $flush String Flush flag, set to 'flush' to disable warnings about explicitly committing implicit
+ * transactions, or calling commit when no transaction is in progress.
+ * This will silently break any ongoing explicit transaction. Only set the flush flag if you are sure
+ * that it is safe to ignore these warnings in your context.
+ */
+ final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) {
+ if ( $flush != 'flush' ) {
+ if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to commit, something got out of sync!" );
+ } elseif( $this->mTrxAutomatic ) {
+ wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
+ }
+ } else {
+ if ( !$this->mTrxLevel ) {
+ return; // nothing to do
+ } elseif( !$this->mTrxAutomatic ) {
+ wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
+ }
}
+
$this->doCommit( $fname );
$this->runOnTransactionIdleCallbacks();
}
* @param $newName String: name of table to be created
* @param $temporary Boolean: whether the new table should be temporary
* @param $fname String: calling function name
+ * @throws MWException
* @return Boolean: true if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
*
* @param $prefix string Only show tables with this prefix, e.g. mw_
* @param $fname String: calling function name
+ * @throws MWException
*/
function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
* on object's error ignore settings).
*
* @param $filename String: File name to open
- * @param $lineCallback Callback: Optional function called before reading each line
- * @param $resultCallback Callback: Optional function called for each MySQL result
- * @param $fname String: Calling function name or false if name should be
+ * @param 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
+ * @throws MWException
* @return bool|string
*/
public function sourceFile(