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
193 return $lb->getMaintenanceConnectionRef(
196 $this->
getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) ),
197 $lb::CONN_TRX_AUTOCOMMIT
206 if ( $this->isDbDomainExplicit ) {
210 if ( isset( $server[
'dbname'] ) ) {
218 $server[
'schema'] ??
null,
219 $server[
'tablePrefix'] ??
''
222 return $domain->getId();
236 if ( $cluster !==
null ) {
238 $info = $lb->getServerInfo( $lb->getWriterIndex() );
239 if ( isset( $info[
'blobs table'] ) ) {
240 return $info[
'blobs table'];
244 return $db->getLBInfo(
'blobs table' ) ??
'blobs';
257 static $supportedTypes = [
'mysql',
'sqlite' ];
260 if ( !in_array( $dbw->getType(), $supportedTypes,
true ) ) {
261 throw new DBUnexpectedError( $dbw,
"RDBMS type '{$dbw->getType()}' not supported." );
264 $sqlFilePath =
"$IP/maintenance/storage/blobs.sql";
265 $sql = file_get_contents( $sqlFilePath );
266 if ( $sql ===
false ) {
267 throw new RuntimeException(
"Failed to read '$sqlFilePath'." );
270 $rawTable = $this->
getTable( $dbw, $cluster );
271 $encTable = $dbw->tableName( $rawTable );
274 [
'/*$wgDBprefix*/blobs',
'/*_*/blobs' ],
275 [ $encTable, $encTable ],
279 $dbw::QUERY_IGNORE_DBO_TRX
299 static $externalBlobCache = [];
301 $cacheID = ( $itemID === false ) ?
"$cluster/$id" :
"$cluster/$id/";
302 $cacheID =
"$cacheID@{$this->dbDomain}";
304 if ( isset( $externalBlobCache[$cacheID] ) ) {
305 $this->logger->debug(
"ExternalStoreDB::fetchBlob cache hit on $cacheID" );
307 return $externalBlobCache[$cacheID];
310 $this->logger->debug(
"ExternalStoreDB::fetchBlob cache miss on $cacheID" );
313 $ret =
$dbr->selectField(
316 [
'blob_id' => $id ],
319 if ( $ret ===
false ) {
320 $this->logger->info(
"ExternalStoreDB::fetchBlob master fallback on $cacheID" );
323 $ret = $dbw->selectField(
326 [
'blob_id' => $id ],
329 if ( $ret ===
false ) {
330 $this->logger->error(
"ExternalStoreDB::fetchBlob master failed to find $cacheID" );
333 if ( $itemID !==
false && $ret !==
false ) {
338 $externalBlobCache = [ $cacheID => $ret ];
355 [
'blob_id',
'blob_text' ],
356 [
'blob_id' => array_keys( $ids ) ],
361 if (
$res !==
false ) {
366 __METHOD__ .
": master fallback on '$cluster' for: " .
367 implode(
',', array_keys( $ids ) )
373 [
'blob_id',
'blob_text' ],
374 [
'blob_id' => array_keys( $ids ) ],
376 if (
$res ===
false ) {
377 $this->logger->error( __METHOD__ .
": master failed on '$cluster'" );
383 $this->logger->error(
384 __METHOD__ .
": master on '$cluster' failed locating items: " .
385 implode(
',', array_keys( $ids ) )
399 foreach (
$res as $row ) {
401 $itemIDs = $ids[$id];
403 if ( count( $itemIDs ) === 1 && reset( $itemIDs ) ===
false ) {
405 $ret[$id] = $row->blob_text;
418 $path = explode(
'/', $url );