28use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
48 use ProtectedHookAccessorTrait;
105 $this->wanCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
117 if ( !self::$instance || !self::$instance->title->equals(
$title ) ) {
118 self::$instance =
new self(
$title );
131 return [
'partitionCache',
'fullResultCache',
'title' ];
138 $this->partitionCache = [];
139 $this->fullResultCache = [];
140 $this->wanCache->touchCheckKey( $this->
makeCheckKey() );
159 if ( $this->db ===
null ) {
174 public function getLinks( $table, $startId =
false, $endId =
false, $max = INF ) {
175 return TitleArray::newFromResult( $this->
queryLinks( $table, $startId, $endId, $max ) );
187 protected function queryLinks( $table, $startId, $endId, $max, $select =
'all' ) {
188 if ( !$startId && !$endId && is_infinite( $max )
189 && isset( $this->fullResultCache[$table] )
191 wfDebug( __METHOD__ .
": got results from cache" );
192 $res = $this->fullResultCache[$table];
194 wfDebug( __METHOD__ .
": got results from DB" );
195 $fromField = $this->
getPrefix( $table ) .
'_from';
200 $conds[] =
"$fromField >= " . intval( $startId );
203 $conds[] =
"$fromField <= " . intval( $endId );
205 $options = [
'ORDER BY' => $fromField ];
206 if ( is_finite( $max ) && $max > 0 ) {
207 $options[
'LIMIT'] = $max;
210 if ( $select ===
'ids' ) {
214 [
'page_id' => $fromField ],
215 array_filter( $conds,
function ( $clause ) {
216 return !preg_match(
'/(\b|=)page_id(\b|=)/', $clause );
225 [
'page_namespace',
'page_title',
'page_id' ],
228 array_merge( [
'STRAIGHT_JOIN' ], $options )
232 if ( $select ===
'all' && !$startId && !$endId &&
$res->numRows() < $max ) {
234 $this->fullResultCache[$table] =
$res;
236 wfDebug( __METHOD__ .
": results from DB were uncacheable" );
252 'imagelinks' =>
'il',
253 'categorylinks' =>
'cl',
254 'templatelinks' =>
'tl',
258 if ( isset( $prefixes[$table] ) ) {
259 return $prefixes[$table];
262 $this->getHookRunner()->onBacklinkCacheGetPrefix( $table, $prefix );
266 throw new MWException(
"Invalid table \"$table\" in " . __CLASS__ );
283 case 'templatelinks':
285 "{$prefix}_namespace" => $this->title->getNamespace(),
286 "{$prefix}_title" => $this->title->getDBkey(),
287 "page_id={$prefix}_from"
292 "{$prefix}_namespace" => $this->title->getNamespace(),
293 "{$prefix}_title" => $this->title->getDBkey(),
294 $this->
getDB()->makeList( [
295 "{$prefix}_interwiki" =>
'',
296 "{$prefix}_interwiki IS NULL",
298 "page_id={$prefix}_from"
302 case 'categorylinks':
304 "{$prefix}_to" => $this->title->getDBkey(),
305 "page_id={$prefix}_from"
310 $this->getHookRunner()->onBacklinkCacheGetConditions( $table, $this->title, $conds );
312 throw new MWException(
"Invalid table \"$table\" in " . __CLASS__ );
338 if ( isset( $this->partitionCache[$table] ) ) {
339 $entry = reset( $this->partitionCache[$table] );
341 return min( $max, $entry[
'numRows'] );
345 if ( isset( $this->fullResultCache[$table] ) ) {
346 return min( $max, $this->fullResultCache[$table]->numRows() );
349 $memcKey = $this->wanCache->makeKey(
351 md5( $this->title->getPrefixedDBkey() ),
357 $count = $this->wanCache->get(
364 if ( $count && ( $curTTL > 0 ) ) {
365 return min( $max, $count );
369 if ( is_infinite( $max ) ) {
376 $count = $this->
getLinks( $table,
false,
false, $max )->count();
377 if ( $count < $max ) {
378 $this->wanCache->set( $memcKey, $count, self::CACHE_EXPIRY );
382 return min( $max, $count );
396 if ( isset( $this->partitionCache[$table][$batchSize] ) ) {
397 wfDebug( __METHOD__ .
": got from partition cache" );
399 return $this->partitionCache[$table][$batchSize][
'batches'];
402 $this->partitionCache[$table][$batchSize] =
false;
403 $cacheEntry =& $this->partitionCache[$table][$batchSize];
406 if ( isset( $this->fullResultCache[$table] ) ) {
407 $cacheEntry = $this->
partitionResult( $this->fullResultCache[$table], $batchSize );
408 wfDebug( __METHOD__ .
": got from full result cache" );
410 return $cacheEntry[
'batches'];
413 $memcKey = $this->wanCache->makeKey(
415 md5( $this->title->getPrefixedDBkey() ),
422 $memcValue = $this->wanCache->get(
429 if ( is_array( $memcValue ) && ( $curTTL > 0 ) ) {
430 $cacheEntry = $memcValue;
431 wfDebug( __METHOD__ .
": got from memcached $memcKey" );
433 return $cacheEntry[
'batches'];
437 $cacheEntry = [
'numRows' => 0,
'batches' => [] ];
440 $selectSize = max( $batchSize, 200000 - ( 200000 % $batchSize ) );
443 $res = $this->
queryLinks( $table, $start,
false, $selectSize,
'ids' );
446 $cacheEntry[
'numRows'] += $partitions[
'numRows'];
447 $cacheEntry[
'batches'] = array_merge( $cacheEntry[
'batches'], $partitions[
'batches'] );
448 if ( count( $partitions[
'batches'] ) ) {
449 list( , $lEnd ) = end( $partitions[
'batches'] );
452 }
while ( $partitions[
'numRows'] >= $selectSize );
454 if ( count( $cacheEntry[
'batches'] ) ) {
455 $cacheEntry[
'batches'][0][0] =
false;
456 $cacheEntry[
'batches'][count( $cacheEntry[
'batches'] ) - 1][1] =
false;
460 $this->wanCache->set( $memcKey, $cacheEntry, self::CACHE_EXPIRY );
463 $memcKey = $this->wanCache->makeKey(
465 md5( $this->title->getPrefixedDBkey() ),
468 $this->wanCache->set( $memcKey, $cacheEntry[
'numRows'], self::CACHE_EXPIRY );
470 wfDebug( __METHOD__ .
": got from database" );
472 return $cacheEntry[
'batches'];
485 $numRows =
$res->numRows();
486 $numBatches = ceil( $numRows / $batchSize );
488 for ( $i = 0; $i < $numBatches; $i++ ) {
489 if ( $i == 0 && $isComplete ) {
492 $rowNum = $i * $batchSize;
493 $res->seek( $rowNum );
494 $row =
$res->fetchObject();
495 $start = (int)$row->page_id;
498 if ( $i == ( $numBatches - 1 ) && $isComplete ) {
501 $rowNum = min( $numRows - 1, ( $i + 1 ) * $batchSize - 1 );
502 $res->seek( $rowNum );
503 $row =
$res->fetchObject();
504 $end = (int)$row->page_id;
508 if ( $start && $end && $start > $end ) {
509 throw new MWException( __METHOD__ .
': Internal error: query result out of order' );
512 $batches[] = [ $start, $end ];
515 return [
'numRows' => $numRows,
'batches' => $batches ];
529 $resSets[] =
$dbr->select(
530 [
'templatelinks',
'page_restrictions',
'page' ],
531 [
'page_namespace',
'page_title',
'page_id' ],
533 'tl_namespace' => $this->title->getNamespace(),
534 'tl_title' => $this->title->getDBkey(),
542 if ( $this->title->getNamespace() ==
NS_FILE ) {
543 $resSets[] =
$dbr->select(
544 [
'imagelinks',
'page_restrictions',
'page' ],
545 [
'page_namespace',
'page_title',
'page_id' ],
547 'il_to' => $this->title->getDBkey(),
559 foreach ( $resSets as
$res ) {
560 foreach (
$res as $row ) {
561 $mergedRes[$row->page_id] = $row;
565 return TitleArray::newFromResult(
575 return $this->wanCache->makeKey(
577 md5( $this->title->getPrefixedDBkey() )
$wgUpdateRowsPerJob
Number of rows to update per job.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Class for fetching backlink lists, approximate backlink counts and partitions.
getCascadeProtectedLinks()
Get a Title iterator for cascade-protected template/file use backlinks.
array[] $partitionCache
Multi dimensions array representing batches.
queryLinks( $table, $startId, $endId, $max, $select='all')
Get the backlinks for a given table.
getLinks( $table, $startId=false, $endId=false, $max=INF)
Get the backlinks for a given table.
partition( $table, $batchSize)
Partition the backlinks into batches.
IResultWrapper[] $fullResultCache
Contains the whole links from a database result.
getPrefix( $table)
Get the field name prefix for a given table.
partitionResult( $res, $batchSize, $isComplete=true)
Partition a DB result with backlinks in it into batches.
__construct(Title $title)
Create a new BacklinkCache.
clear()
Clear locally stored data and database object.
getDB()
Get the replica DB connection to the database When non existing, will initialize the connection.
__sleep()
Serialization handler, diasallows to serialize the database to prevent failures after this class is d...
getNumLinks( $table, $max=INF)
Get the approximate number of backlinks.
hasLinks( $table)
Check if there are any backlinks.
setDB( $db)
Set the Database object to use.
$db
Local copy of a database object.
makeCheckKey()
Returns check key for the backlinks cache for a particular title.
getConditions( $table)
Get the SQL condition array for selecting backlinks, with a join on the page table.
$title
Local copy of a Title object.
static BacklinkCache $instance
Represents a title within MediaWiki.
Multi-datacenter aware caching interface.