33 use InvalidArgumentException;
38 use Wikimedia\Assert\Assert;
39 use Wikimedia\AtEase\AtEase;
130 Assert::parameterType(
'integer',
$cacheExpiry,
'$cacheExpiry' );
210 return $lb->getConnectionRef( $index, [], $this->dbDomain );
227 # Write to external storage if required
228 if ( $this->useExternalStore ) {
230 $data = $this->extStoreAccess->insert( $data, [
'domain' => $this->dbDomain ] );
237 $flags .=
'external';
246 $old_id = $dbw->nextSequenceValue(
'text_old_id_seq' );
252 'old_flags' => $flags,
257 $textId = $dbw->insertId();
277 public function getBlob( $blobAddress, $queryFlags = 0 ) {
278 Assert::parameterType(
'string', $blobAddress,
'$blobAddress' );
281 $blob = $this->cache->getWithSetCallback(
284 function ( $unused, &$ttl, &$setOpts ) use ( $blobAddress, $queryFlags, &$error ) {
286 list( $result, $errors ) = $this->
fetchBlobs( [ $blobAddress ], $queryFlags );
288 $error = $errors[$blobAddress] ??
null;
289 return $result[$blobAddress];
298 Assert::postcondition( is_string(
$blob ),
'Blob must not be null' );
315 $addressByCacheKey = $this->cache->makeMultiKeys(
317 function ( $blobAddress ) {
321 $blobsByCacheKey = $this->cache->getMultiWithUnionSetCallback(
324 function ( array $blobAddresses, array &$ttls, array &$setOpts ) use ( $queryFlags, &$errors ) {
326 list( $result, $errors ) = $this->
fetchBlobs( $blobAddresses, $queryFlags );
336 $blobsByAddress = [];
337 foreach ( $blobsByCacheKey as $cacheKey =>
$blob ) {
338 $blobsByAddress[ $addressByCacheKey[ $cacheKey ] ] =
$blob !==
false ?
$blob :
null;
343 foreach ( $errors as $error ) {
344 $result->warning(
'internalerror', $error );
361 $textIdToBlobAddress = [];
364 foreach ( $blobAddresses as $blobAddress ) {
367 if ( $schema ===
'tt' ) {
368 $textId = intval( $id );
369 $textIdToBlobAddress[$textId] = $blobAddress;
371 $errors[$blobAddress] =
"Unknown blob address schema: $schema";
372 $result[$blobAddress] =
false;
376 if ( !$textId || $id !== (
string)$textId ) {
377 $errors[$blobAddress] =
"Bad blob address: $blobAddress";
378 $result[$blobAddress] =
false;
382 $textIds = array_keys( $textIdToBlobAddress );
384 return [ $result, $errors ];
389 ? self::READ_LATEST_IMMUTABLE
391 list( $index, $options, $fallbackIndex, $fallbackOptions ) =
395 $rows = $dbConnection->select(
397 [
'old_id',
'old_text',
'old_flags' ],
398 [
'old_id' => $textIds ],
405 if ( $dbConnection->numRows( $rows ) !== count( $textIds ) && $fallbackIndex !== null ) {
406 $fetchedTextIds = [];
407 foreach ( $rows as $row ) {
408 $fetchedTextIds[] = $row->old_id;
410 $missingTextIds = array_diff( $textIds, $fetchedTextIds );
412 $rowsFromFallback = $dbConnection->select(
414 [
'old_id',
'old_text',
'old_flags' ],
415 [
'old_id' => $missingTextIds ],
419 $appendIterator =
new AppendIterator();
420 $appendIterator->append( $rows );
421 $appendIterator->append( $rowsFromFallback );
422 $rows = $appendIterator;
425 foreach ( $rows as $row ) {
426 $blobAddress = $textIdToBlobAddress[$row->old_id];
427 $blob = $this->
expandBlob( $row->old_text, $row->old_flags, $blobAddress );
428 if (
$blob ===
false ) {
429 $errors[$blobAddress] =
"Bad data in text row {$row->old_id}.";
431 $result[$blobAddress] =
$blob;
435 if ( count( $result ) !== count( $blobAddresses ) ) {
436 foreach ( $blobAddresses as $blobAddress ) {
437 if ( !isset( $result[$blobAddress ] ) ) {
438 $errors[$blobAddress] =
"Unable to fetch blob at $blobAddress";
439 $result[$blobAddress] =
false;
443 return [ $result, $errors ];
457 return $this->cache->makeGlobalKey(
459 $this->dbLoadBalancer->resolveDomainID( $this->dbDomain ),
483 public function expandBlob( $raw, $flags, $cacheKey =
null ) {
484 if ( is_string( $flags ) ) {
485 $flags = explode(
',', $flags );
489 if ( in_array(
'external', $flags ) ) {
491 $parts = explode(
'://', $url, 2 );
492 if ( count( $parts ) == 1 || $parts[1] ==
'' ) {
498 return $this->cache->getWithSetCallback(
501 function () use ( $url, $flags ) {
503 $blob = $this->extStoreAccess
504 ->fetchFromURL( $url, [
'domain' => $this->dbDomain ] );
511 $blob = $this->extStoreAccess->fetchFromURL( $url, [
'domain' => $this->dbDomain ] );
541 $blobFlags[] =
'utf-8';
543 if ( $this->compressBlobs ) {
544 if ( function_exists(
'gzdeflate' ) ) {
545 $deflated = gzdeflate(
$blob );
547 if ( $deflated ===
false ) {
551 $blobFlags[] =
'gzip';
554 wfDebug( __METHOD__ .
" -- no zlib support, not compressing\n" );
557 return implode(
',', $blobFlags );
577 Assert::parameterType(
'string',
$blob,
'$blob' );
579 if ( in_array(
'error', $blobFlags ) ) {
584 if ( in_array(
'gzip', $blobFlags ) ) {
585 # Deal with optional compression of archived pages.
586 # This can be done periodically via maintenance/compressOld.php, and
587 # as pages are saved if $wgCompressRevisions is set.
590 if (
$blob ===
false ) {
591 wfWarn( __METHOD__ .
': gzinflate() failed' );
596 if ( in_array(
'object', $blobFlags ) ) {
597 # Generic compressed storage
599 if ( !is_object( $obj ) ) {
603 $blob = $obj->getText();
607 if (
$blob !==
false && $this->legacyEncoding
608 && !in_array(
'utf-8', $blobFlags ) && !in_array(
'utf8', $blobFlags )
610 # Old revisions kept around in a legacy encoding?
611 # Upconvert on demand.
612 # ("utf8" checked for compatibility with some broken
613 # conversion scripts 2008-12-30)
614 # Even with //IGNORE iconv can whine about illegal characters in
615 # *input* string. We just ignore those too.
616 # REF: https://bugs.php.net/bug.php?id=37166
617 # REF: https://phabricator.wikimedia.org/T18885
618 AtEase::suppressWarnings();
619 $blob = iconv( $this->legacyEncoding,
'UTF-8//IGNORE',
$blob );
620 AtEase::restoreWarnings();
638 $ttl = WANObjectCache::TTL_UNCACHEABLE;
640 $ttl = $this->cacheExpiry ?: WANObjectCache::TTL_UNCACHEABLE;
669 if ( $schema !==
'tt' ) {
673 $textId = intval( $id );
675 if ( !$textId || $id !== (
string)$textId ) {
676 throw new InvalidArgumentException(
"Malformed text_id: $id" );
709 if ( !preg_match(
'/^(\w+):(\w+)(\?(.*))?$/', $address, $m ) ) {
710 throw new InvalidArgumentException(
"Bad blob address: $address" );
713 $schema = strtolower( $m[1] );
715 $parameters = isset( $m[4] ) ?
wfCgiToArray( $m[4] ) : [];
717 return [ $schema, $id, $parameters ];
721 if ( $this->useExternalStore && $this->extStoreAccess->isReadOnly() ) {