* @since 1.28
*/
abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAwareInterface {
- /** @var string Server that this instance is currently connected to */
- protected $server;
- /** @var string User that this instance is currently connected under the name of */
- protected $user;
- /** @var string Password used to establish the current connection */
- protected $password;
- /** @var array[] Map of (table => (dbname, schema, prefix) map) */
- protected $tableAliases = [];
- /** @var string[] Map of (index alias => index) */
- protected $indexAliases = [];
- /** @var bool Whether this PHP instance is for a CLI script */
- protected $cliMode;
- /** @var string Agent name for query profiling */
- protected $agent;
- /** @var int Bit field of class DBO_* constants */
- protected $flags;
- /** @var array LoadBalancer tracking information */
- protected $lbInfo = [];
- /** @var array|bool Variables use for schema element placeholders */
- protected $schemaVars = false;
- /** @var array Parameters used by initConnection() to establish a connection */
- protected $connectionParams = [];
- /** @var array SQL variables values to use for all new connections */
- protected $connectionVariables = [];
- /** @var string Current SQL query delimiter */
- protected $delimiter = ';';
- /** @var string|bool|null Stashed value of html_errors INI setting */
- protected $htmlErrors;
- /** @var int Row batch size to use for emulated INSERT SELECT queries */
- protected $nonNativeInsertSelectBatchSize = 10000;
-
/** @var BagOStuff APC cache */
protected $srvCache;
/** @var LoggerInterface */
protected $profiler;
/** @var TransactionProfiler */
protected $trxProfiler;
+
/** @var DatabaseDomain */
protected $currentDomain;
+
/** @var object|resource|null Database connection */
protected $conn;
/** @var IDatabase|null Lazy handle to the master DB this server replicates from */
private $lazyMasterHandle;
+ /** @var string Server that this instance is currently connected to */
+ protected $server;
+ /** @var string User that this instance is currently connected under the name of */
+ protected $user;
+ /** @var string Password used to establish the current connection */
+ protected $password;
+ /** @var bool Whether this PHP instance is for a CLI script */
+ protected $cliMode;
+ /** @var string Agent name for query profiling */
+ protected $agent;
+ /** @var array Parameters used by initConnection() to establish a connection */
+ protected $connectionParams;
+ /** @var string[]|int[]|float[] SQL variables values to use for all new connections */
+ protected $connectionVariables;
+ /** @var int Row batch size to use for emulated INSERT SELECT queries */
+ protected $nonNativeInsertSelectBatchSize;
+
+ /** @var int Current bit field of class DBO_* constants */
+ protected $flags;
+ /** @var array Current LoadBalancer tracking information */
+ protected $lbInfo = [];
+ /** @var string Current SQL query delimiter */
+ protected $delimiter = ';';
+ /** @var array[] Current map of (table => (dbname, schema, prefix) map) */
+ protected $tableAliases = [];
+ /** @var string[] Current map of (index alias => index) */
+ protected $indexAliases = [];
+ /** @var array|null Current variables use for schema element placeholders */
+ protected $schemaVars;
+
+ /** @var string|bool|null Stashed value of html_errors INI setting */
+ private $htmlErrors;
+ /** @var int[] Prior flags member variable values */
+ private $priorFlags = [];
+
/** @var array Map of (name => 1) for locks obtained via lock() */
protected $sessionNamedLocks = [];
/** @var array Map of (table name => 1) for TEMPORARY tables */
protected $sessionTempTables = [];
/** @var string ID of the active transaction or the empty string otherwise */
- protected $trxShortId = '';
+ private $trxShortId = '';
/** @var int Transaction status */
- protected $trxStatus = self::STATUS_TRX_NONE;
+ private $trxStatus = self::STATUS_TRX_NONE;
/** @var Exception|null The last error that caused the status to become STATUS_TRX_ERROR */
- protected $trxStatusCause;
+ private $trxStatusCause;
/** @var array|null Error details of the last statement-only rollback */
private $trxStatusIgnoredCause;
/** @var float|null UNIX timestamp at the time of BEGIN for the last transaction */
/** @var bool Whether to suppress triggering of transaction end callbacks */
private $trxEndCallbacksSuppressed = false;
- /** @var int[] Prior flags member variable values */
- private $priorFlags = [];
-
/** @var integer|null Rows affected by the last query to query() or its CRUD wrappers */
protected $affectedRowCount;
/** @var float Query rount trip time estimate */
private $lastRoundTripEstimate = 0.0;
+ /** @var string Whether the database is a file on disk */
+ const ATTR_DB_IS_FILE = 'db-is-file';
/** @var string Lock granularity is on the level of the entire database */
const ATTR_DB_LEVEL_LOCKING = 'db-level-locking';
/** @var string The SCHEMA keyword refers to a grouping of tables in a database */
* @note exceptions for missing libraries/drivers should be thrown in initConnection()
* @param array $params Parameters passed from Database::factory()
*/
- protected function __construct( array $params ) {
+ public function __construct( array $params ) {
+ $this->connectionParams = [];
foreach ( [ 'host', 'user', 'password', 'dbname', 'schema', 'tablePrefix' ] as $name ) {
$this->connectionParams[$name] = $params[$name];
}
-
+ $this->connectionVariables = $params['variables'] ?? [];
$this->cliMode = $params['cliMode'];
- // Agent name is added to SQL queries in a comment, so make sure it can't break out
- $this->agent = str_replace( '/', '-', $params['agent'] );
-
+ $this->agent = $params['agent'];
$this->flags = $params['flags'];
- if ( $this->flags & self::DBO_DEFAULT ) {
- if ( $this->cliMode ) {
- $this->flags &= ~self::DBO_TRX;
- } else {
- $this->flags |= self::DBO_TRX;
- }
- }
- // Disregard deprecated DBO_IGNORE flag (T189999)
- $this->flags &= ~self::DBO_IGNORE;
-
- $this->connectionVariables = $params['variables'];
+ $this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'] ?? 10000;
$this->srvCache = $params['srvCache'] ?? new HashBagOStuff();
-
$this->profiler = is_callable( $params['profiler'] ) ? $params['profiler'] : null;
$this->trxProfiler = $params['trxProfiler'];
$this->connLogger = $params['connLogger'];
$this->errorLogger = $params['errorLogger'];
$this->deprecationLogger = $params['deprecationLogger'];
- if ( isset( $params['nonNativeInsertSelectBatchSize'] ) ) {
- $this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'];
- }
-
// Set initial dummy domain until open() sets the final DB/prefix
$this->currentDomain = new DatabaseDomain(
$params['dbname'] != '' ? $params['dbname'] : null,
'cliMode' => (bool)$params['cliMode'],
'agent' => (string)$params['agent'],
// Objects and callbacks
+ 'srvCache' => $params['srvCache'] ?? new HashBagOStuff(),
'profiler' => $params['profiler'] ?? null,
'trxProfiler' => $params['trxProfiler'] ?? new TransactionProfiler(),
'connLogger' => $params['connLogger'] ?? new NullLogger(),
*/
final public static function attributesFromType( $dbType, $driver = null ) {
static $defaults = [
+ self::ATTR_DB_IS_FILE => false,
self::ATTR_DB_LEVEL_LOCKING => false,
self::ATTR_SCHEMAS_AS_TABLE_GROUPS => false
];
// we auto-detect the first available driver. For types without built-in support,
// an class named "Database<Type>" us used, eg. DatabaseFoo for type 'foo'.
static $builtinTypes = [
- 'mssql' => DatabaseMssql::class,
'mysql' => [ 'mysqli' => DatabaseMysqli::class ],
'sqlite' => DatabaseSqlite::class,
'postgres' => DatabasePostgres::class,
}
/**
- * @return array Map of (Database::ATTR_* constant => value
+ * @return array Map of (Database::ATTR_* constant => value)
* @since 1.31
*/
protected static function getAttributes() {
return $this->getServerVersion();
}
+ /**
+ * Backwards-compatibility no-op method for disabling query buffering
+ *
+ * @param null|bool $buffer Whether to buffer queries (ignored)
+ * @return bool Whether buffering was already enabled (always true)
+ * @deprecated Since 1.34 Use query batching; this no longer does anything
+ */
public function bufferResults( $buffer = null ) {
- $res = !$this->getFlag( self::DBO_NOBUFFER );
- if ( $buffer !== null ) {
- $buffer
- ? $this->clearFlag( self::DBO_NOBUFFER )
- : $this->setFlag( self::DBO_NOBUFFER );
- }
-
- return $res;
+ return true;
}
final public function trxLevel() {
* @throws DBReadOnlyError
*/
protected function assertIsWritableMaster() {
- if ( $this->getLBInfo( 'replica' ) === true ) {
+ if ( $this->getLBInfo( 'replica' ) ) {
throw new DBReadOnlyRoleError(
$this,
'Write operations are not allowed on replica database connections'
// Send the query to the server and fetch any corresponding errors
list( $ret, $err, $errno, $unignorable ) = $this->executeQuery( $sql, $fname, $flags );
if ( $ret === false ) {
- $ignoreErrors = $this->hasFlags( $flags, self::QUERY_SILENCE_ERRORS );
+ $ignoreErrors = $this->fieldHasBit( $flags, self::QUERY_SILENCE_ERRORS );
// Throw an error unless both the ignore flag was set and a rollback is not needed
$this->reportQueryError( $err, $errno, $sql, $fname, $ignoreErrors && !$unignorable );
}
// Do not treat temporary table writes as "meaningful writes" since they are only
// visible to one session and are not permanent. Profile them as reads. Integration
// tests can override this behavior via $flags.
- $pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT );
+ $pseudoPermanent = $this->fieldHasBit( $flags, self::QUERY_PSEUDO_PERMANENT );
list( $tmpType, $tmpNew, $tmpDel ) = $this->getTempWrites( $sql, $pseudoPermanent );
$isPermWrite = ( $tmpType !== self::$TEMP_NORMAL );
// DBConnRef uses QUERY_REPLICA_ROLE to enforce the replica role for raw SQL queries
- if ( $isPermWrite && $this->hasFlags( $flags, self::QUERY_REPLICA_ROLE ) ) {
+ if ( $isPermWrite && $this->fieldHasBit( $flags, self::QUERY_REPLICA_ROLE ) ) {
throw new DBReadOnlyRoleError( $this, "Cannot write; target role is DB_REPLICA" );
}
} else {
}
// 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 (T44598)
- $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
+ // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598).
+ $encAgent = str_replace( '/', '-', $this->agent );
+ $commentedSql = preg_replace( '/\s|$/', " /* $fname $encAgent */ ", $sql, 1 );
// Send the query to the server and fetch any corresponding errors.
// This also doubles as a "ping" to see if the connection was dropped.
$this->executeQueryAttempt( $sql, $commentedSql, $isPermWrite, $fname, $flags );
// Check if the query failed due to a recoverable connection loss
- $allowRetry = !$this->hasFlags( $flags, self::QUERY_NO_RETRY );
+ $allowRetry = !$this->fieldHasBit( $flags, self::QUERY_NO_RETRY );
if ( $ret === false && $recoverableCL && $reconnected && $allowRetry ) {
// Silently resend the query to the server since it is safe and possible
list( $ret, $err, $errno, $recoverableSR, $recoverableCL ) =
}
}
- $prefix = !is_null( $this->getLBInfo( 'master' ) ) ? 'query-m: ' : 'query: ';
+ $prefix = $this->getLBInfo( 'master' ) ? 'query-m: ' : 'query: ';
$generalizedSql = new GeneralizedSql( $sql, $this->trxShortId, $prefix );
$startTime = microtime( true );
$startOpts .= ' /*! STRAIGHT_JOIN */';
}
- if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
- $startOpts .= ' HIGH_PRIORITY';
- }
-
if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
$startOpts .= ' SQL_BIG_RESULT';
}
$startOpts .= ' SQL_CALC_FOUND_ROWS';
}
- if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
- $startOpts .= ' SQL_CACHE';
- }
-
- if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
- $startOpts .= ' SQL_NO_CACHE';
- }
-
if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
$useIndex = $this->useIndexClause( $options['USE INDEX'] );
} else {
}
// This will reconnect if possible or return false if not
- $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
- $ok = ( $this->query( self::$PING_QUERY, __METHOD__, true ) !== false );
- $this->restoreFlags( self::RESTORE_PRIOR );
-
+ $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_SILENCE_ERRORS;
+ $ok = ( $this->query( self::$PING_QUERY, __METHOD__, $flags ) !== false );
if ( $ok ) {
$rtt = $this->lastRoundTripEstimate;
}
}
public function setSchemaVars( $vars ) {
- $this->schemaVars = $vars;
+ $this->schemaVars = is_array( $vars ) ? $vars : null;
}
public function sourceStream(
* @return array
*/
protected function getSchemaVars() {
- if ( $this->schemaVars ) {
- return $this->schemaVars;
- } else {
- return $this->getDefaultSchemaVars();
- }
+ return $this->schemaVars ?? $this->getDefaultSchemaVars();
}
/**
* @param int $field
* @param int $flags
* @return bool
+ * @since 1.34
*/
- protected function hasFlags( $field, $flags ) {
+ final protected function fieldHasBit( $field, $flags ) {
return ( ( $field & $flags ) === $flags );
}