30use Wikimedia\ScopedCallback;
52 throw new InvalidArgumentException(
"LBFactory required in 'lbFactory' field." );
54 $this->lbFactory =
$params[
'lbFactory'];
66 list( $cluster, $id, $itemID ) = $this->
parseURL( $url );
67 $ret = $this->
fetchBlob( $cluster, $id, $itemID );
69 if ( $itemID !==
false && $ret !==
false ) {
70 return $ret->getItem( $itemID );
86 $batched = $inverseUrlMap = [];
87 foreach ( $urls as $url ) {
88 list( $cluster, $id, $itemID ) = $this->
parseURL( $url );
89 $batched[$cluster][$id][] = $itemID;
92 $inverseUrlMap[$cluster][$id][$itemID] = $url;
95 foreach ( $batched as $cluster => $batchByCluster ) {
99 foreach ( $batchByCluster[$id] as $itemID ) {
100 $url = $inverseUrlMap[$cluster][$id][$itemID];
101 if ( $itemID ===
false ) {
104 $ret[$url] =
$blob->getItem( $itemID );
116 public function store( $location, $data ) {
120 [
'blob_text' => $data ],
123 $id = $dbw->insertId();
125 throw new MWException( __METHOD__ .
': no insert ID' );
128 return "DB://$location/$id";
135 if ( parent::isReadOnly( $location ) ) {
140 $domainId = $this->
getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
142 return ( $lb->getReadOnlyReason( $domainId ) !==
false );
152 return $this->lbFactory->getExternalLB( $cluster );
165 return $lb->getConnectionRef(
168 $this->
getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) ),
169 $lb::CONN_TRX_AUTOCOMMIT
195 return $lb->getMaintenanceConnectionRef(
198 $this->
getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) ),
199 $lb::CONN_TRX_AUTOCOMMIT
218 if ( $this->isDbDomainExplicit ) {
222 if ( isset( $server[
'dbname'] ) ) {
230 $server[
'schema'] ??
null,
231 $server[
'tablePrefix'] ??
''
234 return $domain->getId();
248 if ( $cluster !==
null ) {
250 $info = $lb->getServerInfo( $lb->getWriterIndex() );
251 if ( isset( $info[
'blobs table'] ) ) {
252 return $info[
'blobs table'];
256 return $db->getLBInfo(
'blobs table' ) ??
'blobs';
269 static $supportedTypes = [
'mysql',
'sqlite' ];
272 if ( !in_array( $dbw->getType(), $supportedTypes,
true ) ) {
273 throw new DBUnexpectedError( $dbw,
"RDBMS type '{$dbw->getType()}' not supported." );
276 $sqlFilePath =
"$IP/maintenance/storage/blobs.sql";
277 $sql = file_get_contents( $sqlFilePath );
278 if ( $sql ===
false ) {
279 throw new RuntimeException(
"Failed to read '$sqlFilePath'." );
282 $rawTable = $this->
getTable( $dbw, $cluster );
283 $encTable = $dbw->tableName( $rawTable );
286 [
'/*$wgDBprefix*/blobs',
'/*_*/blobs' ],
287 [ $encTable, $encTable ],
291 $dbw::QUERY_IGNORE_DBO_TRX
311 static $externalBlobCache = [];
313 $cacheID = ( $itemID === false ) ?
"$cluster/$id" :
"$cluster/$id/";
314 $cacheID =
"$cacheID@{$this->dbDomain}";
316 if ( isset( $externalBlobCache[$cacheID] ) ) {
317 $this->logger->debug( __METHOD__ .
": cache hit on $cacheID" );
319 return $externalBlobCache[$cacheID];
322 $this->logger->debug( __METHOD__ .
": cache miss on $cacheID" );
325 $ret =
$dbr->selectField(
328 [
'blob_id' => $id ],
331 if ( $ret ===
false ) {
333 $this->logger->warning( __METHOD__ .
": primary DB fallback on $cacheID" );
334 $scope = $this->lbFactory->getTransactionProfiler()->silenceForScope();
336 $ret = $dbw->selectField(
339 [
'blob_id' => $id ],
342 ScopedCallback::consume( $scope );
343 if ( $ret ===
false ) {
344 $this->logger->warning( __METHOD__ .
": primary DB failed to find $cacheID" );
347 if ( $itemID !==
false && $ret !==
false ) {
352 $externalBlobCache = [ $cacheID => $ret ];
369 [
'blob_id',
'blob_text' ],
370 [
'blob_id' => array_keys( $ids ) ],
375 if (
$res !==
false ) {
381 __METHOD__ .
": primary fallback on '$cluster' for: " .
382 implode(
',', array_keys( $ids ) )
384 $scope = $this->lbFactory->getTransactionProfiler()->silenceForScope();
388 [
'blob_id',
'blob_text' ],
389 [
'blob_id' => array_keys( $ids ) ],
391 ScopedCallback::consume( $scope );
392 if (
$res ===
false ) {
393 $this->logger->error( __METHOD__ .
": primary failed on '$cluster'" );
399 $this->logger->error(
400 __METHOD__ .
": primary on '$cluster' failed locating items: " .
401 implode(
',', array_keys( $ids ) )
415 foreach (
$res as $row ) {
417 $itemIDs = $ids[$id];
419 if ( count( $itemIDs ) === 1 && reset( $itemIDs ) ===
false ) {
421 $ret[$id] = $row->blob_text;
434 $path = explode(
'/', $url );
unserialize( $serialized)
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
DB accessible external objects.
getSlave( $cluster)
Get a replica DB connection for the specified cluster.
getPrimary( $cluster)
Get a primary database connection for the specified cluster.
__construct(array $params)
batchFetchBlobs( $cluster, array $ids)
Fetch multiple blob items out of the database.
getReplica( $cluster)
Get a replica DB connection for the specified cluster.
mergeBatchResult(array &$ret, array &$ids, $res)
Helper function for self::batchFetchBlobs for merging primary/replica DB results.
getDomainId(array $server)
initializeTable( $cluster)
Create the appropriate blobs table on this cluster.
fetchBlob( $cluster, $id, $itemID)
Fetch a blob item out of the database; a cache of the last-loaded blob will be kept so that multiple ...
fetchFromURL( $url)
The provided URL is in the form of DB://cluster/id or DB://cluster/id/itemid for concatened storage.
getTable( $db, $cluster=null)
Get the 'blobs' table name for this database.
store( $location, $data)
Insert a data item into a given location.string|bool The URL of the stored data item,...
batchFetchFromURLs(array $urls)
Fetch data from given external store URLs.
isReadOnly( $location)
Check if a given location is read-only.bool Whether this location is read-only 1.31
getLoadBalancer( $cluster)
Get a LoadBalancer for the specified cluster.
Key/value blob storage for a particular storage medium type (e.g.
array $params
Usage context options for this instance.
string $dbDomain
Default database domain to store content under.
Class to handle database/schema/prefix specifications for IDatabase.
Helper class to handle automatically marking connections as reusable (via RAII pattern) as well handl...
Base class for general text storage via the "object" flag in old_flags, or two-part external storage ...