108 if ( !self::$instance || !self::$instance->
title->equals(
$title ) ) {
109 self::$instance =
new self(
$title );
122 return [
'partitionCache',
'fullResultCache',
'title' ];
129 $this->partitionCache = [];
130 $this->fullResultCache = [];
149 if ( !isset( $this->db ) ) {
164 public function getLinks( $table, $startId =
false, $endId =
false, $max = INF ) {
177 protected function queryLinks( $table, $startId, $endId, $max, $select =
'all' ) {
179 $fromField = $this->
getPrefix( $table ) .
'_from';
181 if ( !$startId && !$endId && is_infinite( $max )
182 && isset( $this->fullResultCache[$table] )
184 wfDebug( __METHOD__ .
": got results from cache\n" );
185 $res = $this->fullResultCache[$table];
187 wfDebug( __METHOD__ .
": got results from DB\n" );
192 $conds[] =
"$fromField >= " . intval( $startId );
195 $conds[] =
"$fromField <= " . intval( $endId );
197 $options = [
'ORDER BY' => $fromField ];
198 if ( is_finite( $max ) && $max > 0 ) {
202 if ( $select ===
'ids' ) {
206 [ $this->
getPrefix( $table ) .
'_from AS page_id' ],
207 array_filter( $conds,
function ( $clause ) {
208 return !preg_match(
'/(\b|=)page_id(\b|=)/', $clause );
217 [
'page_namespace',
'page_title',
'page_id' ],
220 array_merge( [
'STRAIGHT_JOIN' ],
$options )
224 if ( $select ===
'all' && !$startId && !$endId &&
$res->numRows() < $max ) {
226 $this->fullResultCache[$table] =
$res;
228 wfDebug( __METHOD__ .
": results from DB were uncacheable\n" );
244 'imagelinks' =>
'il',
245 'categorylinks' =>
'cl',
246 'templatelinks' =>
'tl',
250 if ( isset( $prefixes[$table] ) ) {
251 return $prefixes[$table];
254 Hooks::run(
'BacklinkCacheGetPrefix', [ $table, &$prefix ] );
258 throw new MWException(
"Invalid table \"$table\" in " . __CLASS__ );
275 case 'templatelinks':
277 "{$prefix}_namespace" => $this->
title->getNamespace(),
278 "{$prefix}_title" => $this->
title->getDBkey(),
279 "page_id={$prefix}_from"
284 "{$prefix}_namespace" => $this->
title->getNamespace(),
285 "{$prefix}_title" => $this->
title->getDBkey(),
286 $this->
getDB()->makeList( [
287 "{$prefix}_interwiki" =>
'',
288 "{$prefix}_interwiki IS NULL",
290 "page_id={$prefix}_from"
294 case 'categorylinks':
296 "{$prefix}_to" => $this->
title->getDBkey(),
297 "page_id={$prefix}_from"
302 Hooks::run(
'BacklinkCacheGetConditions', [ $table, $this->
title, &$conds ] );
304 throw new MWException(
"Invalid table \"$table\" in " . __CLASS__ );
327 global $wgUpdateRowsPerJob;
331 if ( isset( $this->partitionCache[$table] ) ) {
332 $entry = reset( $this->partitionCache[$table] );
334 return min( $max, $entry[
'numRows'] );
338 if ( isset( $this->fullResultCache[$table] ) ) {
339 return min( $max, $this->fullResultCache[$table]->numRows() );
342 $memcKey =
$cache->makeKey(
344 md5( $this->
title->getPrefixedDBkey() ),
349 $count =
$cache->get( $memcKey );
351 return min( $max, $count );
355 if ( is_infinite( $max ) ) {
358 $this->
partition( $table, $wgUpdateRowsPerJob );
359 return $this->partitionCache[$table][$wgUpdateRowsPerJob][
'numRows'];
362 $count = $this->
getLinks( $table,
false,
false, $max )->count();
363 if ( $count < $max ) {
364 $cache->set( $memcKey, $count, self::CACHE_EXPIRY );
368 return min( $max, $count );
382 if ( isset( $this->partitionCache[$table][$batchSize] ) ) {
383 wfDebug( __METHOD__ .
": got from partition cache\n" );
385 return $this->partitionCache[$table][$batchSize][
'batches'];
389 $this->partitionCache[$table][$batchSize] =
false;
390 $cacheEntry =& $this->partitionCache[$table][$batchSize];
393 if ( isset( $this->fullResultCache[$table] ) ) {
394 $cacheEntry = $this->
partitionResult( $this->fullResultCache[$table], $batchSize );
395 wfDebug( __METHOD__ .
": got from full result cache\n" );
397 return $cacheEntry[
'batches'];
400 $memcKey =
$cache->makeKey(
402 md5( $this->
title->getPrefixedDBkey() ),
408 $memcValue =
$cache->get( $memcKey );
409 if ( is_array( $memcValue ) ) {
410 $cacheEntry = $memcValue;
411 wfDebug( __METHOD__ .
": got from memcached $memcKey\n" );
413 return $cacheEntry[
'batches'];
417 $cacheEntry = [
'numRows' => 0,
'batches' => [] ];
420 $selectSize = max( $batchSize, 200000 - ( 200000 % $batchSize ) );
423 $res = $this->
queryLinks( $table, $start,
false, $selectSize,
'ids' );
426 $cacheEntry[
'numRows'] += $partitions[
'numRows'];
427 $cacheEntry[
'batches'] = array_merge( $cacheEntry[
'batches'], $partitions[
'batches'] );
428 if (
count( $partitions[
'batches'] ) ) {
429 list( , $lEnd ) = end( $partitions[
'batches'] );
432 }
while ( $partitions[
'numRows'] >= $selectSize );
434 if (
count( $cacheEntry[
'batches'] ) ) {
435 $cacheEntry[
'batches'][0][0] =
false;
436 $cacheEntry[
'batches'][
count( $cacheEntry[
'batches'] ) - 1][1] =
false;
440 $cache->set( $memcKey, $cacheEntry, self::CACHE_EXPIRY );
443 $memcKey =
$cache->makeKey(
445 md5( $this->
title->getPrefixedDBkey() ),
448 $cache->set( $memcKey, $cacheEntry[
'numRows'], self::CACHE_EXPIRY );
450 wfDebug( __METHOD__ .
": got from database\n" );
452 return $cacheEntry[
'batches'];
465 $numRows =
$res->numRows();
466 $numBatches = ceil( $numRows / $batchSize );
468 for ( $i = 0; $i < $numBatches; $i++ ) {
469 if ( $i == 0 && $isComplete ) {
472 $rowNum = $i * $batchSize;
473 $res->seek( $rowNum );
474 $row =
$res->fetchObject();
475 $start = (int)$row->page_id;
478 if ( $i == ( $numBatches - 1 ) && $isComplete ) {
481 $rowNum = min( $numRows - 1, ( $i + 1 ) * $batchSize - 1 );
482 $res->seek( $rowNum );
483 $row =
$res->fetchObject();
484 $end = (int)$row->page_id;
488 if ( $start && $end && $start > $end ) {
489 throw new MWException( __METHOD__ .
': Internal error: query result out of order' );
492 $batches[] = [ $start, $end ];
495 return [
'numRows' => $numRows,
'batches' => $batches ];
509 $resSets[] =
$dbr->select(
510 [
'templatelinks',
'page_restrictions',
'page' ],
511 [
'page_namespace',
'page_title',
'page_id' ],
513 'tl_namespace' => $this->
title->getNamespace(),
514 'tl_title' => $this->
title->getDBkey(),
523 $resSets[] =
$dbr->select(
524 [
'imagelinks',
'page_restrictions',
'page' ],
525 [
'page_namespace',
'page_title',
'page_id' ],
527 'il_to' => $this->
title->getDBkey(),
539 foreach ( $resSets
as $res ) {
540 foreach (
$res as $row ) {
541 $mergedRes[$row->page_id] = $row;