102 $this->wanCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
114 if ( !self::$instance || !self::$instance->title->equals(
$title ) ) {
115 self::$instance =
new self(
$title );
128 return [
'partitionCache',
'fullResultCache',
'title' ];
135 $this->partitionCache = [];
136 $this->fullResultCache = [];
137 $this->wanCache->touchCheckKey( $this->
makeCheckKey() );
156 if ( $this->db ===
null ) {
171 public function getLinks( $table, $startId =
false, $endId =
false, $max = INF ) {
184 protected function queryLinks( $table, $startId, $endId, $max, $select =
'all' ) {
185 $fromField = $this->
getPrefix( $table ) .
'_from';
187 if ( !$startId && !$endId && is_infinite( $max )
188 && isset( $this->fullResultCache[$table] )
190 wfDebug( __METHOD__ .
": got results from cache\n" );
191 $res = $this->fullResultCache[$table];
193 wfDebug( __METHOD__ .
": got results from DB\n" );
198 $conds[] =
"$fromField >= " . intval( $startId );
201 $conds[] =
"$fromField <= " . intval( $endId );
203 $options = [
'ORDER BY' => $fromField ];
204 if ( is_finite( $max ) && $max > 0 ) {
205 $options[
'LIMIT'] = $max;
208 if ( $select ===
'ids' ) {
212 [ $this->
getPrefix( $table ) .
'_from AS page_id' ],
213 array_filter( $conds,
function ( $clause ) {
214 return !preg_match(
'/(\b|=)page_id(\b|=)/', $clause );
223 [
'page_namespace',
'page_title',
'page_id' ],
226 array_merge( [
'STRAIGHT_JOIN' ], $options )
230 if ( $select ===
'all' && !$startId && !$endId &&
$res->numRows() < $max ) {
232 $this->fullResultCache[$table] =
$res;
234 wfDebug( __METHOD__ .
": results from DB were uncacheable\n" );
250 'imagelinks' =>
'il',
251 'categorylinks' =>
'cl',
252 'templatelinks' =>
'tl',
256 if ( isset( $prefixes[$table] ) ) {
257 return $prefixes[$table];
260 Hooks::run(
'BacklinkCacheGetPrefix', [ $table, &$prefix ] );
264 throw new MWException(
"Invalid table \"$table\" in " . __CLASS__ );
281 case 'templatelinks':
283 "{$prefix}_namespace" => $this->title->getNamespace(),
284 "{$prefix}_title" => $this->title->getDBkey(),
285 "page_id={$prefix}_from"
290 "{$prefix}_namespace" => $this->title->getNamespace(),
291 "{$prefix}_title" => $this->title->getDBkey(),
292 $this->
getDB()->makeList( [
293 "{$prefix}_interwiki" =>
'',
294 "{$prefix}_interwiki IS NULL",
296 "page_id={$prefix}_from"
300 case 'categorylinks':
302 "{$prefix}_to" => $this->title->getDBkey(),
303 "page_id={$prefix}_from"
308 Hooks::run(
'BacklinkCacheGetConditions', [ $table, $this->title, &$conds ] );
310 throw new MWException(
"Invalid table \"$table\" in " . __CLASS__ );
336 if ( isset( $this->partitionCache[$table] ) ) {
337 $entry = reset( $this->partitionCache[$table] );
339 return min( $max, $entry[
'numRows'] );
343 if ( isset( $this->fullResultCache[$table] ) ) {
344 return min( $max, $this->fullResultCache[$table]->numRows() );
347 $memcKey = $this->wanCache->makeKey(
349 md5( $this->title->getPrefixedDBkey() ),
355 $count = $this->wanCache->get(
362 if ( $count && ( $curTTL > 0 ) ) {
363 return min( $max, $count );
367 if ( is_infinite( $max ) ) {
374 $count = $this->
getLinks( $table,
false,
false, $max )->count();
375 if ( $count < $max ) {
376 $this->wanCache->set( $memcKey, $count, self::CACHE_EXPIRY );
380 return min( $max, $count );
394 if ( isset( $this->partitionCache[$table][$batchSize] ) ) {
395 wfDebug( __METHOD__ .
": got from partition cache\n" );
397 return $this->partitionCache[$table][$batchSize][
'batches'];
400 $this->partitionCache[$table][$batchSize] =
false;
401 $cacheEntry =& $this->partitionCache[$table][$batchSize];
404 if ( isset( $this->fullResultCache[$table] ) ) {
405 $cacheEntry = $this->
partitionResult( $this->fullResultCache[$table], $batchSize );
406 wfDebug( __METHOD__ .
": got from full result cache\n" );
408 return $cacheEntry[
'batches'];
411 $memcKey = $this->wanCache->makeKey(
413 md5( $this->title->getPrefixedDBkey() ),
420 $memcValue = $this->wanCache->get(
427 if ( is_array( $memcValue ) && ( $curTTL > 0 ) ) {
428 $cacheEntry = $memcValue;
429 wfDebug( __METHOD__ .
": got from memcached $memcKey\n" );
431 return $cacheEntry[
'batches'];
435 $cacheEntry = [
'numRows' => 0,
'batches' => [] ];
438 $selectSize = max( $batchSize, 200000 - ( 200000 % $batchSize ) );
441 $res = $this->
queryLinks( $table, $start,
false, $selectSize,
'ids' );
444 $cacheEntry[
'numRows'] += $partitions[
'numRows'];
445 $cacheEntry[
'batches'] = array_merge( $cacheEntry[
'batches'], $partitions[
'batches'] );
446 if ( count( $partitions[
'batches'] ) ) {
447 list( , $lEnd ) = end( $partitions[
'batches'] );
450 }
while ( $partitions[
'numRows'] >= $selectSize );
452 if ( count( $cacheEntry[
'batches'] ) ) {
453 $cacheEntry[
'batches'][0][0] =
false;
454 $cacheEntry[
'batches'][count( $cacheEntry[
'batches'] ) - 1][1] =
false;
458 $this->wanCache->set( $memcKey, $cacheEntry, self::CACHE_EXPIRY );
461 $memcKey = $this->wanCache->makeKey(
463 md5( $this->title->getPrefixedDBkey() ),
466 $this->wanCache->set( $memcKey, $cacheEntry[
'numRows'], self::CACHE_EXPIRY );
468 wfDebug( __METHOD__ .
": got from database\n" );
470 return $cacheEntry[
'batches'];
483 $numRows =
$res->numRows();
484 $numBatches = ceil( $numRows / $batchSize );
486 for ( $i = 0; $i < $numBatches; $i++ ) {
487 if ( $i == 0 && $isComplete ) {
490 $rowNum = $i * $batchSize;
491 $res->seek( $rowNum );
492 $row =
$res->fetchObject();
493 $start = (int)$row->page_id;
496 if ( $i == ( $numBatches - 1 ) && $isComplete ) {
499 $rowNum = min( $numRows - 1, ( $i + 1 ) * $batchSize - 1 );
500 $res->seek( $rowNum );
501 $row =
$res->fetchObject();
502 $end = (int)$row->page_id;
506 if ( $start && $end && $start > $end ) {
507 throw new MWException( __METHOD__ .
': Internal error: query result out of order' );
510 $batches[] = [ $start, $end ];
513 return [
'numRows' => $numRows,
'batches' => $batches ];
527 $resSets[] =
$dbr->select(
528 [
'templatelinks',
'page_restrictions',
'page' ],
529 [
'page_namespace',
'page_title',
'page_id' ],
531 'tl_namespace' => $this->title->getNamespace(),
532 'tl_title' => $this->title->getDBkey(),
540 if ( $this->title->getNamespace() ==
NS_FILE ) {
541 $resSets[] =
$dbr->select(
542 [
'imagelinks',
'page_restrictions',
'page' ],
543 [
'page_namespace',
'page_title',
'page_id' ],
545 'il_to' => $this->title->getDBkey(),
557 foreach ( $resSets as
$res ) {
558 foreach (
$res as $row ) {
559 $mergedRes[$row->page_id] = $row;
573 return $this->wanCache->makeKey(
575 md5( $this->title->getPrefixedDBkey() )