* @file
*/
-use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\LBFactory;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\DBConnRef;
use Wikimedia\Rdbms\MaintainableDBConnRef;
use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\DBUnexpectedError;
/**
* DB accessible external objects.
* @ingroup ExternalStorage
*/
class ExternalStoreDB extends ExternalStoreMedium {
+ /** @var LBFactory */
+ private $lbFactory;
+
+ /**
+ * @see ExternalStoreMedium::__construct()
+ * @param array $params Additional parameters include:
+ * - lbFactory: an LBFactory instance
+ */
+ public function __construct( array $params ) {
+ parent::__construct( $params );
+ if ( !isset( $params['lbFactory'] ) || !( $params['lbFactory'] instanceof LBFactory ) ) {
+ throw new InvalidArgumentException( "LBFactory required in 'lbFactory' field." );
+ }
+ $this->lbFactory = $params['lbFactory'];
+ }
+
/**
* The provided URL is in the form of DB://cluster/id
* or DB://cluster/id/itemid for concatened storage.
return $ret;
}
+ /**
+ * @inheritDoc
+ */
public function store( $location, $data ) {
$dbw = $this->getMaster( $location );
- $dbw->insert( $this->getTable( $dbw ),
+ $dbw->insert(
+ $this->getTable( $dbw, $location ),
[ 'blob_text' => $data ],
- __METHOD__ );
+ __METHOD__
+ );
$id = $dbw->insertId();
if ( !$id ) {
throw new MWException( __METHOD__ . ': no insert ID' );
return "DB://$location/$id";
}
+ /**
+ * @inheritDoc
+ */
public function isReadOnly( $location ) {
- return ( $this->getLoadBalancer( $location )->getReadOnlyReason() !== false );
+ if ( parent::isReadOnly( $location ) ) {
+ return true;
+ }
+
+ $lb = $this->getLoadBalancer( $location );
+ $domainId = $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
+
+ return ( $lb->getReadOnlyReason( $domainId ) !== false );
}
/**
* @return ILoadBalancer
*/
private function getLoadBalancer( $cluster ) {
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- return $lbFactory->getExternalLB( $cluster );
+ return $this->lbFactory->getExternalLB( $cluster );
}
/**
* Get a replica DB connection for the specified cluster
*
+ * @since 1.34
* @param string $cluster Cluster name
* @return DBConnRef
*/
- public function getSlave( $cluster ) {
- global $wgDefaultExternalStore;
-
+ public function getReplica( $cluster ) {
$lb = $this->getLoadBalancer( $cluster );
- $domainId = $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
-
- if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
- wfDebug( "read only external store\n" );
- $lb->allowLagged( true );
- } else {
- wfDebug( "writable external store\n" );
- }
- $db = $lb->getConnectionRef( DB_REPLICA, [], $domainId );
- $db->clearFlag( DBO_TRX ); // sanity
+ return $lb->getConnectionRef(
+ DB_REPLICA,
+ [],
+ $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) ),
+ $lb::CONN_TRX_AUTOCOMMIT
+ );
+ }
- return $db;
+ /**
+ * Get a replica DB connection for the specified cluster
+ *
+ * @param string $cluster Cluster name
+ * @return DBConnRef
+ * @deprecated since 1.34
+ */
+ public function getSlave( $cluster ) {
+ return $this->getReplica( $cluster );
}
/**
*/
public function getMaster( $cluster ) {
$lb = $this->getLoadBalancer( $cluster );
- $domainId = $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
-
- $db = $lb->getMaintenanceConnectionRef( DB_MASTER, [], $domainId );
- $db->clearFlag( DBO_TRX ); // sanity
- return $db;
+ return $lb->getMaintenanceConnectionRef(
+ DB_MASTER,
+ [],
+ $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) ),
+ $lb::CONN_TRX_AUTOCOMMIT
+ );
}
/**
* @return string|bool Database domain ID or false
*/
private function getDomainId( array $server ) {
- if ( isset( $this->params['wiki'] ) ) {
- return $this->params['wiki']; // explicit domain
+ if ( $this->isDbDomainExplicit ) {
+ return $this->dbDomain; // explicit foreign domain
}
if ( isset( $server['dbname'] ) ) {
* Get the 'blobs' table name for this database
*
* @param IDatabase $db
+ * @param string|null $cluster Cluster name
* @return string Table name ('blobs' by default)
*/
- public function getTable( $db ) {
- $table = $db->getLBInfo( 'blobs table' );
- if ( is_null( $table ) ) {
- $table = 'blobs';
+ public function getTable( $db, $cluster = null ) {
+ if ( $cluster !== null ) {
+ $lb = $this->getLoadBalancer( $cluster );
+ $info = $lb->getServerInfo( $lb->getWriterIndex() );
+ if ( isset( $info['blobs table'] ) ) {
+ return $info['blobs table'];
+ }
}
- return $table;
+ return $db->getLBInfo( 'blobs table' ) ?? 'blobs'; // b/c
+ }
+
+ /**
+ * Create the appropriate blobs table on this cluster
+ *
+ * @see getTable()
+ * @since 1.34
+ * @param string $cluster
+ */
+ public function initializeTable( $cluster ) {
+ global $IP;
+
+ static $supportedTypes = [ 'mysql', 'sqlite' ];
+
+ $dbw = $this->getMaster( $cluster );
+ if ( !in_array( $dbw->getType(), $supportedTypes, true ) ) {
+ throw new DBUnexpectedError( $dbw, "RDBMS type '{$dbw->getType()}' not supported." );
+ }
+
+ $sqlFilePath = "$IP/maintenance/storage/blobs.sql";
+ $sql = file_get_contents( $sqlFilePath );
+ if ( $sql === false ) {
+ throw new RuntimeException( "Failed to read '$sqlFilePath'." );
+ }
+
+ $rawTable = $this->getTable( $dbw, $cluster ); // e.g. "blobs_cluster23"
+ $encTable = $dbw->tableName( $rawTable );
+ $dbw->query(
+ str_replace(
+ [ '/*$wgDBprefix*/blobs', '/*_*/blobs' ],
+ [ $encTable, $encTable ],
+ $sql
+ ),
+ __METHOD__,
+ $dbw::QUERY_IGNORE_DBO_TRX
+ );
}
/**
static $externalBlobCache = [];
$cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
-
- $wiki = $this->params['wiki'] ?? false;
- $cacheID = ( $wiki === false ) ? $cacheID : "$cacheID@$wiki";
+ $cacheID = "$cacheID@{$this->dbDomain}";
if ( isset( $externalBlobCache[$cacheID] ) ) {
- wfDebugLog( 'ExternalStoreDB-cache',
- "ExternalStoreDB::fetchBlob cache hit on $cacheID" );
+ $this->logger->debug( "ExternalStoreDB::fetchBlob cache hit on $cacheID" );
return $externalBlobCache[$cacheID];
}
- wfDebugLog( 'ExternalStoreDB-cache',
- "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
+ $this->logger->debug( "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
- $dbr = $this->getSlave( $cluster );
- $ret = $dbr->selectField( $this->getTable( $dbr ),
- 'blob_text', [ 'blob_id' => $id ], __METHOD__ );
+ $dbr = $this->getReplica( $cluster );
+ $ret = $dbr->selectField(
+ $this->getTable( $dbr, $cluster ),
+ 'blob_text',
+ [ 'blob_id' => $id ],
+ __METHOD__
+ );
if ( $ret === false ) {
- wfDebugLog( 'ExternalStoreDB',
- "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
+ $this->logger->info( "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
// Try the master
$dbw = $this->getMaster( $cluster );
- $ret = $dbw->selectField( $this->getTable( $dbw ),
- 'blob_text', [ 'blob_id' => $id ], __METHOD__ );
+ $ret = $dbw->selectField(
+ $this->getTable( $dbw, $cluster ),
+ 'blob_text',
+ [ 'blob_id' => $id ],
+ __METHOD__
+ );
if ( $ret === false ) {
- wfDebugLog( 'ExternalStoreDB',
- "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
+ $this->logger->error( "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
}
}
if ( $itemID !== false && $ret !== false ) {
* Unlocated ids are not represented
*/
private function batchFetchBlobs( $cluster, array $ids ) {
- $dbr = $this->getSlave( $cluster );
- $res = $dbr->select( $this->getTable( $dbr ),
- [ 'blob_id', 'blob_text' ], [ 'blob_id' => array_keys( $ids ) ], __METHOD__ );
+ $dbr = $this->getReplica( $cluster );
+ $res = $dbr->select(
+ $this->getTable( $dbr, $cluster ),
+ [ 'blob_id', 'blob_text' ],
+ [ 'blob_id' => array_keys( $ids ) ],
+ __METHOD__
+ );
+
$ret = [];
if ( $res !== false ) {
$this->mergeBatchResult( $ret, $ids, $res );
}
if ( $ids ) {
- wfDebugLog( __CLASS__, __METHOD__ .
- " master fallback on '$cluster' for: " .
- implode( ',', array_keys( $ids ) ) );
+ $this->logger->info(
+ __METHOD__ . ": master fallback on '$cluster' for: " .
+ implode( ',', array_keys( $ids ) )
+ );
// Try the master
$dbw = $this->getMaster( $cluster );
- $res = $dbw->select( $this->getTable( $dbr ),
+ $res = $dbw->select(
+ $this->getTable( $dbr, $cluster ),
[ 'blob_id', 'blob_text' ],
[ 'blob_id' => array_keys( $ids ) ],
__METHOD__ );
if ( $res === false ) {
- wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'" );
+ $this->logger->error( __METHOD__ . ": master failed on '$cluster'" );
} else {
$this->mergeBatchResult( $ret, $ids, $res );
}
}
if ( $ids ) {
- wfDebugLog( __CLASS__, __METHOD__ .
- " master on '$cluster' failed locating items: " .
- implode( ',', array_keys( $ids ) ) );
+ $this->logger->error(
+ __METHOD__ . ": master on '$cluster' failed locating items: " .
+ implode( ',', array_keys( $ids ) )
+ );
}
return $ret;