MediaWiki  master
SqlBlobStore.php
Go to the documentation of this file.
1 <?php
28 namespace MediaWiki\Storage;
29 
30 use AppendIterator;
33 use IDBAccessObject;
34 use IExpiringStore;
35 use InvalidArgumentException;
36 use MWException;
37 use StatusValue;
38 use WANObjectCache;
39 use Wikimedia\Assert\Assert;
40 use Wikimedia\AtEase\AtEase;
43 
53 
54  // Note: the name has been taken unchanged from the old Revision class.
55  public const TEXT_CACHE_GROUP = 'revisiontext:10';
56 
60  private $dbLoadBalancer;
61 
65  private $extStoreAccess;
66 
70  private $cache;
71 
75  private $dbDomain;
76 
80  private $cacheExpiry = 604800; // 7 days
81 
85  private $compressBlobs = false;
86 
90  private $legacyEncoding = false;
91 
95  private $useExternalStore = false;
96 
108  public function __construct(
112  $dbDomain = false
113  ) {
114  $this->dbLoadBalancer = $dbLoadBalancer;
115  $this->extStoreAccess = $extStoreAccess;
116  $this->cache = $cache;
117  $this->dbDomain = $dbDomain;
118  }
119 
123  public function getCacheExpiry() {
124  return $this->cacheExpiry;
125  }
126 
130  public function setCacheExpiry( int $cacheExpiry ) {
131  $this->cacheExpiry = $cacheExpiry;
132  }
133 
137  public function getCompressBlobs() {
138  return $this->compressBlobs;
139  }
140 
144  public function setCompressBlobs( $compressBlobs ) {
145  $this->compressBlobs = $compressBlobs;
146  }
147 
152  public function getLegacyEncoding() {
153  return $this->legacyEncoding;
154  }
155 
161  wfDeprecated( __METHOD__ );
162  return null;
163  }
164 
173  public function setLegacyEncoding( string $legacyEncoding ) {
174  $this->legacyEncoding = $legacyEncoding;
175  }
176 
180  public function getUseExternalStore() {
182  }
183 
187  public function setUseExternalStore( bool $useExternalStore ) {
188  $this->useExternalStore = $useExternalStore;
189  }
190 
194  private function getDBLoadBalancer() {
195  return $this->dbLoadBalancer;
196  }
197 
203  private function getDBConnection( $index ) {
204  $lb = $this->getDBLoadBalancer();
205  return $lb->getConnectionRef( $index, [], $this->dbDomain );
206  }
207 
218  public function storeBlob( $data, $hints = [] ) {
219  try {
220  $flags = $this->compressData( $data );
221 
222  # Write to external storage if required
223  if ( $this->useExternalStore ) {
224  // Store and get the URL
225  $data = $this->extStoreAccess->insert( $data, [ 'domain' => $this->dbDomain ] );
226  if ( !$data ) {
227  throw new BlobAccessException( "Failed to store text to external storage" );
228  }
229  if ( $flags ) {
230  $flags .= ',';
231  }
232  $flags .= 'external';
233 
234  // TODO: we could also return an address for the external store directly here.
235  // That would mean bypassing the text table entirely when the external store is
236  // used. We'll need to assess expected fallout before doing that.
237  }
238 
239  $dbw = $this->getDBConnection( DB_PRIMARY );
240 
241  $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
242  $dbw->insert(
243  'text',
244  [
245  'old_id' => $old_id,
246  'old_text' => $data,
247  'old_flags' => $flags,
248  ],
249  __METHOD__
250  );
251 
252  $textId = $dbw->insertId();
253 
254  return self::makeAddressFromTextId( $textId );
255  } catch ( MWException $e ) {
256  throw new BlobAccessException( $e->getMessage(), 0, $e );
257  }
258  }
259 
272  public function getBlob( $blobAddress, $queryFlags = 0 ) {
273  Assert::parameterType( 'string', $blobAddress, '$blobAddress' );
274 
275  $error = null;
276  $blob = $this->cache->getWithSetCallback(
277  $this->getCacheKey( $blobAddress ),
278  $this->getCacheTTL(),
279  function ( $unused, &$ttl, &$setOpts ) use ( $blobAddress, $queryFlags, &$error ) {
280  // Ignore $setOpts; blobs are immutable and negatives are not cached
281  list( $result, $errors ) = $this->fetchBlobs( [ $blobAddress ], $queryFlags );
282  // No negative caching; negative hits on text rows may be due to corrupted replica DBs
283  $error = $errors[$blobAddress] ?? null;
284  return $result[$blobAddress];
285  },
286  [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => IExpiringStore::TTL_PROC_LONG ]
287  );
288 
289  if ( $error ) {
290  throw new BlobAccessException( $error );
291  }
292 
293  Assert::postcondition( is_string( $blob ), 'Blob must not be null' );
294  return $blob;
295  }
296 
308  public function getBlobBatch( $blobAddresses, $queryFlags = 0 ) {
309  // FIXME: All caching has temporarily been removed in I94c6f9ba7b9caeeb due to T235188.
310  // Caching behavior should be restored by reverting I94c6f9ba7b9caeeb as soon as
311  // the root cause of T235188 has been resolved.
312 
313  list( $blobsByAddress, $errors ) = $this->fetchBlobs( $blobAddresses, $queryFlags );
314 
315  $blobsByAddress = array_map( static function ( $blob ) {
316  return $blob === false ? null : $blob;
317  }, $blobsByAddress );
318 
319  $result = StatusValue::newGood( $blobsByAddress );
320  if ( $errors ) {
321  foreach ( $errors as $error ) {
322  $result->warning( 'internalerror', $error );
323  }
324  }
325  return $result;
326  }
327 
338  private function fetchBlobs( $blobAddresses, $queryFlags ) {
339  $textIdToBlobAddress = [];
340  $result = [];
341  $errors = [];
342  foreach ( $blobAddresses as $blobAddress ) {
343  try {
344  list( $schema, $id ) = self::splitBlobAddress( $blobAddress );
345  } catch ( InvalidArgumentException $ex ) {
346  throw new BlobAccessException(
347  $ex->getMessage() . '. Use findBadBlobs.php to remedy.',
348  0,
349  $ex
350  );
351  }
352 
353  // TODO: MCR: also support 'ex' schema with ExternalStore URLs, plus flags encoded in the URL!
354  if ( $schema === 'bad' ) {
355  // Database row was marked as "known bad", no need to trigger an error.
356  wfDebug(
357  __METHOD__
358  . ": loading known-bad content ($blobAddress), returning empty string"
359  );
360  $result[$blobAddress] = '';
361  continue;
362  } elseif ( $schema === 'tt' ) {
363  $textId = intval( $id );
364 
365  if ( $textId < 1 || $id !== (string)$textId ) {
366  $errors[$blobAddress] = "Bad blob address: $blobAddress."
367  . ' Use findBadBlobs.php to remedy.';
368  $result[$blobAddress] = false;
369  }
370 
371  $textIdToBlobAddress[$textId] = $blobAddress;
372  } else {
373  $errors[$blobAddress] = "Unknown blob address schema: $schema."
374  . ' Use findBadBlobs.php to remedy.';
375  $result[$blobAddress] = false;
376  }
377  }
378 
379  $textIds = array_keys( $textIdToBlobAddress );
380  if ( !$textIds ) {
381  return [ $result, $errors ];
382  }
383  // Callers doing updates will pass in READ_LATEST as usual. Since the text/blob tables
384  // do not normally get rows changed around, set READ_LATEST_IMMUTABLE in those cases.
385  $queryFlags |= DBAccessObjectUtils::hasFlags( $queryFlags, self::READ_LATEST )
386  ? self::READ_LATEST_IMMUTABLE
387  : 0;
388  list( $index, $options, $fallbackIndex, $fallbackOptions ) =
389  DBAccessObjectUtils::getDBOptions( $queryFlags );
390  // Text data is immutable; check replica DBs first.
391  $dbConnection = $this->getDBConnection( $index );
392  $rows = $dbConnection->select(
393  'text',
394  [ 'old_id', 'old_text', 'old_flags' ],
395  [ 'old_id' => $textIds ],
396  __METHOD__,
397  $options
398  );
399 
400  // Fallback to DB_PRIMARY in some cases if not all the rows were found, using the appropriate
401  // options, such as FOR UPDATE to avoid missing rows due to REPEATABLE-READ.
402  if ( $dbConnection->numRows( $rows ) !== count( $textIds ) && $fallbackIndex !== null ) {
403  $fetchedTextIds = [];
404  foreach ( $rows as $row ) {
405  $fetchedTextIds[] = $row->old_id;
406  }
407  $missingTextIds = array_diff( $textIds, $fetchedTextIds );
408  $dbConnection = $this->getDBConnection( $fallbackIndex );
409  $rowsFromFallback = $dbConnection->select(
410  'text',
411  [ 'old_id', 'old_text', 'old_flags' ],
412  [ 'old_id' => $missingTextIds ],
413  __METHOD__,
414  $fallbackOptions
415  );
416  $appendIterator = new AppendIterator();
417  $appendIterator->append( $rows );
418  $appendIterator->append( $rowsFromFallback );
419  $rows = $appendIterator;
420  }
421 
422  foreach ( $rows as $row ) {
423  $blobAddress = $textIdToBlobAddress[$row->old_id];
424  $blob = $this->expandBlob( $row->old_text, $row->old_flags, $blobAddress );
425  if ( $blob === false ) {
426  $errors[$blobAddress] = "Bad data in text row {$row->old_id}."
427  . ' Use findBadBlobs.php to remedy.';
428  }
429  $result[$blobAddress] = $blob;
430  }
431 
432  // If we're still missing some of the rows, set errors for missing blobs.
433  if ( count( $result ) !== count( $blobAddresses ) ) {
434  foreach ( $blobAddresses as $blobAddress ) {
435  if ( !isset( $result[$blobAddress ] ) ) {
436  $errors[$blobAddress] = "Unable to fetch blob at $blobAddress."
437  . ' Use findBadBlobs.php to remedy.';
438  $result[$blobAddress] = false;
439  }
440  }
441  }
442  return [ $result, $errors ];
443  }
444 
455  private function getCacheKey( $blobAddress ) {
456  return $this->cache->makeGlobalKey(
457  'SqlBlobStore-blob',
458  $this->dbLoadBalancer->resolveDomainID( $this->dbDomain ),
459  $blobAddress
460  );
461  }
462 
482  public function expandBlob( $raw, $flags, $cacheKey = null ) {
483  if ( is_string( $flags ) ) {
484  $flags = explode( ',', $flags );
485  }
486 
487  // Use external methods for external objects, text in table is URL-only then
488  if ( in_array( 'external', $flags ) ) {
489  $url = $raw;
490  $parts = explode( '://', $url, 2 );
491  if ( count( $parts ) == 1 || $parts[1] == '' ) {
492  return false;
493  }
494 
495  if ( $cacheKey ) {
496  // The cached value should be decompressed, so handle that and return here.
497  return $this->cache->getWithSetCallback(
498  $this->getCacheKey( $cacheKey ),
499  $this->getCacheTTL(),
500  function () use ( $url, $flags ) {
501  // Ignore $setOpts; blobs are immutable and negatives are not cached
502  $blob = $this->extStoreAccess
503  ->fetchFromURL( $url, [ 'domain' => $this->dbDomain ] );
504 
505  return $blob === false ? false : $this->decompressData( $blob, $flags );
506  },
507  [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
508  );
509  } else {
510  $blob = $this->extStoreAccess->fetchFromURL( $url, [ 'domain' => $this->dbDomain ] );
511  return $blob === false ? false : $this->decompressData( $blob, $flags );
512  }
513  } else {
514  return $this->decompressData( $raw, $flags );
515  }
516  }
517 
534  public function compressData( &$blob ) {
535  $blobFlags = [];
536 
537  // Revisions not marked as UTF-8 will have legacy decoding applied by decompressData().
538  // XXX: if $this->legacyEncoding is not set, we could skip this. That would however be
539  // risky, since $this->legacyEncoding being set in the future would lead to data corruption.
540  $blobFlags[] = 'utf-8';
541 
542  if ( $this->compressBlobs ) {
543  if ( function_exists( 'gzdeflate' ) ) {
544  $deflated = gzdeflate( $blob );
545 
546  if ( $deflated === false ) {
547  wfLogWarning( __METHOD__ . ': gzdeflate() failed' );
548  } else {
549  $blob = $deflated;
550  $blobFlags[] = 'gzip';
551  }
552  } else {
553  wfDebug( __METHOD__ . " -- no zlib support, not compressing" );
554  }
555  }
556  return implode( ',', $blobFlags );
557  }
558 
574  public function decompressData( string $blob, array $blobFlags ) {
575  if ( in_array( 'error', $blobFlags ) ) {
576  // Error row, return false
577  return false;
578  }
579 
580  if ( in_array( 'gzip', $blobFlags ) ) {
581  # Deal with optional compression of archived pages.
582  # This can be done periodically via maintenance/compressOld.php, and
583  # as pages are saved if $wgCompressRevisions is set.
584  $blob = gzinflate( $blob );
585 
586  if ( $blob === false ) {
587  wfWarn( __METHOD__ . ': gzinflate() failed' );
588  return false;
589  }
590  }
591 
592  if ( in_array( 'object', $blobFlags ) ) {
593  # Generic compressed storage
594  $obj = unserialize( $blob );
595  if ( !is_object( $obj ) ) {
596  // Invalid object
597  return false;
598  }
599  $blob = $obj->getText();
600  }
601 
602  // Needed to support old revisions left over from the 1.4 / 1.5 migration.
603  if ( $blob !== false && $this->legacyEncoding
604  && !in_array( 'utf-8', $blobFlags ) && !in_array( 'utf8', $blobFlags )
605  ) {
606  # Old revisions kept around in a legacy encoding?
607  # Upconvert on demand.
608  # ("utf8" checked for compatibility with some broken
609  # conversion scripts 2008-12-30)
610  # Even with //IGNORE iconv can whine about illegal characters in
611  # *input* string. We just ignore those too.
612  # REF: https://bugs.php.net/bug.php?id=37166
613  # REF: https://phabricator.wikimedia.org/T18885
614  AtEase::suppressWarnings();
615  $blob = iconv( $this->legacyEncoding, 'UTF-8//IGNORE', $blob );
616  AtEase::restoreWarnings();
617  }
618 
619  return $blob;
620  }
621 
629  private function getCacheTTL() {
631 
632  if ( $cache->getQoS( $cache::ATTR_DURABILITY ) >= $cache::QOS_DURABILITY_RDBMS ) {
633  // Do not cache RDBMs blobs in...the RDBMs store
634  $ttl = $cache::TTL_UNCACHEABLE;
635  } else {
636  $ttl = $this->cacheExpiry ?: $cache::TTL_UNCACHEABLE;
637  }
638 
639  return $ttl;
640  }
641 
662  public function getTextIdFromAddress( $address ) {
663  list( $schema, $id, ) = self::splitBlobAddress( $address );
664 
665  if ( $schema !== 'tt' ) {
666  return null;
667  }
668 
669  $textId = intval( $id );
670 
671  if ( !$textId || $id !== (string)$textId ) {
672  throw new InvalidArgumentException( "Malformed text_id: $id" );
673  }
674 
675  return $textId;
676  }
677 
691  public static function makeAddressFromTextId( $id ) {
692  return 'tt:' . $id;
693  }
694 
705  public static function splitBlobAddress( $address ) {
706  if ( !preg_match( '/^([-+.\w]+):([^\s?]+)(\?([^\s]*))?$/', $address, $m ) ) {
707  throw new InvalidArgumentException( "Bad blob address: $address" );
708  }
709 
710  $schema = strtolower( $m[1] );
711  $id = $m[2];
712  $parameters = isset( $m[4] ) ? wfCgiToArray( $m[4] ) : [];
713 
714  return [ $schema, $id, $parameters ];
715  }
716 
717  public function isReadOnly() {
718  if ( $this->useExternalStore && $this->extStoreAccess->isReadOnly() ) {
719  return true;
720  }
721 
722  return ( $this->getDBLoadBalancer()->getReadOnlyReason() !== false );
723  }
724 }
MediaWiki\Storage\SqlBlobStore\getBlob
getBlob( $blobAddress, $queryFlags=0)
Retrieve a blob, given an address.
Definition: SqlBlobStore.php:272
WANObjectCache\getQoS
getQoS( $flag)
Definition: WANObjectCache.php:2568
MediaWiki\Storage\SqlBlobStore\getDBLoadBalancer
getDBLoadBalancer()
Definition: SqlBlobStore.php:194
MediaWiki\Storage\BlobAccessException
Exception representing a failure to access a data blob.
Definition: BlobAccessException.php:33
StatusValue
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:43
MediaWiki\Storage\SqlBlobStore\getCacheKey
getCacheKey( $blobAddress)
Get a cache key for a given Blob address.
Definition: SqlBlobStore.php:455
MediaWiki\Storage\SqlBlobStore\TEXT_CACHE_GROUP
const TEXT_CACHE_GROUP
Definition: SqlBlobStore.php:55
MediaWiki\Storage\SqlBlobStore\$useExternalStore
bool $useExternalStore
Definition: SqlBlobStore.php:95
MediaWiki\Storage\SqlBlobStore\getDBConnection
getDBConnection( $index)
Definition: SqlBlobStore.php:203
MediaWiki\Storage\SqlBlobStore
Service for storing and loading Content objects.
Definition: SqlBlobStore.php:52
MediaWiki\Storage\SqlBlobStore\__construct
__construct(ILoadBalancer $dbLoadBalancer, ExternalStoreAccess $extStoreAccess, WANObjectCache $cache, $dbDomain=false)
Definition: SqlBlobStore.php:108
MediaWiki\Storage\SqlBlobStore\$compressBlobs
bool $compressBlobs
Definition: SqlBlobStore.php:85
MediaWiki\Storage\SqlBlobStore\$dbDomain
string bool $dbDomain
DB domain ID of a wiki or false for the local one.
Definition: SqlBlobStore.php:75
MediaWiki\Storage\SqlBlobStore\getTextIdFromAddress
getTextIdFromAddress( $address)
Returns an ID corresponding to the old_id field in the text table, corresponding to the given $addres...
Definition: SqlBlobStore.php:662
MediaWiki\Storage\SqlBlobStore\splitBlobAddress
static splitBlobAddress( $address)
Splits a blob address into three parts: the schema, the ID, and parameters/flags.
Definition: SqlBlobStore.php:705
MediaWiki\Storage\SqlBlobStore\expandBlob
expandBlob( $raw, $flags, $cacheKey=null)
Expand a raw data blob according to the flags given.
Definition: SqlBlobStore.php:482
MediaWiki\Storage\SqlBlobStore\$dbLoadBalancer
ILoadBalancer $dbLoadBalancer
Definition: SqlBlobStore.php:60
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1056
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:57
MediaWiki\Storage\SqlBlobStore\getBlobBatch
getBlobBatch( $blobAddresses, $queryFlags=0)
A batched version of BlobStore::getBlob.
Definition: SqlBlobStore.php:308
ExternalStoreAccess
Key/value blob storage for a collection of storage medium types (e.g.
Definition: ExternalStoreAccess.php:22
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
MediaWiki\Storage\SqlBlobStore\setLegacyEncoding
setLegacyEncoding(string $legacyEncoding)
Set the legacy encoding to assume for blobs that do not have the utf-8 flag set.
Definition: SqlBlobStore.php:173
MediaWiki\Storage\SqlBlobStore\getCompressBlobs
getCompressBlobs()
Definition: SqlBlobStore.php:137
MediaWiki\Storage\SqlBlobStore\getLegacyEncoding
getLegacyEncoding()
Definition: SqlBlobStore.php:152
IExpiringStore
Generic interface providing TTL constants for lightweight expiring object stores.
Definition: IExpiringStore.php:13
MWException
MediaWiki exception.
Definition: MWException.php:29
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
MediaWiki\Storage\SqlBlobStore\getLegacyEncodingConversionLang
getLegacyEncodingConversionLang()
Definition: SqlBlobStore.php:160
MediaWiki\Storage\SqlBlobStore\setCacheExpiry
setCacheExpiry(int $cacheExpiry)
Definition: SqlBlobStore.php:130
MediaWiki\Storage\SqlBlobStore\getCacheTTL
getCacheTTL()
Get the text cache TTL.
Definition: SqlBlobStore.php:629
MediaWiki\Storage\SqlBlobStore\$extStoreAccess
ExternalStoreAccess $extStoreAccess
Definition: SqlBlobStore.php:65
MediaWiki\Storage\SqlBlobStore\isReadOnly
isReadOnly()
Check if the blob metadata or backing blob data store is read-only.
Definition: SqlBlobStore.php:717
$blob
$blob
Definition: testCompression.php:70
MediaWiki\Storage\SqlBlobStore\decompressData
decompressData(string $blob, array $blobFlags)
Re-converts revision text according to its flags.
Definition: SqlBlobStore.php:574
MediaWiki\Storage\SqlBlobStore\makeAddressFromTextId
static makeAddressFromTextId( $id)
Returns an address referring to content stored in the text table row with the given ID.
Definition: SqlBlobStore.php:691
wfCgiToArray
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
Definition: GlobalFunctions.php:375
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
DBAccessObjectUtils
Helper class for DAO classes.
Definition: DBAccessObjectUtils.php:29
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
MediaWiki\Storage\SqlBlobStore\storeBlob
storeBlob( $data, $hints=[])
Stores an arbitrary blob of data and returns an address that can be used with getBlob() to retrieve t...
Definition: SqlBlobStore.php:218
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:128
MediaWiki\Storage\SqlBlobStore\fetchBlobs
fetchBlobs( $blobAddresses, $queryFlags)
MCR migration note: this corresponded to Revision::fetchText.
Definition: SqlBlobStore.php:338
MediaWiki\Storage\SqlBlobStore\getUseExternalStore
getUseExternalStore()
Definition: SqlBlobStore.php:180
MediaWiki\Storage
Definition: BlobAccessException.php:23
MediaWiki\Storage\SqlBlobStore\getCacheExpiry
getCacheExpiry()
Definition: SqlBlobStore.php:123
MediaWiki\Storage\SqlBlobStore\$cacheExpiry
int $cacheExpiry
Definition: SqlBlobStore.php:80
MediaWiki\Storage\BlobStore
Service for loading and storing data blobs.
Definition: BlobStore.php:35
unserialize
unserialize( $serialized)
Definition: ApiMessageTrait.php:146
MediaWiki\Storage\SqlBlobStore\setCompressBlobs
setCompressBlobs( $compressBlobs)
Definition: SqlBlobStore.php:144
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1043
MediaWiki\Storage\SqlBlobStore\$cache
WANObjectCache $cache
Definition: SqlBlobStore.php:70
MediaWiki\Storage\SqlBlobStore\setUseExternalStore
setUseExternalStore(bool $useExternalStore)
Definition: SqlBlobStore.php:187
MediaWiki\Storage\SqlBlobStore\compressData
compressData(&$blob)
If $wgCompressRevisions is enabled, we will compress data.
Definition: SqlBlobStore.php:534
MediaWiki\Storage\SqlBlobStore\$legacyEncoding
bool string $legacyEncoding
Definition: SqlBlobStore.php:90
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81