/** Maximum time to wait before retry */
const DEADLOCK_DELAY_MAX = 1500000;
-# ------------------------------------------------------------------------------
-# Variables
-# ------------------------------------------------------------------------------
+ /** How many row changes in a write query trigger a log entry */
+ const LOG_WRITE_THRESHOLD = 300;
protected $mLastQuery = '';
protected $mDoneWrites = false;
*/
protected $allViews = null;
-# ------------------------------------------------------------------------------
-# Accessors
-# ------------------------------------------------------------------------------
- # These optionally set a variable and return the previous state
-
/**
* A string describing the current software version, and possibly
* other details in a user-friendly way. Will be listed on Special:Version, etc.
return $this->getSqlFilePath( 'update-keys.sql' );
}
-# ------------------------------------------------------------------------------
-# Other functions
-# ------------------------------------------------------------------------------
+ /**
+ * Get the type of the DBMS, as it appears in $wgDBtype.
+ *
+ * @return string
+ */
+ abstract function getType();
+
+ /**
+ * Open a connection to the database. Usually aborts on failure
+ *
+ * @param string $server Database server host
+ * @param string $user Database user name
+ * @param string $password Database user password
+ * @param string $dbName Database name
+ * @return bool
+ * @throws DBConnectionError
+ */
+ abstract function open( $server, $user, $password, $dbName );
+
+ /**
+ * Fetch the next row from the given result object, in object form.
+ * Fields can be retrieved with $row->fieldname, with fields acting like
+ * member variables.
+ * If no more rows are available, false is returned.
+ *
+ * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc.
+ * @return stdClass|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ abstract function fetchObject( $res );
+
+ /**
+ * Fetch the next row from the given result object, in associative array
+ * form. Fields are retrieved with $row['fieldname'].
+ * If no more rows are available, false is returned.
+ *
+ * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc.
+ * @return array|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ abstract function fetchRow( $res );
+
+ /**
+ * Get the number of rows in a result object
+ *
+ * @param mixed $res A SQL result
+ * @return int
+ */
+ abstract function numRows( $res );
+
+ /**
+ * Get the number of fields in a result object
+ * @see http://www.php.net/mysql_num_fields
+ *
+ * @param mixed $res A SQL result
+ * @return int
+ */
+ abstract function numFields( $res );
+
+ /**
+ * Get a field name in a result object
+ * @see http://www.php.net/mysql_field_name
+ *
+ * @param mixed $res A SQL result
+ * @param int $n
+ * @return string
+ */
+ abstract function fieldName( $res, $n );
+
+ /**
+ * Get the inserted value of an auto-increment row
+ *
+ * The value inserted should be fetched from nextSequenceValue()
+ *
+ * Example:
+ * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
+ * $dbw->insert( 'page', array( 'page_id' => $id ) );
+ * $id = $dbw->insertId();
+ *
+ * @return int
+ */
+ abstract function insertId();
+
+ /**
+ * Change the position of the cursor in a result object
+ * @see http://www.php.net/mysql_data_seek
+ *
+ * @param mixed $res A SQL result
+ * @param int $row
+ */
+ abstract function dataSeek( $res, $row );
+
+ /**
+ * Get the last error number
+ * @see http://www.php.net/mysql_errno
+ *
+ * @return int
+ */
+ abstract function lastErrno();
+
+ /**
+ * Get a description of the last error
+ * @see http://www.php.net/mysql_error
+ *
+ * @return string
+ */
+ abstract function lastError();
+
+ /**
+ * mysql_fetch_field() wrapper
+ * Returns false if the field doesn't exist
+ *
+ * @param string $table Table name
+ * @param string $field Field name
+ *
+ * @return Field
+ */
+ abstract function fieldInfo( $table, $field );
+
+ /**
+ * Get information about an index into an object
+ * @param string $table Table name
+ * @param string $index Index name
+ * @param string $fname Calling function name
+ * @return mixed Database-specific index description class or false if the index does not exist
+ */
+ abstract function indexInfo( $table, $index, $fname = __METHOD__ );
+
+ /**
+ * Get the number of rows affected by the last write query
+ * @see http://www.php.net/mysql_affected_rows
+ *
+ * @return int
+ */
+ abstract function affectedRows();
+
+ /**
+ * Wrapper for addslashes()
+ *
+ * @param string $s String to be slashed.
+ * @return string Slashed string.
+ */
+ abstract function strencode( $s );
+
+ /**
+ * Returns a wikitext link to the DB's website, e.g.,
+ * return "[http://www.mysql.com/ MySQL]";
+ * Should at least contain plain text, if for some reason
+ * your database has no website.
+ *
+ * @return string Wikitext of a link to the server software's web site
+ */
+ abstract function getSoftwareLink();
+
+ /**
+ * A string describing the current software version, like from
+ * mysql_get_server_info().
+ *
+ * @return string Version information from the database server.
+ */
+ abstract function getServerVersion();
/**
* Constructor.
return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
}
+ /**
+ * Determine whether a SQL statement is sensitive to isolation level.
+ * A SQL statement is considered transactable if its result could vary
+ * depending on the transaction isolation level. Operational commands
+ * such as 'SET' and 'SHOW' are not considered to be transactable.
+ *
+ * @param string $sql
+ * @return bool
+ */
+ public function isTransactableQuery( $sql ) {
+ $verb = substr( $sql, 0, strcspn( $sql, " \t\r\n" ) );
+ return !in_array( $verb, array( 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ) );
+ }
+
/**
* Run an SQL query and return the result. Normally throws a DBQueryError
* on failure. If errors are ignored, returns false instead.
global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength;
$this->mLastQuery = $sql;
- if ( $this->isWriteQuery( $sql ) ) {
+
+ $isWriteQuery = $this->isWriteQuery( $sql );
+ if ( $isWriteQuery ) {
# Set a flag indicating that writes have been done
wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" );
$this->mDoneWrites = microtime( true );
// Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
$commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
- # If DBO_TRX is set, start a transaction
- if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK'
- ) {
- # Avoid establishing transactions for SHOW and SET statements too -
- # that would delay transaction initializations to once connection
- # is really used by application
- $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
- if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
- if ( $wgDebugDBTransactions ) {
- wfDebug( "Implicit transaction start.\n" );
- }
- $this->begin( __METHOD__ . " ($fname)" );
- $this->mTrxAutomatic = true;
+ if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX ) && $this->isTransactableQuery( $sql ) ) {
+ if ( $wgDebugDBTransactions ) {
+ wfDebug( "Implicit transaction start.\n" );
}
+ $this->begin( __METHOD__ . " ($fname)" );
+ $this->mTrxAutomatic = true;
}
# Keep track of whether the transaction has write queries pending
- if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
+ if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWriteQuery ) {
$this->mTrxDoneWrites = true;
Profiler::instance()->getTransactionProfiler()->transactionWritingIn(
$this->mServer, $this->mDBname, $this->mTrxShortId );
if ( false === $ret ) {
$this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
+ } else {
+ $n = $this->affectedRows();
+ if ( $isWriteQuery && $n > self::LOG_WRITE_THRESHOLD && PHP_SAPI !== 'cli' ) {
+ wfDebugLog( 'DBPerformance',
+ "Query affected $n rows:\n" .
+ DatabaseBase::generalizeSQL( $sql ) . "\n" . wfBacktrace( true ) );
+ }
}
- return $this->resultObject( $ret );
+ $res = $this->resultObject( $ret );
+
+ // Destroy profile sections in the opposite order to their creation
+ $queryProfSection = false;
+ $totalProfSection = false;
+
+ return $res;
}
/**
} elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
$list .= "$value";
} elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
- if ( count( $value ) == 0 ) {
+ // Remove null from array to be handled separately if found
+ $includeNull = false;
+ foreach ( array_keys( $value, null, true ) as $nullKey ) {
+ $includeNull = true;
+ unset( $value[$nullKey] );
+ }
+ if ( count( $value ) == 0 && !$includeNull ) {
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
- // enforce linear numeric ordering on other arrays here.
- $value = array_values( $value );
- $list .= $field . " = " . $this->addQuotes( $value[0] );
+ } elseif ( count( $value ) == 0 ) {
+ // only check if $field is null
+ $list .= "$field IS NULL";
} else {
- $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+ // IN clause contains at least one valid element
+ if ( $includeNull ) {
+ // Group subconditions to ensure correct precedence
+ $list .= '(';
+ }
+ if ( 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
+ // enforce linear numeric ordering on other arrays here.
+ $value = array_values( $value );
+ $list .= $field . " = " . $this->addQuotes( $value[0] );
+ } else {
+ $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+ }
+ // if null present in array, append IS NULL
+ if ( $includeNull ) {
+ $list .= " OR $field IS NULL)";
+ }
}
} elseif ( $value === null ) {
if ( $mode == LIST_AND || $mode == LIST_OR ) {
* calling rollback 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.
+ * @throws DBUnexpectedError
* @since 1.23 Added $flush parameter
*/
final public function rollback( $fname = __METHOD__, $flush = '' ) {