*/
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
+use Wikimedia\ScopedCallback;
+use Wikimedia\Rdbms\TransactionProfiler;
+use Wikimedia\Rdbms\LikeMatch;
+use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\ResultWrapper;
+use Wikimedia\Rdbms\DBMasterPos;
+use Wikimedia\Rdbms\Blob;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\IMaintainableDatabase;
/**
- * Database abstraction object
+ * Relational database abstraction object
+ *
* @ingroup Database
+ * @since 1.28
*/
-abstract class Database implements IDatabase, LoggerAwareInterface {
+abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAwareInterface {
/** Number of times to re-try an operation in case of deadlock */
const DEADLOCK_TRIES = 4;
/** Minimum time to wait before retry, in microseconds */
/** @var string SQL query */
protected $mLastQuery = '';
- /** @var bool */
- protected $mDoneWrites = false;
+ /** @var float|bool UNIX timestamp of last write query */
+ protected $mLastWriteTime = false;
/** @var string|bool */
protected $mPHPError = false;
/** @var string */
/** @var callback Error logging callback */
protected $errorLogger;
- /** @var resource Database connection */
+ /** @var resource|null Database connection */
protected $mConn = null;
/** @var bool */
protected $mOpened = false;
* Either a short hexidecimal string if a transaction is active or ""
*
* @var string
- * @see DatabaseBase::mTrxLevel
+ * @see Database::mTrxLevel
*/
protected $mTrxShortId = '';
/**
* point (possibly more up-to-date since the first SELECT defines the snapshot).
*
* @var float|null
- * @see DatabaseBase::mTrxLevel
+ * @see Database::mTrxLevel
*/
private $mTrxTimestamp = null;
/** @var float Lag estimate at the time of BEGIN */
* Used to provide additional context for error reporting.
*
* @var string
- * @see DatabaseBase::mTrxLevel
+ * @see Database::mTrxLevel
*/
private $mTrxFname = null;
/**
* Record if possible write queries were done in the last transaction started
*
* @var bool
- * @see DatabaseBase::mTrxLevel
+ * @see Database::mTrxLevel
*/
private $mTrxDoneWrites = false;
/**
* Record if the current transaction was started implicitly due to DBO_TRX being set.
*
* @var bool
- * @see DatabaseBase::mTrxLevel
+ * @see Database::mTrxLevel
*/
private $mTrxAutomatic = false;
/**
*/
private $mTrxAtomicLevels = [];
/**
- * Record if the current transaction was started implicitly by DatabaseBase::startAtomic
+ * Record if the current transaction was started implicitly by Database::startAtomic
*
* @var bool
*/
/** @var array Map of (name => 1) for locks obtained via lock() */
private $mNamedLocksHeld = [];
/** @var array Map of (table name => 1) for TEMPORARY tables */
- private $mSessionTempTables = [];
+ protected $mSessionTempTables = [];
/** @var IDatabase|null Lazy handle to the master DB this server replicates from */
private $lazyMasterHandle;
- /**
- * @since 1.21
- * @var resource File handle for upgrade
- */
- protected $fileHandle = null;
-
- /**
- * @since 1.22
- * @var string[] Process cache of VIEWs names in the database
- */
- protected $allViews = null;
-
/** @var float UNIX timestamp */
protected $lastPing = 0.0;
$this->agent = str_replace( '/', '-', $params['agent'] );
$this->mFlags = $params['flags'];
- if ( $this->mFlags & DBO_DEFAULT ) {
+ if ( $this->mFlags & self::DBO_DEFAULT ) {
if ( $this->cliMode ) {
- $this->mFlags &= ~DBO_TRX;
+ $this->mFlags &= ~self::DBO_TRX;
} else {
- $this->mFlags |= DBO_TRX;
+ $this->mFlags |= self::DBO_TRX;
}
}
$this->trxProfiler = $params['trxProfiler'];
$this->connLogger = $params['connLogger'];
$this->queryLogger = $params['queryLogger'];
+ $this->errorLogger = $params['errorLogger'];
// Set initial dummy domain until open() sets the final DB/prefix
$this->currentDomain = DatabaseDomain::newUnspecified();
} else {
$driver = $dbType;
}
- if ( $driver === false ) {
+ if ( $driver === false || $driver === '' ) {
throw new InvalidArgumentException( __METHOD__ .
" no viable database extension found for type '$dbType'" );
}
$class = 'Database' . ucfirst( $driver );
- if ( class_exists( $class ) && is_subclass_of( $class, 'IDatabase' ) ) {
+ if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) {
// Resolve some defaults for b/c
$p['host'] = isset( $p['host'] ) ? $p['host'] : false;
$p['user'] = isset( $p['user'] ) ? $p['user'] : false;
}
if ( !isset( $p['errorLogger'] ) ) {
$p['errorLogger'] = function ( Exception $e ) {
- trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_WARNING );
+ trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
};
}
}
public function bufferResults( $buffer = null ) {
- $res = !$this->getFlag( DBO_NOBUFFER );
+ $res = !$this->getFlag( self::DBO_NOBUFFER );
if ( $buffer !== null ) {
- $buffer ? $this->clearFlag( DBO_NOBUFFER ) : $this->setFlag( DBO_NOBUFFER );
+ $buffer
+ ? $this->clearFlag( self::DBO_NOBUFFER )
+ : $this->setFlag( self::DBO_NOBUFFER );
}
return $res;
* @return bool The previous value of the flag.
*/
protected function ignoreErrors( $ignoreErrors = null ) {
- $res = $this->getFlag( DBO_IGNORE );
+ $res = $this->getFlag( self::DBO_IGNORE );
if ( $ignoreErrors !== null ) {
- $ignoreErrors ? $this->setFlag( DBO_IGNORE ) : $this->clearFlag( DBO_IGNORE );
+ $ignoreErrors
+ ? $this->setFlag( self::DBO_IGNORE )
+ : $this->clearFlag( self::DBO_IGNORE );
}
return $res;
return $old;
}
- /**
- * Set the filehandle to copy write statements to.
- *
- * @param resource $fh File handle
- */
- public function setFileHandle( $fh ) {
- $this->fileHandle = $fh;
- }
-
public function getLBInfo( $name = null ) {
if ( is_null( $name ) ) {
return $this->mLBInfo;
* @see setLazyMasterHandle()
* @since 1.27
*/
- public function getLazyMasterHandle() {
+ protected function getLazyMasterHandle() {
return $this->lazyMasterHandle;
}
}
public function doneWrites() {
- return (bool)$this->mDoneWrites;
+ return (bool)$this->mLastWriteTime;
}
public function lastDoneWrites() {
- return $this->mDoneWrites ?: false;
+ return $this->mLastWriteTime ?: false;
}
public function writesPending() {
return !!( $this->mFlags & $flag );
}
+ /**
+ * @param string $name Class field name
+ * @return mixed
+ * @deprecated Since 1.28
+ */
public function getProperty( $name ) {
return $this->$name;
}
protected function installErrorHandler() {
$this->mPHPError = false;
$this->htmlErrors = ini_set( 'html_errors', '0' );
- set_error_handler( [ $this, 'connectionerrorLogger' ] );
+ set_error_handler( [ $this, 'connectionErrorLogger' ] );
}
/**
if ( $this->htmlErrors !== false ) {
ini_set( 'html_errors', $this->htmlErrors );
}
+
+ return $this->getLastPHPError();
+ }
+
+ /**
+ * @return string|bool Last PHP error for this DB (typically connection errors)
+ */
+ protected function getLastPHPError() {
if ( $this->mPHPError ) {
$error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
$error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
return $error;
- } else {
- return false;
}
+
+ return false;
}
/**
+ * This method should not be used outside of Database classes
+ *
* @param int $errno
* @param string $errstr
*/
- public function connectionerrorLogger( $errno, $errstr ) {
+ public function connectionErrorLogger( $errno, $errstr ) {
$this->mPHPError = $errstr;
}
*/
abstract protected function closeConnection();
- function reportConnectionError( $error = 'Unknown error' ) {
+ public function reportConnectionError( $error = 'Unknown error' ) {
$myError = $this->lastError();
if ( $myError ) {
$error = $myError;
* @return bool
*/
protected function isTransactableQuery( $sql ) {
- $verb = $this->getQueryVerb( $sql );
- return !in_array( $verb, [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ], true );
+ return !in_array(
+ $this->getQueryVerb( $sql ),
+ [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET', 'CREATE', 'ALTER' ],
+ true
+ );
}
/**
*/
protected function registerTempTableOperation( $sql ) {
if ( preg_match(
- '/^(CREATE|DROP)\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+ '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
$sql,
$matches
) ) {
- list( , $verb, $table ) = $matches;
- if ( $verb === 'CREATE' ) {
- $this->mSessionTempTables[$table] = 1;
- } else {
- unset( $this->mSessionTempTables[$table] );
- }
+ $this->mSessionTempTables[$matches[1]] = 1;
+
+ return true;
+ } elseif ( preg_match(
+ '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+ $sql,
+ $matches
+ ) ) {
+ unset( $this->mSessionTempTables[$matches[1]] );
return true;
} elseif ( preg_match(
throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
}
# Set a flag indicating that writes have been done
- $this->mDoneWrites = microtime( true );
+ $this->mLastWriteTime = microtime( true );
}
// Add trace comment to the begin of the sql string, right after the operator.
- // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
+ // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
$commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
# Start implicit transactions that wrap the request if DBO_TRX is enabled
- if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX )
+ if ( !$this->mTrxLevel && $this->getFlag( self::DBO_TRX )
&& $this->isTransactableQuery( $sql )
) {
$this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
- if ( $this->getFlag( DBO_DEBUG ) ) {
+ if ( $this->getFlag( self::DBO_DEBUG ) ) {
$this->queryLogger->debug( "{$this->mDBname} {$commentedSql}" );
}
if ( false === $ret ) {
# Deadlocks cause the entire transaction to abort, not just the statement.
- # http://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html
+ # https://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html
# https://www.postgresql.org/docs/9.1/static/explicit-locking.html
if ( $this->wasDeadlock() ) {
if ( $this->explicitTrxActive() || $priorWritesPending ) {
private function handleSessionLoss() {
$this->mTrxLevel = 0;
- $this->mTrxIdleCallbacks = []; // bug 65263
- $this->mTrxPreCommitCallbacks = []; // bug 65263
+ $this->mTrxIdleCallbacks = []; // T67263
+ $this->mTrxPreCommitCallbacks = []; // T67263
$this->mSessionTempTables = [];
$this->mNamedLocksHeld = [];
try {
* @param array $options Associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return array
- * @see DatabaseBase::select()
+ * @see Database::select()
*/
- public function makeSelectOptions( $options ) {
+ protected function makeSelectOptions( $options ) {
$preLimitTail = $postLimitTail = '';
$startOpts = '';
$preLimitTail .= $this->makeOrderBy( $options );
- // if (isset($options['LIMIT'])) {
- // $tailOpts .= $this->limitResult('', $options['LIMIT'],
- // isset($options['OFFSET']) ? $options['OFFSET']
- // : false);
- // }
-
if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
$postLimitTail .= ' FOR UPDATE';
}
*
* @param array $options Associative array of options
* @return string
- * @see DatabaseBase::select()
+ * @see Database::select()
* @since 1.21
*/
- public function makeGroupByWithHaving( $options ) {
+ protected function makeGroupByWithHaving( $options ) {
$sql = '';
if ( isset( $options['GROUP BY'] ) ) {
$gb = is_array( $options['GROUP BY'] )
*
* @param array $options Associative array of options
* @return string
- * @see DatabaseBase::select()
+ * @see Database::select()
* @since 1.21
*/
- public function makeOrderBy( $options ) {
+ protected function makeOrderBy( $options ) {
if ( isset( $options['ORDER BY'] ) ) {
$ob = is_array( $options['ORDER BY'] )
? implode( ',', $options['ORDER BY'] )
return '';
}
- // See IDatabase::select for the docs for this function
public function select( $table, $vars, $conds = '', $fname = __METHOD__,
$options = [], $join_conds = [] ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
$useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
? $options['USE INDEX']
: [];
- $ignoreIndexes = ( isset( $options['IGNORE INDEX'] ) && is_array( $options['IGNORE INDEX'] ) )
+ $ignoreIndexes = (
+ isset( $options['IGNORE INDEX'] ) &&
+ is_array( $options['IGNORE INDEX'] )
+ )
? $options['IGNORE INDEX']
: [];
if ( is_array( $table ) ) {
$from = ' FROM ' .
- $this->tableNamesWithIndexClauseOrJOIN( $table, $useIndexes, $ignoreIndexes, $join_conds );
+ $this->tableNamesWithIndexClauseOrJOIN(
+ $table, $useIndexes, $ignoreIndexes, $join_conds );
} elseif ( $table != '' ) {
if ( $table[0] == ' ' ) {
$from = ' FROM ' . $table;
} else {
$from = ' FROM ' .
- $this->tableNamesWithIndexClauseOrJOIN( [ $table ], $useIndexes, $ignoreIndexes, [] );
+ $this->tableNamesWithIndexClauseOrJOIN(
+ [ $table ], $useIndexes, $ignoreIndexes, [] );
}
} else {
$from = '';
if ( is_array( $conds ) ) {
$conds = $this->makeList( $conds, self::LIST_AND );
}
- $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex WHERE $conds $preLimitTail";
+ $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex " .
+ "WHERE $conds $preLimitTail";
} else {
$sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
}
) {
$rows = 0;
$sql = $this->selectSQLText( $tables, '1', $conds, $fname, $options, $join_conds );
- $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count", $fname );
+ // The identifier quotes is primarily for MSSQL.
+ $rowCountCol = $this->addIdentifierQuotes( "rowcount" );
+ $tableName = $this->addIdentifierQuotes( "tmp_count" );
+ $res = $this->query( "SELECT COUNT(*) AS $rowCountCol FROM ($sql) $tableName", $fname );
if ( $res ) {
$row = $this->fetchRow( $res );
}
public function tableExists( $table, $fname = __METHOD__ ) {
+ $tableRaw = $this->tableName( $table, 'raw' );
+ if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
+ return true; // already known to exist
+ }
+
$table = $this->tableName( $table );
- $old = $this->ignoreErrors( true );
- $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
- $this->ignoreErrors( $old );
+ $ignoreErrors = true;
+ $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname, $ignoreErrors );
return (bool)$res;
}
}
/**
- * Helper for DatabaseBase::insert().
+ * Helper for Database::insert().
*
* @param array $options
* @return string
}
/**
- * Make UPDATE options array for DatabaseBase::makeUpdateOptions
+ * Make UPDATE options array for Database::makeUpdateOptions
*
* @param array $options
* @return array
}
/**
- * Make UPDATE options for the DatabaseBase::update function
+ * Make UPDATE options for the Database::update function
*
- * @param array $options The options passed to DatabaseBase::update
+ * @param array $options The options passed to Database::update
* @return string
*/
protected function makeUpdateOptions( $options ) {
return implode( ' ', $opts );
}
- function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+ public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
$table = $this->tableName( $table );
$opts = $this->makeUpdateOptions( $options );
$sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET );
$sql .= " WHERE " . $this->makeList( $conds, self::LIST_AND );
}
- return $this->query( $sql, $fname );
+ return (bool)$this->query( $sql, $fname );
}
public function makeList( $a, $mode = self::LIST_COMMA ) {
}
}
- /**
- * Return aggregated value alias
- *
- * @param array $valuedata
- * @param string $valuename
- *
- * @return string
- */
public function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuename;
}
return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
}
- /**
- * @param string $field Field or column to cast
- * @return string
- * @since 1.28
- */
public function buildStringCast( $field ) {
return $field;
}
return $this->mServer;
}
- /**
- * Format a table name ready for use in constructing an SQL query
- *
- * This does two important things: it quotes the table names to clean them up,
- * and it adds a table prefix if only given a table name with no quotes.
- *
- * All functions of this object which require a table name call this function
- * themselves. Pass the canonical name to such functions. This is only needed
- * when calling query() directly.
- *
- * @note This function does not sanitize user input. It is not safe to use
- * this function to escape user input.
- * @param string $name Database table name
- * @param string $format One of:
- * quoted - Automatically pass the table name through addIdentifierQuotes()
- * so that it can be used in a query.
- * raw - Do not add identifier quotes to the table name
- * @return string Full database name
- */
public function tableName( $name, $format = 'quoted' ) {
# 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
} elseif ( count( $dbDetails ) == 2 ) {
list( $database, $table ) = $dbDetails;
# We don't want any prefix added in this case
+ $prefix = '';
# In dbs that support it, $database may actually be the schema
# but that doesn't affect any of the functionality here
- $prefix = '';
$schema = '';
} else {
list( $table ) = $dbDetails;
# Quote $table and apply the prefix if not quoted.
# $tableName might be empty if this is called from Database::replaceVars()
$tableName = "{$prefix}{$table}";
- if ( $format == 'quoted'
- && !$this->isQuotedIdentifier( $tableName ) && $tableName !== ''
+ if ( $format === 'quoted'
+ && !$this->isQuotedIdentifier( $tableName )
+ && $tableName !== ''
) {
$tableName = $this->addIdentifierQuotes( $tableName );
}
- # Quote $schema and merge it with the table name if needed
- if ( strlen( $schema ) ) {
- if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) {
- $schema = $this->addIdentifierQuotes( $schema );
- }
- $tableName = $schema . '.' . $tableName;
- }
-
- # Quote $database and merge it with the table name if needed
- if ( $database !== '' ) {
- if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
- $database = $this->addIdentifierQuotes( $database );
- }
- $tableName = $database . '.' . $tableName;
- }
+ # Quote $schema and $database and merge them with the table name if needed
+ $tableName = $this->prependDatabaseOrSchema( $schema, $tableName, $format );
+ $tableName = $this->prependDatabaseOrSchema( $database, $tableName, $format );
return $tableName;
}
/**
- * Fetch a number of table names into an array
- * This is handy when you need to construct SQL for joins
- *
- * Example:
- * extract( $dbr->tableNames( 'user', 'watchlist' ) );
- * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
- * WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
- *
- * @return array
+ * @param string|null $namespace Database or schema
+ * @param string $relation Name of table, view, sequence, etc...
+ * @param string $format One of (raw, quoted)
+ * @return string Relation name with quoted and merged $namespace as needed
*/
+ private function prependDatabaseOrSchema( $namespace, $relation, $format ) {
+ if ( strlen( $namespace ) ) {
+ if ( $format === 'quoted' && !$this->isQuotedIdentifier( $namespace ) ) {
+ $namespace = $this->addIdentifierQuotes( $namespace );
+ }
+ $relation = $namespace . '.' . $relation;
+ }
+
+ return $relation;
+ }
+
public function tableNames() {
$inArray = func_get_args();
$retVal = [];
return $retVal;
}
- /**
- * Fetch a number of table names into an zero-indexed numerical array
- * This is handy when you need to construct SQL for joins
- *
- * Example:
- * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' );
- * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
- * WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
- *
- * @return array
- */
public function tableNamesN() {
$inArray = func_get_args();
$retVal = [];
* @param string|bool $alias Alias (optional)
* @return string SQL name for aliased table. Will not alias a table to its own name
*/
- public function tableNameWithAlias( $name, $alias = false ) {
+ protected function tableNameWithAlias( $name, $alias = false ) {
if ( !$alias || $alias == $name ) {
return $this->tableName( $name );
} else {
* @param array $tables [ [alias] => table ]
* @return string[] See tableNameWithAlias()
*/
- public function tableNamesWithAlias( $tables ) {
+ protected function tableNamesWithAlias( $tables ) {
$retval = [];
foreach ( $tables as $alias => $table ) {
if ( is_numeric( $alias ) ) {
* @param string|bool $alias Alias (optional)
* @return string SQL name for aliased field. Will not alias a field to its own name
*/
- public function fieldNameWithAlias( $name, $alias = false ) {
+ protected function fieldNameWithAlias( $name, $alias = false ) {
if ( !$alias || (string)$alias === (string)$name ) {
return $name;
} else {
* @param array $fields [ [alias] => field ]
* @return string[] See fieldNameWithAlias()
*/
- public function fieldNamesWithAlias( $fields ) {
+ protected function fieldNamesWithAlias( $fields ) {
$retval = [];
foreach ( $fields as $alias => $field ) {
if ( is_numeric( $alias ) ) {
}
}
if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
- $ignore = $this->ignoreIndexClause( implode( ',', (array)$ignore_index[$alias] ) );
+ $ignore = $this->ignoreIndexClause(
+ implode( ',', (array)$ignore_index[$alias] ) );
if ( $ignore != '' ) {
$tableClause .= ' ' . $ignore;
}
$this->query( $sql, $fname );
}
- /**
- * Returns the size of a text field, or -1 for "unlimited"
- *
- * @param string $table
- * @param string $field
- * @return int
- */
public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
return $this->insert( $destTable, $rows, $fname, $insertOptions );
}
- public function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
+ protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
$fname = __METHOD__,
$insertOptions = [], $selectOptions = []
) {
$selectOptions );
if ( is_array( $srcTable ) ) {
- $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
+ $srcTable = implode( ',', array_map( [ $this, 'tableName' ], $srcTable ) );
} else {
$srcTable = $this->tableName( $srcTable );
}
- $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
+ $sql = "INSERT $insertOptions" .
+ " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
" SELECT $startOpts " . implode( ',', $varMap ) .
" FROM $srcTable $useIndex $ignoreIndex ";
*/
public function limitResult( $sql, $limit, $offset = false ) {
if ( !is_numeric( $limit ) ) {
- throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
+ throw new DBUnexpectedError( $this,
+ "Invalid non-numeric limit passed to limitResult()\n" );
}
return "$sql LIMIT "
}
/**
- * Determines if the given query error was a connection drop
- * STUB
+ * Do not use this method outside of Database/DBError classes
*
* @param integer|string $errno
- * @return bool
+ * @return bool Whether the given query error was a connection drop
*/
public function wasConnectionError( $errno ) {
return false;
}
- /**
- * Perform a deadlock-prone transaction.
- *
- * This function invokes a callback function to perform a set of write
- * queries. If a deadlock occurs during the processing, the transaction
- * will be rolled back and the callback function will be called again.
- *
- * Avoid using this method outside of Job or Maintenance classes.
- *
- * Usage:
- * $dbw->deadlockLoop( callback, ... );
- *
- * Extra arguments are passed through to the specified callback function.
- * This method requires that no transactions are already active to avoid
- * causing premature commits or exceptions.
- *
- * Returns whatever the callback function returned on its successful,
- * iteration, or false on error, for example if the retry limit was
- * reached.
- *
- * @return mixed
- * @throws DBUnexpectedError
- * @throws Exception
- */
public function deadlockLoop() {
$args = func_get_args();
$function = array_shift( $args );
return 0;
}
- public function getSlavePos() {
+ public function getReplicaPos() {
# Stub
return false;
}
return;
}
- $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
+ $autoTrx = $this->getFlag( self::DBO_TRX ); // automatic begin() enabled?
/** @var Exception $e */
$e = null; // first exception
do { // callbacks may add callbacks :)
foreach ( $callbacks as $callback ) {
try {
list( $phpCallback ) = $callback;
- $this->clearFlag( DBO_TRX ); // make each query its own transaction
+ $this->clearFlag( self::DBO_TRX ); // make each query its own transaction
call_user_func_array( $phpCallback, [ $trigger ] );
if ( $autoTrx ) {
- $this->setFlag( DBO_TRX ); // restore automatic begin()
+ $this->setFlag( self::DBO_TRX ); // restore automatic begin()
} else {
- $this->clearFlag( DBO_TRX ); // restore auto-commit
+ $this->clearFlag( self::DBO_TRX ); // restore auto-commit
}
} catch ( Exception $ex ) {
call_user_func( $this->errorLogger, $ex );
$this->begin( $fname, self::TRANSACTION_INTERNAL );
// If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
// in all changes being in one transaction to keep requests transactional.
- if ( !$this->getFlag( DBO_TRX ) ) {
+ if ( !$this->getFlag( self::DBO_TRX ) ) {
$this->mTrxAutomaticAtomic = true;
}
}
$this->queryLogger->error( $msg );
return; // join the main transaction set
}
- } elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
+ } elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
// @TODO: make this an exception at some point
$msg = "$fname: Implicit transaction expected (DBO_TRX set).";
$this->queryLogger->error( $msg );
$this->mTrxTimestamp = microtime( true );
$this->mTrxFname = $fname;
$this->mTrxDoneWrites = false;
- $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
$this->mTrxAutomaticAtomic = false;
$this->mTrxAtomicLevels = [];
$this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
// as lag itself just to be safe
$status = $this->getApproximateLagStatus();
$this->mTrxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] );
+ // T147697: make explicitTrxActive() return true until begin() finishes. This way, no
+ // caller will think its OK to muck around with the transaction just because startAtomic()
+ // has not yet completed (e.g. setting mTrxAtomicLevels).
+ $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
}
/**
* Issues the BEGIN command to the database server.
*
- * @see DatabaseBase::begin()
+ * @see Database::begin()
* @param string $fname
*/
protected function doBegin( $fname ) {
}
} else {
if ( !$this->mTrxLevel ) {
- $this->queryLogger->error( "$fname: No transaction to commit, something got out of sync." );
+ $this->queryLogger->error(
+ "$fname: No transaction to commit, something got out of sync." );
return; // nothing to do
} elseif ( $this->mTrxAutomatic ) {
// @TODO: make this an exception at some point
$writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- $this->mDoneWrites = microtime( true );
+ $this->mLastWriteTime = microtime( true );
$this->trxProfiler->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
}
/**
* Issues the COMMIT command to the database server.
*
- * @see DatabaseBase::commit()
+ * @see Database::commit()
* @param string $fname
*/
protected function doCommit( $fname ) {
$this->queryLogger->error(
"$fname: No transaction to rollback, something got out of sync." );
return; // nothing to do
- } elseif ( $this->getFlag( DBO_TRX ) ) {
+ } elseif ( $this->getFlag( self::DBO_TRX ) ) {
throw new DBUnexpectedError(
$this,
"$fname: Expected mass rollback of all peer databases (DBO_TRX set)."
/**
* Issues the ROLLBACK command to the database server.
*
- * @see DatabaseBase::rollback()
+ * @see Database::rollback()
* @param string $fname
*/
protected function doRollback( $fname ) {
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
- "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)."
+ "$fname: Cannot flush snapshot because writes are pending ($fnames)."
);
}
return $this->mTrxLevel && ( $this->mTrxAtomicLevels || !$this->mTrxAutomatic );
}
- /**
- * Creates a new table with structure copied from existing table
- * Note that unlike most database abstraction functions, this function does not
- * automatically append database prefix, because it works at a lower
- * abstraction level.
- * The table names passed to this function shall not be quoted (this
- * function calls addIdentifierQuotes when needed).
- *
- * @param string $oldName Name of table whose structure should be copied
- * @param string $newName Name of table to be created
- * @param bool $temporary Whether the new table should be temporary
- * @param string $fname Calling function name
- * @throws RuntimeException
- * @return bool True if operation was successful
- */
- public function duplicateTableStructure( $oldName, $newName, $temporary = false,
- $fname = __METHOD__
+ public function duplicateTableStructure(
+ $oldName, $newName, $temporary = false, $fname = __METHOD__
) {
throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
- function listTables( $prefix = null, $fname = __METHOD__ ) {
+ public function listTables( $prefix = null, $fname = __METHOD__ ) {
throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
- /**
- * Reset the views process cache set by listViews()
- * @since 1.22
- */
- final public function clearViewsCache() {
- $this->allViews = null;
- }
-
- /**
- * Lists all the VIEWs in the database
- *
- * 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
- * @throws RuntimeException
- * @return array
- * @since 1.22
- */
public function listViews( $prefix = null, $fname = __METHOD__ ) {
throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
- /**
- * Differentiates between a TABLE and a VIEW
- *
- * @param string $name Name of the database-structure to test.
- * @throws RuntimeException
- * @return bool
- * @since 1.22
- */
- public function isView( $name ) {
- throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
- }
-
public function timestamp( $ts = 0 ) {
- $t = new ConvertableTimestamp( $ts );
+ $t = new ConvertibleTimestamp( $ts );
// Let errors bubble up to avoid putting garbage in the DB
return $t->getTimestamp( TS_MW );
}
* necessary. Boolean values are passed through as is, to indicate success
* of write queries or failure.
*
- * Once upon a time, DatabaseBase::query() returned a bare MySQL result
+ * Once upon a time, Database::query() returned a bare MySQL result
* resource, and it was necessary to call this function to convert it to
* a wrapper. Nowadays, raw database objects are never exposed to external
* callers, so this is unnecessary in external code.
}
// This will reconnect if possible or return false if not
- $this->clearFlag( DBO_TRX, self::REMEMBER_PRIOR );
+ $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
$ok = ( $this->query( self::PING_QUERY, __METHOD__, true ) !== false );
$this->restoreFlags( self::RESTORE_PRIOR );
* @return array|null ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
* @since 1.27
*/
- public function getTransactionLagStatus() {
+ protected function getTransactionLagStatus() {
return $this->mTrxLevel
? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ]
: null;
* @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate)
* @since 1.27
*/
- public function getApproximateLagStatus() {
+ protected function getApproximateLagStatus() {
return [
'lag' => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
'since' => microtime( true )
return 0;
}
- function maxListLen() {
+ public function maxListLen() {
return 0;
}
public function setSessionOptions( array $options ) {
}
- /**
- * Read and execute SQL commands from a file.
- *
- * Returns true on success, error string or exception on failure (depending
- * on object's error ignore settings).
- *
- * @param string $filename File name to open
- * @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 Optional function called for each
- * complete line sent
- * @return bool|string
- * @throws Exception
- */
public function sourceFile(
- $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
+ $filename,
+ callable $lineCallback = null,
+ callable $resultCallback = null,
+ $fname = false,
+ callable $inputCallback = null
) {
MediaWiki\suppressWarnings();
$fp = fopen( $filename, 'r' );
}
try {
- $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
+ $error = $this->sourceStream(
+ $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
} catch ( Exception $e ) {
fclose( $fp );
throw $e;
$this->mSchemaVars = $vars;
}
- /**
- * Read and execute commands from an open file handle.
- *
- * Returns true on success, error string or exception on failure (depending
- * on object's error ignore settings).
- *
- * @param resource $fp File handle
- * @param bool|callable $lineCallback Optional function called before reading each query
- * @param bool|callable $resultCallback Optional function called for each MySQL result
- * @param string $fname Calling function name
- * @param bool|callable $inputCallback Optional function called for each complete query sent
- * @return bool|string
- */
- public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = __METHOD__, $inputCallback = false
+ public function sourceStream(
+ $fp,
+ callable $lineCallback = null,
+ callable $resultCallback = null,
+ $fname = __METHOD__,
+ callable $inputCallback = null
) {
$cmd = '';
if ( $done || feof( $fp ) ) {
$cmd = $this->replaceVars( $cmd );
- if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
+ if ( !$inputCallback || call_user_func( $inputCallback, $cmd ) ) {
$res = $this->query( $cmd, $fname );
if ( $resultCallback ) {
/**
* Called by sourceStream() to check if we've reached a statement end
*
- * @param string $sql SQL assembled so far
- * @param string $newLine New line about to be added to $sql
+ * @param string &$sql SQL assembled so far
+ * @param string &$newLine 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 );
+ $newLine = preg_replace(
+ '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
if ( $newLine != $prev ) {
return true;
}
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
- "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)."
+ "$fname: Cannot flush pre-lock snapshot because writes are pending ($fnames)."
);
}
return 'infinity';
}
- try {
- $t = new ConvertableTimestamp( $expiry );
-
- return $t->getTimestamp( $format );
- } catch ( TimestampException $e ) {
- return false;
- }
+ return ConvertibleTimestamp::convert( $format, $expiry );
}
public function setBigSelects( $value = true ) {
return true;
}
+ /**
+ * Get the underlying binding handle, mConn
+ *
+ * Makes sure that mConn is set (disconnects and ping() failure can unset it).
+ * This catches broken callers than catch and ignore disconnection exceptions.
+ * Unlike checking isOpen(), this is safe to call inside of open().
+ *
+ * @return resource|object
+ * @throws DBUnexpectedError
+ * @since 1.26
+ */
+ protected function getBindingHandle() {
+ if ( !$this->mConn ) {
+ throw new DBUnexpectedError(
+ $this,
+ 'DB connection was already closed or the connection dropped.'
+ );
+ }
+
+ return $this->mConn;
+ }
+
/**
* @since 1.19
* @return string
*/
public function __clone() {
$this->connLogger->warning(
- "Cloning " . get_class( $this ) . " is not recomended; forking connection:\n" .
+ "Cloning " . static::class . " is not recomended; forking connection:\n" .
( new RuntimeException() )->getTraceAsString()
);
// Open a new connection resource without messing with the old one
$this->mOpened = false;
$this->mConn = false;
- $this->mTrxLevel = 0; // no trx anymore
+ $this->mTrxEndCallbacks = []; // don't copy
+ $this->handleSessionLoss(); // no trx or locks anymore
$this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
$this->lastPing = microtime( true );
}
}
if ( $this->mConn ) {
- // Avoid connection leaks for sanity
+ // Avoid connection leaks for sanity. Normally, resources close at script completion.
+ // The connection might already be closed in zend/hhvm by now, so suppress warnings.
+ \MediaWiki\suppressWarnings();
$this->closeConnection();
+ \MediaWiki\restoreWarnings();
$this->mConn = false;
$this->mOpened = false;
}
}
}
+
+class_alias( Database::class, 'DatabaseBase' );