*
* The StatusValue will be "OK" unless:
* - a) unexpected operation errors occurred (network partitions, disk full...)
- * - b) significant operation errors occurred and 'force' was not set
+ * - b) predicted operation errors occurred and 'force' was not set
*
* @param array $ops List of operations to execute in order
* @param array $opts Batch operation options
* any checks from "syncChecks" are still synchronous.
*
* @param array $config
- * @throws FileBackendError
+ * @throws LogicException
*/
public function __construct( array $config ) {
parent::__construct( $config );
$masterStatus = $mbe->doOperations( $realOps, $opts );
$status->merge( $masterStatus );
// Propagate the operations to the clone backends if there were no unexpected errors
- // and if there were either no expected errors or if the 'force' option was used.
- // However, if nothing succeeded at all, then don't replicate any of the operations.
- // If $ops only had one operation, this might avoid backend sync inconsistencies.
+ // and everything didn't fail due to predicted errors. If $ops only had one operation,
+ // this might avoid backend sync inconsistencies.
if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
foreach ( $this->backends as $index => $backend ) {
if ( $index === $this->masterIndex ) {
continue;
}
}
- if ( ( $this->syncChecks & self::CHECK_SHA1 ) && $cBackend->getFileSha1Base36( $cParams ) !== $mSha1 ) { // wrong SHA1
+ if (
+ ( $this->syncChecks & self::CHECK_SHA1 ) &&
+ $cBackend->getFileSha1Base36( $cParams ) !== $mSha1
+ ) { // wrong SHA1
$status->fatal( 'backend-fail-synced', $path );
continue;
}
*
* The resulting StatusValue will be "OK" unless:
* - a) unexpected operation errors occurred (network partitions, disk full...)
- * - b) significant operation errors occurred and 'force' was not set
+ * - b) predicted operation errors occurred and 'force' was not set
*
* @param FileOp[] $performOps List of FileOp operations
* @param array $opts Batch operation options
return true;
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
return apc_inc( $key . self::KEY_SUFFIX, $value );
}
- public function decr( $key, $value = 1 ) {
+ public function decr( $key, $value = 1, $flags = 0 ) {
return apc_dec( $key . self::KEY_SUFFIX, $value );
}
}
return true;
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
// https://github.com/krakjoe/apcu/issues/166
if ( apcu_exists( $key . self::KEY_SUFFIX ) ) {
return apcu_inc( $key . self::KEY_SUFFIX, $value );
}
}
- public function decr( $key, $value = 1 ) {
+ public function decr( $key, $value = 1, $flags = 0 ) {
// https://github.com/krakjoe/apcu/issues/166
if ( apcu_exists( $key . self::KEY_SUFFIX ) ) {
return apcu_dec( $key . self::KEY_SUFFIX, $value );
* Increase stored value of $key by $value while preserving its TTL
* @param string $key Key to increase
* @param int $value Value to add to $key (default: 1) [optional]
+ * @param int $flags Bit field of class WRITE_* constants [optional]
* @return int|bool New value or false on failure
*/
- abstract public function incr( $key, $value = 1 );
+ abstract public function incr( $key, $value = 1, $flags = 0 );
/**
* Decrease stored value of $key by $value while preserving its TTL
* @param string $key
* @param int $value Value to subtract from $key (default: 1) [optional]
+ * @param int $flags Bit field of class WRITE_* constants [optional]
* @return int|bool New value or false on failure
*/
- abstract public function decr( $key, $value = 1 );
+ abstract public function decr( $key, $value = 1, $flags = 0 );
/**
- * Increase stored value of $key by $value while preserving its TTL
+ * Increase the value of the given key (no TTL change) if it exists or create it otherwise
*
- * This will create the key with value $init and TTL $ttl instead if not present
+ * This will create the key with the value $init and TTL $ttl instead if not present.
+ * Callers should make sure that both ($init - $value) and $ttl are invariants for all
+ * operations to any given key. The value of $init should be at least that of $value.
*
- * @param string $key
- * @param int $ttl
- * @param int $value
- * @param int $init
+ * @param string $key Key built via makeKey() or makeGlobalKey()
+ * @param int $exptime Time-to-live (in seconds) or a UNIX timestamp expiration
+ * @param int $value Amount to increase the key value by [default: 1]
+ * @param int|null $init Value to initialize the key to if it does not exist [default: $value]
+ * @param int $flags Bit field of class WRITE_* constants [optional]
* @return int|bool New value or false on failure
* @since 1.24
*/
- abstract public function incrWithInit( $key, $ttl, $value = 1, $init = 1 );
+ abstract public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 );
/**
* Get the "last error" registered; clearLastError() should be called manually
public function set( $key, $value, $exptime = 0, $flags = 0 ) {
$this->procCache->set( $key, $value, $exptime, $flags );
+
if ( !$this->fieldHasFlags( $flags, self::WRITE_CACHE_ONLY ) ) {
$this->backend->set( $key, $value, $exptime, $flags );
}
public function delete( $key, $flags = 0 ) {
$this->procCache->delete( $key, $flags );
+
if ( !$this->fieldHasFlags( $flags, self::WRITE_CACHE_ONLY ) ) {
$this->backend->delete( $key, $flags );
}
return true;
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
$this->procCache->delete( $key );
- return $this->backend->incr( $key, $value );
+ return $this->backend->incr( $key, $value, $flags );
}
- public function decr( $key, $value = 1 ) {
+ public function decr( $key, $value = 1, $flags = 0 ) {
$this->procCache->delete( $key );
- return $this->backend->decr( $key, $value );
+ return $this->backend->decr( $key, $value, $flags );
}
- public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
+ public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
$this->procCache->delete( $key );
- return $this->backend->incrWithInit( $key, $ttl, $value, $init );
+ return $this->backend->incrWithInit( $key, $exptime, $value, $init, $flags );
}
public function addBusyCallback( callable $workCallback ) {
return true;
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
return false;
}
- public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
+ public function decr( $key, $value = 1, $flags = 0 ) {
+ return false;
+ }
+
+ public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
return false; // faster
}
return true;
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
$n = $this->get( $key );
if ( $this->isInteger( $n ) ) {
- $n = max( $n + intval( $value ), 0 );
+ $n = max( $n + (int)$value, 0 );
$this->bag[$key][self::KEY_VAL] = $n;
return $n;
return false;
}
+ public function decr( $key, $value = 1, $flags = 0 ) {
+ return $this->incr( $key, -$value, $flags );
+ }
+
/**
* Clear all values in cache
*/
$mainValue->{SerializedValueContainer::SEGMENTED_HASHES}
);
- return $this->deleteMulti( $orderedKeys, $flags );
+ return $this->deleteMulti( $orderedKeys, $flags & ~self::WRITE_PRUNE_SEGMENTS );
}
/**
final protected function mergeViaCas( $key, callable $callback, $exptime, $attempts, $flags ) {
$attemptsLeft = $attempts;
do {
- $casToken = null; // passed by reference
+ $token = null; // passed by reference
// Get the old value and CAS token from cache
$this->clearLastError();
$currentValue = $this->resolveSegments(
$key,
- $this->doGet( $key, self::READ_LATEST, $casToken )
+ $this->doGet( $key, $flags, $token )
);
if ( $this->getLastError() ) {
// Don't spam slow retries due to network problems (retry only on races)
$success = $this->add( $key, $value, $exptime, $flags );
} else {
// Try to update the key, failing if it gets changed in the meantime
- $success = $this->cas( $casToken, $key, $value, $exptime, $flags );
+ $success = $this->cas( $token, $key, $value, $exptime, $flags );
}
if ( $this->getLastError() ) {
// Don't spam slow retries due to network problems (retry only on races)
return $res;
}
- /**
- * Decrease stored value of $key by $value while preserving its TTL
- * @param string $key
- * @param int $value Value to subtract from $key (default: 1) [optional]
- * @return int|bool New value or false on failure
- */
- public function decr( $key, $value = 1 ) {
- return $this->incr( $key, -$value );
- }
-
- /**
- * Increase stored value of $key by $value while preserving its TTL
- *
- * This will create the key with value $init and TTL $ttl instead if not present
- *
- * @param string $key
- * @param int $ttl
- * @param int $value
- * @param int $init
- * @return int|bool New value or false on failure
- * @since 1.24
- */
- public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
+ public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
+ $init = is_int( $init ) ? $init : $value;
$this->clearLastError();
- $newValue = $this->incr( $key, $value );
+ $newValue = $this->incr( $key, $value, $flags );
if ( $newValue === false && !$this->getLastError() ) {
// No key set; initialize
- $newValue = $this->add( $key, (int)$init, $ttl ) ? $init : false;
+ $newValue = $this->add( $key, (int)$init, $exptime, $flags ) ? $init : false;
if ( $newValue === false && !$this->getLastError() ) {
// Raced out initializing; increment
- $newValue = $this->incr( $key, $value );
+ $newValue = $this->incr( $key, $value, $flags );
}
}
$this->lastError = $err;
}
- /**
- * Let a callback be run to avoid wasting time on special blocking calls
- *
- * The callbacks may or may not be called ever, in any particular order.
- * They are likely to be invoked when something WRITE_SYNC is used used.
- * They should follow a caching pattern as shown below, so that any code
- * using the work will get it's result no matter what happens.
- * @code
- * $result = null;
- * $workCallback = function () use ( &$result ) {
- * if ( !$result ) {
- * $result = ....
- * }
- * return $result;
- * }
- * @endcode
- *
- * @param callable $workCallback
- * @since 1.28
- */
final public function addBusyCallback( callable $workCallback ) {
$this->busyCallbacks[] = $workCallback;
}
return ( $value === (string)$integer );
}
- /**
- * Construct a cache key.
- *
- * @param string $keyspace
- * @param array $args
- * @return string Colon-delimited list of $keyspace followed by escaped components of $args
- * @since 1.27
- */
public function makeKeyInternal( $keyspace, $args ) {
$key = $keyspace;
foreach ( $args as $arg ) {
return $this->attrMap[$flag] ?? self::QOS_UNKNOWN;
}
- /**
- * @return int|float The chunk size, in bytes, of segmented objects (INF for no limit)
- * @since 1.34
- */
public function getSegmentationSize() {
return $this->segmentationSize;
}
- /**
- * @return int|float Maximum total segmented object size in bytes (INF for no limit)
- * @since 1.34
- */
public function getSegmentedValueMaxSize() {
return $this->segmentedValueMaxSize;
}
return $this->checkResult( $key, $result );
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
$this->debug( "incr($key)" );
$result = $this->acquireSyncClient()->increment( $key, $value );
return $this->checkResult( $key, $result );
}
- public function decr( $key, $value = 1 ) {
+ public function decr( $key, $value = 1, $flags = 0 ) {
$this->debug( "decr($key)" );
$result = $this->acquireSyncClient()->decrement( $key, $value );
);
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
$n = $this->client->incr( $this->validateKeyEncoding( $key ), $value );
return ( $n !== false && $n !== null ) ? $n : false;
}
- public function decr( $key, $value = 1 ) {
+ public function decr( $key, $value = 1, $flags = 0 ) {
$n = $this->client->decr( $this->validateKeyEncoding( $key ), $value );
return ( $n !== false && $n !== null ) ? $n : false;
$missIndexes[] = $i;
}
- if ( $value !== false
- && $missIndexes
- && $this->fieldHasFlags( $flags, self::READ_VERIFIED )
+ if (
+ $value !== false &&
+ $this->fieldHasFlags( $flags, self::READ_VERIFIED ) &&
+ $missIndexes
) {
// Backfill the value to the higher (and often faster/smaller) cache tiers
$this->doWrite(
);
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
return $this->doWrite(
$this->cacheIndexes,
$this->asyncWrites,
);
}
- public function decr( $key, $value = 1 ) {
+ public function decr( $key, $value = 1, $flags = 0 ) {
return $this->doWrite(
$this->cacheIndexes,
$this->asyncWrites,
);
}
- public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
+ public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
return $this->doWrite(
$this->cacheIndexes,
$this->asyncWrites,
return $this->handleError( "Failed to delete $key", $rcode, $rerr, $rhdrs, $rbody );
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
// @TODO: make this atomic
$n = $this->get( $key, self::READ_LATEST );
if ( $this->isInteger( $n ) ) { // key exists?
- $n = max( $n + intval( $value ), 0 );
+ $n = max( $n + (int)$value, 0 );
// @TODO: respect $exptime
return $this->set( $key, $n ) ? $n : false;
}
return false;
}
+ public function decr( $key, $value = 1, $flags = 0 ) {
+ return $this->incr( $key, -$value, $flags );
+ }
+
/**
* Processes the response body.
*
return $result;
}
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
$conn = $this->getConnection( $key );
if ( !$conn ) {
return false;
return $result;
}
+ public function decr( $key, $value = 1, $flags = 0 ) {
+ $conn = $this->getConnection( $key );
+ if ( !$conn ) {
+ return false;
+ }
+
+ try {
+ if ( !$conn->exists( $key ) ) {
+ return false;
+ }
+ // @FIXME: on races, the key may have a 0 TTL
+ $result = $conn->decrBy( $key, $value );
+ } catch ( RedisException $e ) {
+ $result = false;
+ $this->handleException( $conn, $e );
+ }
+
+ $this->logRequest( 'decr', $key, $conn->getServer(), $result );
+
+ return $result;
+ }
+
protected function doChangeTTL( $key, $exptime, $flags ) {
$conn = $this->getConnection( $key );
if ( !$conn ) {
return $this->writeStore->changeTTLMulti( $keys, $exptime, $flags );
}
- public function incr( $key, $value = 1 ) {
- return $this->writeStore->incr( $key, $value );
+ public function incr( $key, $value = 1, $flags = 0 ) {
+ return $this->writeStore->incr( $key, $value, $flags );
}
- public function decr( $key, $value = 1 ) {
- return $this->writeStore->decr( $key, $value );
+ public function decr( $key, $value = 1, $flags = 0 ) {
+ return $this->writeStore->decr( $key, $value, $flags );
}
- public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
- return $this->writeStore->incrWithInit( $key, $ttl, $value, $init );
+ public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
+ return $this->writeStore->incrWithInit( $key, $exptime, $value, $init, $flags );
}
public function getLastError() {
return true;
}
- /**
- * Construct a cache key.
- *
- * @since 1.27
- * @param string $keyspace
- * @param array $args
- * @return string
- */
public function makeKeyInternal( $keyspace, $args ) {
// WinCache keys have a maximum length of 150 characters. From that,
// subtract the number of characters we need for the keyspace and for
return $keyspace . ':' . implode( ':', $args );
}
- /**
- * Increase stored value of $key by $value while preserving its original TTL
- * @param string $key Key to increase
- * @param int $value Value to add to $key (Default 1)
- * @return int|bool New value or false on failure
- */
- public function incr( $key, $value = 1 ) {
+ public function incr( $key, $value = 1, $flags = 0 ) {
if ( !wincache_lock( $key ) ) { // optimize with FIFO lock
return false;
}
return $n;
}
+
+ public function decr( $key, $value = 1, $flags = 0 ) {
+ return $this->incr( $key, -$value, $flags );
+ }
}
/** @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 */
$this->cliMode = $params['cliMode'];
$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;
- }
- }
$this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'] ?? 10000;
$this->srvCache = $params['srvCache'] ?? new HashBagOStuff();
*/
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
];
$this->password = $password;
$connectVars = [
- // pg_connect() user $user as the default database. Since a database is required,
- // then pick a "don't care" database that is more likely to exist than that one.
+ // A database must be specified in order to connect to Postgres. If $dbName is not
+ // specified, then use the standard "postgres" database that should exist by default.
'dbname' => strlen( $dbName ) ? $dbName : 'postgres',
'user' => $user,
'password' => $password
return $row ? ( strtolower( $row->default_transaction_read_only ) === 'on' ) : false;
}
- public static function getAttributes() {
+ protected static function getAttributes() {
return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ];
}
* - dbDirectory : directory containing the DB and the lock file directory
* - dbFilePath : use this to force the path of the DB file
* - trxMode : one of (deferred, immediate, exclusive)
- * @param array $p
+ * @param array $params
*/
- public function __construct( array $p ) {
- if ( isset( $p['dbFilePath'] ) ) {
- $this->dbPath = $p['dbFilePath'];
- if ( !strlen( $p['dbname'] ) ) {
- $p['dbname'] = self::generateDatabaseName( $this->dbPath );
+ public function __construct( array $params ) {
+ if ( isset( $params['dbFilePath'] ) ) {
+ $this->dbPath = $params['dbFilePath'];
+ if ( !strlen( $params['dbname'] ) ) {
+ $params['dbname'] = self::generateDatabaseName( $this->dbPath );
}
- } elseif ( isset( $p['dbDirectory'] ) ) {
- $this->dbDir = $p['dbDirectory'];
+ } elseif ( isset( $params['dbDirectory'] ) ) {
+ $this->dbDir = $params['dbDirectory'];
}
- parent::__construct( $p );
+ parent::__construct( $params );
- $this->trxMode = strtoupper( $p['trxMode'] ?? '' );
+ $this->trxMode = strtoupper( $params['trxMode'] ?? '' );
$lockDirectory = $this->getLockFileDirectory();
if ( $lockDirectory !== null ) {
}
protected static function getAttributes() {
- return [ self::ATTR_DB_LEVEL_LOCKING => true ];
+ return [
+ self::ATTR_DB_IS_FILE => true,
+ self::ATTR_DB_LEVEL_LOCKING => true
+ ];
}
/**
*
* In systems like mysql/mariadb, different databases can easily be referenced on a single
* connection merely by name, even in a single query via JOIN. On the other hand, Postgres
- * treats databases as fully separate, only allowing mechanisms like postgres_fdw to
- * effectively "mount" foreign DBs. This is true even among DBs on the same server.
+ * treats databases as logically separate, with different database users, requiring special
+ * mechanisms like postgres_fdw to "mount" foreign DBs. This is true even among DBs on the
+ * same server. Changing the selected database via selectDomain() requires a new connection.
*
* @return bool
* @since 1.29
public function setSessionOptions( array $options );
/**
- * Set variables to be used in sourceFile/sourceStream, in preference to the
- * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
- * all. If it's set to false, $GLOBALS will be used.
+ * Set schema variables to be used when streaming commands from SQL files or stdin
*
- * @param bool|array $vars Mapping variable name to value.
+ * Variables appear as SQL comments and are substituted by their corresponding values
+ *
+ * @param array|null $vars Map of (variable => value) or null to use the defaults
*/
public function setSchemaVars( $vars );
$this->defaultGroup = $conf['defaultGroup'] ?? null;
$this->secret = $conf['secret'] ?? '';
- $this->id = mt_rand();
- $this->ticket = mt_rand();
+ static $nextId, $nextTicket;
+ $this->id = $nextId = ( is_int( $nextId ) ? $nextId++ : mt_rand() );
+ $this->ticket = $nextTicket = ( is_int( $nextTicket ) ? $nextTicket++ : mt_rand() );
}
public function destroy() {
foreach ( $loads as $serverName => $load ) {
$serverInfo = $template;
if ( $master ) {
- $serverInfo['master'] = true;
if ( $this->masterTemplateOverrides ) {
$serverInfo = $this->masterTemplateOverrides + $serverInfo;
}
$master = false;
} else {
- $serverInfo['replica'] = true;
}
if ( isset( $this->templateOverridesByServer[$serverName] ) ) {
$serverInfo = $this->templateOverridesByServer[$serverName] + $serverInfo;
parent::__construct( $conf );
$this->servers = $conf['servers'] ?? [];
- foreach ( $this->servers as $i => $server ) {
- if ( $i == 0 ) {
- $this->servers[$i]['master'] = true;
- } else {
- $this->servers[$i]['replica'] = true;
- }
- }
-
$this->externalClusters = $conf['externalClusters'] ?? [];
$this->loadMonitorClass = $conf['loadMonitorClass'] ?? 'LoadMonitor';
}
/** @var bool Whether any connection has been attempted yet */
private $connectionAttempted = false;
+ /** var int An identifier for this class instance */
+ private $id;
/** @var int|null Integer ID of the managing LBFactory instance or null if none */
private $ownerId;
/** @var string|bool Explicit DBO_TRX transaction round active or false if none */
if ( ++$listKey !== $i ) {
throw new UnexpectedValueException( 'List expected for "servers" parameter' );
}
- if ( $i == 0 ) {
- $server['master'] = true;
- } else {
- $server['replica'] = true;
- }
$this->servers[$i] = $server;
foreach ( ( $server['groupLoads'] ?? [] ) as $group => $ratio ) {
$this->groupLoads[$group][$i] = $ratio;
$group = $params['defaultGroup'] ?? self::GROUP_GENERIC;
$this->defaultGroup = isset( $this->groupLoads[$group] ) ? $group : self::GROUP_GENERIC;
+ static $nextId;
+ $this->id = $nextId = ( is_int( $nextId ) ? $nextId++ : mt_rand() );
$this->ownerId = $params['ownerId'] ?? null;
}
$serverIndex = $conn->getLBInfo( 'serverIndex' );
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
if ( $serverIndex === null || $refCount === null ) {
- /**
- * This can happen in code like:
- * foreach ( $dbs as $db ) {
- * $conn = $lb->getConnection( $lb::DB_REPLICA, [], $db );
- * ...
- * $lb->reuseConnection( $conn );
- * }
- * When a connection to the local DB is opened in this way, reuseConnection()
- * should be ignored
- */
- return;
+ return; // non-foreign connection; no domain-use tracking to update
} elseif ( $conn instanceof DBConnRef ) {
// DBConnRef already handles calling reuseConnection() and only passes the live
// Database instance to this method. Any caller passing in a DBConnRef is broken.
*
* @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
*
- * @param int $i Server index
+ * @param int $i Specific server index
* @param int $flags Class CONN_* constant bitfield
* @return Database
* @throws InvalidArgumentException When the server index is invalid
* @throws UnexpectedValueException When the DB domain of the connection is corrupted
*/
private function getLocalConnection( $i, $flags = 0 ) {
+ $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
// Connection handles required to be in auto-commit mode use a separate connection
// pool since the main pool is effected by implicit and explicit transaction rounds
- $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
-
$connKey = $autoCommit ? self::KEY_LOCAL_NOROUND : self::KEY_LOCAL;
+
if ( isset( $this->conns[$connKey][$i][0] ) ) {
$conn = $this->conns[$connKey][$i][0];
} else {
- // Open a new connection
- $server = $this->getServerInfoStrict( $i );
- $server['serverIndex'] = $i;
- $server['autoCommitOnly'] = $autoCommit;
- $conn = $this->reallyOpenConnection( $server, $this->localDomain );
- $host = $this->getServerName( $i );
+ $conn = $this->reallyOpenConnection(
+ $i,
+ $this->localDomain,
+ [ 'autoCommitOnly' => $autoCommit ]
+ );
if ( $conn->isOpen() ) {
- $this->connLogger->debug(
- __METHOD__ . ": connected to database $i at '$host'." );
+ $this->connLogger->debug( __METHOD__ . ": opened new connection for $i" );
$this->conns[$connKey][$i][0] = $conn;
} else {
- $this->connLogger->warning(
- __METHOD__ . ": failed to connect to database $i at '$host'." );
+ $this->connLogger->warning( __METHOD__ . ": connection error for $i" );
$this->errorConnection = $conn;
$conn = false;
}
}
- // Final sanity check to make sure the right domain is selected
+ // Sanity check to make sure that the right domain is selected
if (
$conn instanceof IDatabase &&
!$this->localDomain->isCompatible( $conn->getDomainID() )
) {
throw new UnexpectedValueException(
"Got connection to '{$conn->getDomainID()}', " .
- "but expected local domain ('{$this->localDomain}')" );
+ "but expected local domain ('{$this->localDomain}')"
+ );
}
return $conn;
*
* @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
*
- * @param int $i Server index
+ * @param int $i Specific server index
* @param string $domain Domain ID to open
* @param int $flags Class CONN_* constant bitfield
* @return Database|bool Returns false on connection error
*/
private function getForeignConnection( $i, $domain, $flags = 0 ) {
$domainInstance = DatabaseDomain::newFromId( $domain );
+ $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
// Connection handles required to be in auto-commit mode use a separate connection
// pool since the main pool is effected by implicit and explicit transaction rounds
- $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
-
if ( $autoCommit ) {
$connFreeKey = self::KEY_FOREIGN_FREE_NOROUND;
$connInUseKey = self::KEY_FOREIGN_INUSE_NOROUND;
}
if ( !$conn ) {
- // Open a new connection
- $server = $this->getServerInfoStrict( $i );
- $server['serverIndex'] = $i;
- $server['foreignPoolRefCount'] = 0;
- $server['foreign'] = true;
- $server['autoCommitOnly'] = $autoCommit;
- $conn = $this->reallyOpenConnection( $server, $domainInstance );
- if ( !$conn->isOpen() ) {
- $this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" );
- $this->errorConnection = $conn;
- $conn = false;
- } else {
+ $conn = $this->reallyOpenConnection(
+ $i,
+ $domainInstance,
+ [
+ 'autoCommitOnly' => $autoCommit,
+ 'foreign' => true,
+ 'foreignPoolRefCount' => 0
+ ]
+ );
+ if ( $conn->isOpen() ) {
// Note that if $domain is an empty string, getDomainID() might not match it
$this->conns[$connInUseKey][$i][$conn->getDomainID()] = $conn;
$this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
+ } else {
+ $this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" );
+ $this->errorConnection = $conn;
+ $conn = false;
}
}
if ( $conn instanceof IDatabase ) {
- // Final sanity check to make sure the right domain is selected
+ // Sanity check to make sure that the right domain is selected
if ( !$domainInstance->isCompatible( $conn->getDomainID() ) ) {
throw new UnexpectedValueException(
"Got connection to '{$conn->getDomainID()}', but expected '$domain'" );
*
* Returns a Database object whether or not the connection was successful.
*
- * @param array $server
+ * @param int $i Specific server index
* @param DatabaseDomain $domain Domain the connection is for, possibly unspecified
+ * @param array $lbInfo Additional information for setLBInfo()
* @return Database
* @throws DBAccessError
* @throws InvalidArgumentException
*/
- protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
+ protected function reallyOpenConnection( $i, DatabaseDomain $domain, array $lbInfo ) {
if ( $this->disabled ) {
throw new DBAccessError();
}
- if ( $domain->getDatabase() === null ) {
- // The database domain does not specify a DB name and some database systems require a
- // valid DB specified on connection. The $server configuration array contains a default
- // DB name to use for connections in such cases.
- if ( $server['type'] === 'mysql' ) {
- // For MySQL, DATABASE and SCHEMA are synonyms, connections need not specify a DB,
- // and the DB name in $server might not exist due to legacy reasons (the default
- // domain used to ignore the local LB domain, even when mismatched).
- $server['dbname'] = null;
- }
- } else {
- $server['dbname'] = $domain->getDatabase();
- }
-
- if ( $domain->getSchema() !== null ) {
- $server['schema'] = $domain->getSchema();
- }
-
- // It is always possible to connect with any prefix, even the empty string
- $server['tablePrefix'] = $domain->getTablePrefix();
-
- // Let the handle know what the cluster master is (e.g. "db1052")
- $masterName = $this->getServerName( $this->getWriterIndex() );
- $server['clusterMasterHost'] = $masterName;
-
- $server['srvCache'] = $this->srvCache;
- // Set loggers and profilers
- $server['connLogger'] = $this->connLogger;
- $server['queryLogger'] = $this->queryLogger;
- $server['errorLogger'] = $this->errorLogger;
- $server['deprecationLogger'] = $this->deprecationLogger;
- $server['profiler'] = $this->profiler;
- $server['trxProfiler'] = $this->trxProfiler;
- // Use the same agent and PHP mode for all DB handles
- $server['cliMode'] = $this->cliMode;
- $server['agent'] = $this->agent;
- // Use DBO_DEFAULT flags by default for LoadBalancer managed databases. Assume that the
- // application calls LoadBalancer::commitMasterChanges() before the PHP script completes.
- $server['flags'] = $server['flags'] ?? IDatabase::DBO_DEFAULT;
-
- // Create a live connection object
+ $server = $this->getServerInfoStrict( $i );
+ $server = array_merge( $server, [
+ // Use the database specified in $domain (null means "none or entrypoint DB");
+ // fallback to the $server default if the RDBMs is an embedded library using a file
+ // on disk since there would be nothing to access to without a DB/file name.
+ 'dbname' => $this->getServerAttributes( $i )[Database::ATTR_DB_IS_FILE]
+ ? ( $domain->getDatabase() ?? $server['dbname'] ?? null )
+ : $domain->getDatabase(),
+ // Override the $server default schema with that of $domain if specified
+ 'schema' => $domain->getSchema() ?? $server['schema'] ?? null,
+ // Use the table prefix specified in $domain
+ 'tablePrefix' => $domain->getTablePrefix(),
+ // Participate in transaction rounds if $server does not specify otherwise
+ 'flags' => $server['flags'] ?? IDatabase::DBO_DEFAULT,
+ // Inject the PHP execution mode and the agent string
+ 'cliMode' => $this->cliMode,
+ 'agent' => $this->agent,
+ // Inject object and callback dependencies
+ 'srvCache' => $this->srvCache,
+ 'connLogger' => $this->connLogger,
+ 'queryLogger' => $this->queryLogger,
+ 'errorLogger' => $this->errorLogger,
+ 'deprecationLogger' => $this->deprecationLogger,
+ 'profiler' => $this->profiler,
+ 'trxProfiler' => $this->trxProfiler
+ ] );
+
+ $lbInfo = array_merge( $lbInfo, [
+ 'ownerId' => $this->id,
+ 'serverIndex' => $i,
+ 'master' => ( $i === $this->getWriterIndex() ),
+ 'replica' => ( $i !== $this->getWriterIndex() ),
+ // Name of the master server of the relevant DB cluster (e.g. "db1052")
+ 'clusterMasterHost' => $this->getServerName( $this->getWriterIndex() )
+ ] );
+
+ $conn = Database::factory( $server['type'], $server, Database::NEW_UNCONNECTED );
try {
- $db = Database::factory( $server['type'], $server );
- // Log when many connection are made on requests
+ $conn->initConnection();
++$this->connectionCounter;
- $currentConnCount = $this->getCurrentConnectionCount();
- if ( $currentConnCount >= self::CONN_HELD_WARN_THRESHOLD ) {
- $this->perfLogger->warning(
- __METHOD__ . ": {connections}+ connections made (master={masterdb})",
- [ 'connections' => $currentConnCount, 'masterdb' => $masterName ]
- );
- }
} catch ( DBConnectionError $e ) {
- // FIXME: This is probably the ugliest thing I have ever done to
- // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
- $db = $e->db;
+ // ignore; let the DB handle the logging
}
- $db->setLBInfo( $server );
- $db->setLazyMasterHandle(
- $this->getLazyConnectionRef( self::DB_MASTER, [], $db->getDomainID() )
- );
- $db->setTableAliases( $this->tableAliases );
- $db->setIndexAliases( $this->indexAliases );
-
- if ( $server['serverIndex'] === $this->getWriterIndex() ) {
+ $conn->setLBInfo( $lbInfo );
+ if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
+ if ( $this->cliMode ) {
+ $conn->clearFlag( $conn::DBO_TRX );
+ } else {
+ $conn->setFlag( $conn::DBO_TRX );
+ }
+ }
+ if ( $i === $this->getWriterIndex() ) {
if ( $this->trxRoundId !== false ) {
- $this->applyTransactionRoundFlags( $db );
+ $this->applyTransactionRoundFlags( $conn );
}
foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
- $db->setTransactionListener( $name, $callback );
+ $conn->setTransactionListener( $name, $callback );
}
}
+ $conn->setTableAliases( $this->tableAliases );
+ $conn->setIndexAliases( $this->indexAliases );
+
+ $conn->setLazyMasterHandle(
+ $this->getLazyConnectionRef( self::DB_MASTER, [], $conn->getDomainID() )
+ );
$this->lazyLoadReplicationPositions(); // session consistency
- return $db;
+ // Log when many connection are made on requests
+ $count = $this->getCurrentConnectionCount();
+ if ( $count >= self::CONN_HELD_WARN_THRESHOLD ) {
+ $this->perfLogger->warning(
+ __METHOD__ . ": {connections}+ connections made (master={masterdb})",
+ [
+ 'connections' => $count,
+ 'dbserver' => $conn->getServer(),
+ 'masterdb' => $conn->getLBInfo( 'clusterMasterHost' )
+ ]
+ );
+ }
+
+ return $conn;
}
/**
public function waitForMasterPos( IDatabase $conn, $pos = false, $timeout = null ) {
$timeout = max( 1, $timeout ?: $this->waitTimeout );
- if ( $this->getServerCount() <= 1 || !$conn->getLBInfo( 'replica' ) ) {
- return true; // server is not a replica DB
+ if ( $conn->getLBInfo( 'serverIndex' ) === $this->getWriterIndex() ) {
+ return true; // not a replica DB server
}
if ( !$pos ) {
) );
// Update the prefix for all local connections...
- $this->forEachOpenConnection( function ( IDatabase $db ) use ( $prefix ) {
- if ( !$db->getLBInfo( 'foreign' ) ) {
- $db->tablePrefix( $prefix );
+ $this->forEachOpenConnection( function ( IDatabase $conn ) use ( $prefix ) {
+ if ( !$conn->getLBInfo( 'foreign' ) ) {
+ $conn->tablePrefix( $prefix );
}
} );
}
) );
}
- protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
+ protected function reallyOpenConnection( $i, DatabaseDomain $domain, array $lbInfo = [] ) {
return $this->db;
}
*/
use MediaWiki\MediaWikiServices;
+use Wikimedia\AtEase\AtEase;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\DBError;
* @return IMaintainableDatabase
* @throws MWException
*/
- private function getDB( $shardIndex ) {
+ private function getConnection( $shardIndex ) {
if ( $shardIndex >= $this->numServerShards ) {
throw new MWException( __METHOD__ . ": Invalid server index \"$shardIndex\"" );
}
$dataRows = [];
foreach ( $keysByTable as $shardIndex => $serverKeys ) {
try {
- $db = $this->getDB( $shardIndex );
+ $db = $this->getConnection( $shardIndex );
foreach ( $serverKeys as $tableName => $tableKeys ) {
$res = $db->select( $tableName,
[ 'keyname', 'value', 'exptime' ],
$this->debug( "get: retrieved data; expiry time is " . $row->exptime );
$db = null; // in case of connection failure
try {
- $db = $this->getDB( $row->shardIndex );
+ $db = $this->getConnection( $row->shardIndex );
if ( $this->isExpired( $db, $row->exptime ) ) { // MISS
$this->debug( "get: key has expired" );
} else { // HIT
foreach ( $keysByTable as $shardIndex => $serverKeys ) {
$db = null; // in case of connection failure
try {
- $db = $this->getDB( $shardIndex );
+ $db = $this->getConnection( $shardIndex );
$this->occasionallyGarbageCollect( $db ); // expire old entries if any
$dbExpiry = $exptime ? $db->timestamp( $exptime ) : $this->getMaxDateTime( $db );
} catch ( DBError $e ) {
$silenceScope = $this->silenceTransactionProfiler();
$db = null; // in case of connection failure
try {
- $db = $this->getDB( $shardIndex );
+ $db = $this->getConnection( $shardIndex );
// (T26425) use a replace if the db supports it instead of
// delete/insert to avoid clashes with conflicting keynames
$db->update(
return $this->modifyMulti( [ $key => null ], 0, $flags, self::$OP_DELETE );
}
- public function incr( $key, $step = 1 ) {
+ public function incr( $key, $step = 1, $flags = 0 ) {
list( $shardIndex, $tableName ) = $this->getTableByKey( $key );
$newCount = false;
$silenceScope = $this->silenceTransactionProfiler();
$db = null; // in case of connection failure
try {
- $db = $this->getDB( $shardIndex );
+ $db = $this->getConnection( $shardIndex );
$encTimestamp = $db->addQuotes( $db->timestamp() );
$db->update(
$tableName,
return $newCount;
}
+ public function decr( $key, $value = 1, $flags = 0 ) {
+ return $this->incr( $key, -$value, $flags );
+ }
+
public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
return $this->modifyMulti(
array_fill_keys( $keys, null ),
foreach ( $shardIndexes as $numServersDone => $shardIndex ) {
$db = null; // in case of connection failure
try {
- $db = $this->getDB( $shardIndex );
+ $db = $this->getConnection( $shardIndex );
$this->deleteServerObjectsExpiringBefore(
$db,
$timestamp,
for ( $shardIndex = 0; $shardIndex < $this->numServerShards; $shardIndex++ ) {
$db = null; // in case of connection failure
try {
- $db = $this->getDB( $shardIndex );
+ $db = $this->getConnection( $shardIndex );
for ( $i = 0; $i < $this->numTableShards; $i++ ) {
$db->delete( $this->getTableNameByShard( $i ), '*', __METHOD__ );
}
$db = null; // in case of connection failure
try {
- $db = $this->getDB( $shardIndex );
+ $db = $this->getConnection( $shardIndex );
$ok = $db->lock( $key, __METHOD__, $timeout );
if ( $ok ) {
$this->locks[$key] = [ 'class' => $rclass, 'depth' => 1 ];
$db = null; // in case of connection failure
try {
- $db = $this->getDB( $shardIndex );
+ $db = $this->getConnection( $shardIndex );
$ok = $db->unlock( $key, __METHOD__ );
if ( !$ok ) {
$this->logger->warning(
}
if ( function_exists( 'gzinflate' ) ) {
- Wikimedia\suppressWarnings();
+ AtEase::suppressWarnings();
$decomp = gzinflate( $serial );
- Wikimedia\restoreWarnings();
+ AtEase::restoreWarnings();
if ( $decomp !== false ) {
$serial = $decomp;
*/
public function createTables() {
for ( $shardIndex = 0; $shardIndex < $this->numServerShards; $shardIndex++ ) {
- $db = $this->getDB( $shardIndex );
+ $db = $this->getConnection( $shardIndex );
if ( $db->getType() !== 'mysql' ) {
throw new MWException( __METHOD__ . ' is not supported on this DB server' );
}
use MediaWiki\Linker\LinkTarget;
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\IMaintainableDatabase;
}
public function execute() {
- $this->db = $this->getDB( DB_MASTER );
-
$options = [
'fix' => $this->hasOption( 'fix' ),
'merge' => $this->hasOption( 'merge' ),
foreach ( $targets as $row ) {
// Find the new title and determine the action to take
- $newTitle = $this->getDestinationTitle( $ns, $name,
- $row->page_namespace, $row->page_title, $options );
+ $newTitle = $this->getDestinationTitle(
+ $ns, $name, $row->page_namespace, $row->page_title );
$logStatus = false;
if ( !$newTitle ) {
$logStatus = 'invalid title';
private function checkLinkTable( $table, $fieldPrefix, $ns, $name, $options,
$extraConds = []
) {
+ $dbw = $this->getDB( DB_MASTER );
+
$batchConds = [];
$fromField = "{$fieldPrefix}_from";
$namespaceField = "{$fieldPrefix}_namespace";
$titleField = "{$fieldPrefix}_title";
$batchSize = 500;
while ( true ) {
- $res = $this->db->select(
+ $res = $dbw->select(
$table,
[ $fromField, $namespaceField, $titleField ],
array_merge( $batchConds, $extraConds, [
$namespaceField => 0,
- $titleField . $this->db->buildLike( "$name:", $this->db->anyString() )
+ $titleField . $dbw->buildLike( "$name:", $dbw->anyString() )
] ),
__METHOD__,
[
foreach ( $res as $row ) {
$logTitle = "from={$row->$fromField} ns={$row->$namespaceField} " .
"dbk={$row->$titleField}";
- $destTitle = $this->getDestinationTitle( $ns, $name,
- $row->$namespaceField, $row->$titleField, $options );
+ $destTitle = $this->getDestinationTitle(
+ $ns, $name, $row->$namespaceField, $row->$titleField );
$this->totalLinks++;
if ( !$destTitle ) {
$this->output( "$table $logTitle *** INVALID\n" );
continue;
}
- $this->db->update( $table,
+ $dbw->update( $table,
// SET
[
$namespaceField => $destTitle->getNamespace(),
$this->output( "$table $logTitle -> " .
$destTitle->getPrefixedDBkey() . "\n" );
}
- $encLastTitle = $this->db->addQuotes( $row->$titleField );
- $encLastFrom = $this->db->addQuotes( $row->$fromField );
+ $encLastTitle = $dbw->addQuotes( $row->$titleField );
+ $encLastFrom = $dbw->addQuotes( $row->$fromField );
$batchConds = [
"$titleField > $encLastTitle " .
* @return IResultWrapper
*/
private function getTargetList( $ns, $name, $options ) {
+ $dbw = $this->getDB( DB_MASTER );
+
if (
$options['move-talk'] &&
MediaWikiServices::getInstance()->getNamespaceInfo()->isSubject( $ns )
$checkNamespaces = NS_MAIN;
}
- return $this->db->select( 'page',
+ return $dbw->select( 'page',
[
'page_id',
'page_title',
],
[
'page_namespace' => $checkNamespaces,
- 'page_title' . $this->db->buildLike( "$name:", $this->db->anyString() ),
+ 'page_title' . $dbw->buildLike( "$name:", $dbw->anyString() ),
],
__METHOD__
);
* @param string $name The conflicting prefix
* @param int $sourceNs The source namespace
* @param int $sourceDbk The source DB key (i.e. page_title)
- * @param array $options Associative array of validated command-line options
* @return Title|false
*/
- private function getDestinationTitle( $ns, $name, $sourceNs, $sourceDbk, $options ) {
+ private function getDestinationTitle( $ns, $name, $sourceNs, $sourceDbk ) {
$dbk = substr( $sourceDbk, strlen( "$name:" ) );
if ( $ns == 0 ) {
// An interwiki; try an alternate encoding with '-' for ':'
* @return bool
*/
private function movePage( $id, LinkTarget $newLinkTarget ) {
- $this->db->update( 'page',
+ $dbw = $this->getDB( DB_MASTER );
+
+ $dbw->update( 'page',
[
"page_namespace" => $newLinkTarget->getNamespace(),
"page_title" => $newLinkTarget->getDBkey(),
[ 'imagelinks', 'il' ] ];
foreach ( $fromNamespaceTables as $tableInfo ) {
list( $table, $fieldPrefix ) = $tableInfo;
- $this->db->update( $table,
+ $dbw->update( $table,
// SET
[ "{$fieldPrefix}_from_namespace" => $newLinkTarget->getNamespace() ],
// WHERE
* @return bool
*/
private function canMerge( $id, LinkTarget $linkTarget, &$logStatus ) {
- $latestDest = Revision::newFromTitle(
- $linkTarget, 0, RevisionRecord::READ_LATEST
- );
- $latestSource = Revision::newFromPageId(
- $id, 0, RevisionRecord::READ_LATEST
- );
+ $latestDest = Revision::newFromTitle( $linkTarget, 0, Revision::READ_LATEST );
+ $latestSource = Revision::newFromPageId( $id, 0, Revision::READ_LATEST );
if ( $latestSource->getTimestamp() > $latestDest->getTimestamp() ) {
$logStatus = 'cannot merge since source is later';
return false;
* @return bool
*/
private function mergePage( $row, Title $newTitle ) {
+ $dbw = $this->getDB( DB_MASTER );
+
$id = $row->page_id;
// Construct the WikiPage object we will need later, while the
$wikiPage->loadPageData( 'fromdbmaster' );
$destId = $newTitle->getArticleID();
- $this->beginTransaction( $this->db, __METHOD__ );
- $this->db->update( 'revision',
+ $this->beginTransaction( $dbw, __METHOD__ );
+ $dbw->update( 'revision',
// SET
[ 'rev_page' => $destId ],
// WHERE
[ 'rev_page' => $id ],
__METHOD__ );
- $this->db->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
+ $dbw->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
- $this->commitTransaction( $this->db, __METHOD__ );
+ $this->commitTransaction( $dbw, __METHOD__ );
/* Call LinksDeletionUpdate to delete outgoing links from the old title,
* and update category counts.
require_once __DIR__ . '/Maintenance.php';
-use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\DatabaseSqlite;
/**
class RebuildTextIndex extends Maintenance {
const RTI_CHUNK_SIZE = 500;
- /**
- * @var IMaintainableDatabase
- */
- private $db;
-
public function __construct() {
parent::__construct();
$this->addDescription( 'Rebuild search index table from scratch' );
public function execute() {
// Shouldn't be needed for Postgres
- $this->db = $this->getDB( DB_MASTER );
- if ( $this->db->getType() == 'postgres' ) {
+ $dbw = $this->getDB( DB_MASTER );
+ if ( $dbw->getType() == 'postgres' ) {
$this->fatalError( "This script is not needed when using Postgres.\n" );
}
- if ( $this->db->getType() == 'sqlite' ) {
+ if ( $dbw->getType() == 'sqlite' ) {
if ( !DatabaseSqlite::getFulltextSearchModule() ) {
$this->fatalError( "Your version of SQLite module for PHP doesn't "
. "support full-text search (FTS3).\n" );
}
- if ( !$this->db->checkForEnabledSearch() ) {
- $this->fatalError( "Your database schema is not configured for "
- . "full-text search support. Run update.php.\n" );
- }
}
- if ( $this->db->getType() == 'mysql' ) {
+ if ( $dbw->getType() == 'mysql' ) {
$this->dropMysqlTextIndex();
$this->clearSearchIndex();
$this->populateSearchIndex();
* Populates the search index with content from all pages
*/
protected function populateSearchIndex() {
- $res = $this->db->select( 'page', 'MAX(page_id) AS count' );
- $s = $this->db->fetchObject( $res );
+ $dbw = $this->getDB( DB_MASTER );
+ $res = $dbw->select( 'page', 'MAX(page_id) AS count' );
+ $s = $dbw->fetchObject( $res );
$count = $s->count;
$this->output( "Rebuilding index fields for {$count} pages...\n" );
$n = 0;
}
$end = $n + self::RTI_CHUNK_SIZE - 1;
- $res = $this->db->select(
+ $res = $dbw->select(
$revQuery['tables'],
$revQuery['fields'],
[ "page_id BETWEEN $n AND $end", 'page_latest = rev_id' ],
* (MySQL only) Drops fulltext index before populating the table.
*/
private function dropMysqlTextIndex() {
- $searchindex = $this->db->tableName( 'searchindex' );
- if ( $this->db->indexExists( 'searchindex', 'si_title', __METHOD__ ) ) {
+ $dbw = $this->getDB( DB_MASTER );
+ $searchindex = $dbw->tableName( 'searchindex' );
+ if ( $dbw->indexExists( 'searchindex', 'si_title', __METHOD__ ) ) {
$this->output( "Dropping index...\n" );
$sql = "ALTER TABLE $searchindex DROP INDEX si_title, DROP INDEX si_text";
- $this->db->query( $sql, __METHOD__ );
+ $dbw->query( $sql, __METHOD__ );
}
}
* (MySQL only) Adds back fulltext index after populating the table.
*/
private function createMysqlTextIndex() {
- $searchindex = $this->db->tableName( 'searchindex' );
+ $dbw = $this->getDB( DB_MASTER );
+ $searchindex = $dbw->tableName( 'searchindex' );
$this->output( "\nRebuild the index...\n" );
foreach ( [ 'si_title', 'si_text' ] as $field ) {
$sql = "ALTER TABLE $searchindex ADD FULLTEXT $field ($field)";
- $this->db->query( $sql, __METHOD__ );
+ $dbw->query( $sql, __METHOD__ );
}
}
* Deletes everything from search index.
*/
private function clearSearchIndex() {
+ $dbw = $this->getDB( DB_MASTER );
$this->output( 'Clearing searchindex table...' );
- $this->db->delete( 'searchindex', '*', __METHOD__ );
+ $dbw->delete( 'searchindex', '*', __METHOD__ );
$this->output( "Done\n" );
}
}
$replicaDB = $this->getOption( 'replicadb', $this->getOption( 'slave', '' ) );
if ( $replicaDB === 'any' ) {
$index = DB_REPLICA;
- } elseif ( $replicaDB != '' ) {
+ } elseif ( $replicaDB !== '' ) {
$index = null;
$serverCount = $lb->getServerCount();
for ( $i = 0; $i < $serverCount; ++$i ) {
break;
}
}
- if ( $index === null ) {
+ if ( $index === null || $index === $lb->getWriterIndex() ) {
$this->fatalError( "No replica DB server configured with the name '$replicaDB'." );
}
} else {
* @ingroup Maintenance
*/
+use MediaWiki\MediaWikiServices;
+
+use Wikimedia\Rdbms\DatabaseSqlite;
+
require_once __DIR__ . '/Maintenance.php';
/**
return;
}
- $this->db = $this->getDB( DB_MASTER );
-
- if ( $this->db->getType() != 'sqlite' ) {
+ $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+ $dbw = $lb->getConnection( DB_MASTER );
+ if ( !( $dbw instanceof DatabaseSqlite ) ) {
$this->error( "This maintenance script requires a SQLite database.\n" );
return;
}
if ( $this->hasOption( 'vacuum' ) ) {
- $this->vacuum();
+ $this->vacuum( $dbw );
}
if ( $this->hasOption( 'integrity' ) ) {
- $this->integrityCheck();
+ $this->integrityCheck( $dbw );
}
if ( $this->hasOption( 'backup-to' ) ) {
- $this->backup( $this->getOption( 'backup-to' ) );
+ $this->backup( $dbw, $this->getOption( 'backup-to' ) );
}
}
- private function vacuum() {
- $prevSize = filesize( $this->db->getDbFilePath() );
+ private function vacuum( DatabaseSqlite $dbw ) {
+ $prevSize = filesize( $dbw->getDbFilePath() );
if ( $prevSize == 0 ) {
$this->fatalError( "Can't vacuum an empty database.\n" );
}
$this->output( 'VACUUM: ' );
- if ( $this->db->query( 'VACUUM' ) ) {
+ if ( $dbw->query( 'VACUUM' ) ) {
clearstatcache();
- $newSize = filesize( $this->db->getDbFilePath() );
+ $newSize = filesize( $dbw->getDbFilePath() );
$this->output( sprintf( "Database size was %d, now %d (%.1f%% reduction).\n",
$prevSize, $newSize, ( $prevSize - $newSize ) * 100.0 / $prevSize ) );
} else {
}
}
- private function integrityCheck() {
+ private function integrityCheck( DatabaseSqlite $dbw ) {
$this->output( "Performing database integrity checks:\n" );
- $res = $this->db->query( 'PRAGMA integrity_check' );
+ $res = $dbw->query( 'PRAGMA integrity_check' );
if ( !$res || $res->numRows() == 0 ) {
$this->error( "Error: integrity check query returned nothing.\n" );
}
}
- private function backup( $fileName ) {
+ private function backup( DatabaseSqlite $dbw, $fileName ) {
$this->output( "Backing up database:\n Locking..." );
- $this->db->query( 'BEGIN IMMEDIATE TRANSACTION', __METHOD__ );
- $ourFile = $this->db->getDbFilePath();
+ $dbw->query( 'BEGIN IMMEDIATE TRANSACTION', __METHOD__ );
+ $ourFile = $dbw->getDbFilePath();
$this->output( " Copying database file $ourFile to $fileName... " );
Wikimedia\suppressWarnings();
if ( !copy( $ourFile, $fileName ) ) {
}
Wikimedia\restoreWarnings();
$this->output( " Releasing lock...\n" );
- $this->db->query( 'COMMIT TRANSACTION', __METHOD__ );
+ $dbw->query( 'COMMIT TRANSACTION', __METHOD__ );
}
private function checkSyntax() {
use MediaWiki\User\UserIdentityValue;
use MediaWikiTestCase;
use PHPUnit_Framework_MockObject_MockObject;
+use Psr\Log\NullLogger;
use Revision;
use TestUserRegistry;
use Title;
->getMock();
$lb->method( 'reallyOpenConnection' )->willReturnCallback(
- function ( array $server, $dbNameOverride ) {
+ function () use ( $server ) {
return $this->getDatabaseMock( $server );
}
);
'cliMode' => true,
'agent' => '',
'load' => 100,
+ 'srvCache' => new HashBagOStuff(),
'profiler' => null,
'trxProfiler' => new TransactionProfiler(),
- 'connLogger' => new \Psr\Log\NullLogger(),
- 'queryLogger' => new \Psr\Log\NullLogger(),
+ 'connLogger' => new NullLogger(),
+ 'queryLogger' => new NullLogger(),
'errorLogger' => function () {
},
'deprecationLogger' => function () {
* @covers \Wikimedia\Rdbms\DatabasePostgres::getAttributes
*/
public function testAttributes() {
- $this->assertTrue( DatabasePostgres::getAttributes()[Database::ATTR_SCHEMAS_AS_TABLE_GROUPS] );
+ $this->assertTrue(
+ Database::attributesFromType( 'postgres' )[Database::ATTR_SCHEMAS_AS_TABLE_GROUPS]
+ );
}
}
* @copyright © 2013 Wikimedia Foundation Inc.
*/
+use Wikimedia\AtEase\AtEase;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\LBFactory;
unset( $db );
/** @var IMaintainableDatabase $db */
- $db = $lb->getConnection( DB_MASTER, [], '' );
+ $db = $lb->getConnection( DB_MASTER, [], $lb::DOMAIN_ANY );
$this->assertEquals(
'',
);
$lb = $factory->getMainLB();
/** @var IMaintainableDatabase $db */
- $db = $lb->getConnection( DB_MASTER, [], '' );
+ $db = $lb->getConnection( DB_MASTER, [], $lb::DOMAIN_ANY );
$this->assertEquals( '', $db->getDomainID(), "Null domain used" );
);
$lb = $factory->getMainLB();
/** @var IDatabase $db */
- $db = $lb->getConnection( DB_MASTER, [], '' );
+ $db = $lb->getConnection( DB_MASTER, [], $lb::DOMAIN_ANY );
- \Wikimedia\suppressWarnings();
+ AtEase::suppressWarnings();
try {
- $this->assertFalse( $db->selectDB( 'garbage-db' ) );
+ $this->assertFalse( $db->selectDomain( 'garbagedb' ) );
$this->fail( "No error thrown." );
} catch ( \Wikimedia\Rdbms\DBQueryError $e ) {
- $this->assertRegExp( '/[\'"]garbage-db[\'"]/', $e->getMessage() );
+ $this->assertRegExp( '/[\'"]garbagedb[\'"]/', $e->getMessage() );
}
- \Wikimedia\restoreWarnings();
+ AtEase::restoreWarnings();
}
/**
);
$lb = $factory->getMainLB();
- if ( !$lb->getConnection( DB_MASTER )->databasesAreIndependent() ) {
- $this->markTestSkipped( "Not applicable per databasesAreIndependent()" );
+ if ( !$factory->getMainLB()->getServerAttributes( 0 )[Database::ATTR_DB_IS_FILE] ) {
+ $this->markTestSkipped( "Not applicable per ATTR_DB_IS_FILE" );
}
/** @var IDatabase $db */
- $lb->getConnection( DB_MASTER, [], '' );
+ $this->assertNotNull( $lb->getConnection( DB_MASTER, [], $lb::DOMAIN_ANY ) );
}
/**
}
$db = $lb->getConnection( DB_MASTER );
- $db->selectDB( 'garbage-db' );
+ $db->selectDomain( 'garbage-db' );
}
/**
$val = $this->cache->incrWithInit( $key, 0, 1, 3 );
$this->assertEquals( 4, $val, "Correct init value" );
+ $this->cache->delete( $key );
+
+ $val = $this->cache->incrWithInit( $key, 0, 5 );
+ $this->assertEquals( 5, $val, "Correct init value" );
}
/**
$db->method( 'getMasterServerInfo' )
->willReturn( [ 'serverId' => 172, 'asOf' => time() ] );
+ $db->setLBInfo( 'replica', true );
+
// Fake the current time.
list( $nowSecFrac, $nowSec ) = explode( ' ', microtime() );
$now = (float)$nowSec + (float)$nowSecFrac;