51 throw new InvalidArgumentException(
"LBFactory required in 'lbFactory' field." );
53 $this->lbFactory =
$params[
'lbFactory'];
65 list( $cluster, $id, $itemID ) = $this->
parseURL( $url );
66 $ret = $this->
fetchBlob( $cluster, $id, $itemID );
68 if ( $itemID !==
false && $ret !==
false ) {
69 return $ret->getItem( $itemID );
85 $batched = $inverseUrlMap = [];
86 foreach ( $urls as $url ) {
87 list( $cluster, $id, $itemID ) = $this->
parseURL( $url );
88 $batched[$cluster][$id][] = $itemID;
91 $inverseUrlMap[$cluster][$id][$itemID] = $url;
94 foreach ( $batched as $cluster => $batchByCluster ) {
98 foreach ( $batchByCluster[$id] as $itemID ) {
99 $url = $inverseUrlMap[$cluster][$id][$itemID];
100 if ( $itemID ===
false ) {
103 $ret[$url] =
$blob->getItem( $itemID );
115 public function store( $location, $data ) {
119 [
'blob_text' => $data ],
122 $id = $dbw->insertId();
124 throw new MWException( __METHOD__ .
': no insert ID' );
127 return "DB://$location/$id";
134 if ( parent::isReadOnly( $location ) ) {
139 $domainId = $this->
getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
141 return ( $lb->getReadOnlyReason( $domainId ) !== false );
151 return $this->lbFactory->getExternalLB( $cluster );
164 return $lb->getConnectionRef(
167 $this->
getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) ),
168 $lb::CONN_TRX_AUTOCOMMIT
192 return $lb->getMaintenanceConnectionRef(
195 $this->
getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) ),
196 $lb::CONN_TRX_AUTOCOMMIT
205 if ( $this->isDbDomainExplicit ) {
209 if ( isset( $server[
'dbname'] ) ) {
217 $server[
'schema'] ??
null,
218 $server[
'tablePrefix'] ??
''
221 return $domain->getId();
235 if ( $cluster !==
null ) {
237 $info = $lb->getServerInfo( $lb->getWriterIndex() );
238 if ( isset( $info[
'blobs table'] ) ) {
239 return $info[
'blobs table'];
243 return $db->getLBInfo(
'blobs table' ) ??
'blobs';
256 static $supportedTypes = [
'mysql',
'sqlite' ];
259 if ( !in_array( $dbw->getType(), $supportedTypes,
true ) ) {
260 throw new DBUnexpectedError( $dbw,
"RDBMS type '{$dbw->getType()}' not supported." );
263 $sqlFilePath =
"$IP/maintenance/storage/blobs.sql";
264 $sql = file_get_contents( $sqlFilePath );
265 if ( $sql ===
false ) {
266 throw new RuntimeException(
"Failed to read '$sqlFilePath'." );
269 $rawTable = $this->
getTable( $dbw, $cluster );
270 $encTable = $dbw->tableName( $rawTable );
273 [
'/*$wgDBprefix*/blobs',
'/*_*/blobs' ],
274 [ $encTable, $encTable ],
278 $dbw::QUERY_IGNORE_DBO_TRX
298 static $externalBlobCache = [];
300 $cacheID = ( $itemID === false ) ?
"$cluster/$id" :
"$cluster/$id/";
301 $cacheID =
"$cacheID@{$this->dbDomain}";
303 if ( isset( $externalBlobCache[$cacheID] ) ) {
304 $this->logger->debug(
"ExternalStoreDB::fetchBlob cache hit on $cacheID" );
306 return $externalBlobCache[$cacheID];
309 $this->logger->debug(
"ExternalStoreDB::fetchBlob cache miss on $cacheID" );
312 $ret =
$dbr->selectField(
315 [
'blob_id' => $id ],
318 if ( $ret ===
false ) {
319 $this->logger->info(
"ExternalStoreDB::fetchBlob master fallback on $cacheID" );
322 $ret = $dbw->selectField(
325 [
'blob_id' => $id ],
328 if ( $ret ===
false ) {
329 $this->logger->error(
"ExternalStoreDB::fetchBlob master failed to find $cacheID" );
332 if ( $itemID !==
false && $ret !==
false ) {
337 $externalBlobCache = [ $cacheID => $ret ];
354 [
'blob_id',
'blob_text' ],
355 [
'blob_id' => array_keys( $ids ) ],
360 if (
$res !==
false ) {
365 __METHOD__ .
": master fallback on '$cluster' for: " .
366 implode(
',', array_keys( $ids ) )
372 [
'blob_id',
'blob_text' ],
373 [
'blob_id' => array_keys( $ids ) ],
375 if (
$res ===
false ) {
376 $this->logger->error( __METHOD__ .
": master failed on '$cluster'" );
382 $this->logger->error(
383 __METHOD__ .
": master on '$cluster' failed locating items: " .
384 implode(
',', array_keys( $ids ) )
398 foreach (
$res as $row ) {
400 $itemIDs = $ids[$id];
402 if ( count( $itemIDs ) === 1 && reset( $itemIDs ) ===
false ) {
404 $ret[$id] = $row->blob_text;
417 $path = explode(
'/', $url );