Merge "Make LinkCache behavior more consistent"
This commit is contained in:
commit
c93be24da9
3 changed files with 566 additions and 66 deletions
|
|
@ -676,12 +676,14 @@ return [
|
|||
$dbLoadBalancer = $services->isServiceDisabled( 'DBLoadBalancer' )
|
||||
? null
|
||||
: $services->getDBLoadBalancer();
|
||||
return new LinkCache(
|
||||
$linkCache = new LinkCache(
|
||||
$services->getTitleFormatter(),
|
||||
$services->getMainWANObjectCache(),
|
||||
$services->getNamespaceInfo(),
|
||||
$dbLoadBalancer
|
||||
);
|
||||
$linkCache->setLogger( LoggerFactory::getInstance( 'LinkCache' ) );
|
||||
return $linkCache;
|
||||
},
|
||||
|
||||
'LinkRenderer' => static function ( MediaWikiServices $services ) : LinkRenderer {
|
||||
|
|
|
|||
281
includes/cache/LinkCache.php
vendored
281
includes/cache/LinkCache.php
vendored
|
|
@ -23,6 +23,11 @@
|
|||
|
||||
use MediaWiki\Linker\LinkTarget;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use MediaWiki\Page\PageIdentity;
|
||||
use MediaWiki\Page\PageReference;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Wikimedia\Rdbms\Database;
|
||||
use Wikimedia\Rdbms\IDatabase;
|
||||
use Wikimedia\Rdbms\ILoadBalancer;
|
||||
|
|
@ -32,7 +37,7 @@ use Wikimedia\Rdbms\ILoadBalancer;
|
|||
*
|
||||
* @ingroup Cache
|
||||
*/
|
||||
class LinkCache {
|
||||
class LinkCache implements LoggerAwareInterface {
|
||||
/** @var MapCacheLRU */
|
||||
private $goodLinks;
|
||||
/** @var MapCacheLRU */
|
||||
|
|
@ -52,6 +57,9 @@ class LinkCache {
|
|||
/** @var ILoadBalancer|null */
|
||||
private $loadBalancer;
|
||||
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* How many Titles to store. There are two caches, so the amount actually
|
||||
* stored in memory can be up to twice this.
|
||||
|
|
@ -80,6 +88,14 @@ class LinkCache {
|
|||
$this->titleFormatter = $titleFormatter;
|
||||
$this->nsInfo = $nsInfo;
|
||||
$this->loadBalancer = $loadBalancer;
|
||||
$this->logger = new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function setLogger( LoggerInterface $logger ) {
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -110,11 +126,71 @@ class LinkCache {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $title Prefixed DB key
|
||||
* @return int Page ID or zero
|
||||
* @param LinkTarget|PageReference|string $page
|
||||
* @param bool $passThrough Return $page if $page is a string
|
||||
*
|
||||
* @return ?string the cache key
|
||||
*/
|
||||
public function getGoodLinkID( $title ) {
|
||||
$info = $this->goodLinks->get( $title );
|
||||
private function getCacheKey( $page, $passThrough = false ) {
|
||||
if ( is_string( $page ) ) {
|
||||
if ( $passThrough ) {
|
||||
return $page;
|
||||
} else {
|
||||
throw new InvalidArgumentException( 'They key may not be given as a string here' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $page instanceof PageReference && $page->getWikiId() !== PageReference::LOCAL ) {
|
||||
// No cross-wiki support yet. Perhaps LinkCache can become wiki-aware in the future.
|
||||
$this->logger->info(
|
||||
'cross-wiki page reference',
|
||||
[ 'page-wiki' => $page->getWikiId(), 'page-reference' => $page ]
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( $page instanceof PageIdentity && !$page->canExist() ) {
|
||||
// Non-proper page, perhaps a special page or interwiki link or relative section link.
|
||||
$this->logger->warning(
|
||||
'non-proper page reference',
|
||||
[ 'page-reference' => $page ]
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( $page instanceof LinkTarget
|
||||
&& ( $page->isExternal() || $page->getText() === '' || $page->getNamespace() < 0 )
|
||||
) {
|
||||
// Interwiki link or relative section link. These do not have a page ID, so they
|
||||
// can neither be "good" nor "bad" in the sense of this class.
|
||||
$this->logger->warning(
|
||||
'link to non-proper page',
|
||||
[ 'page-link' => $page ]
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->titleFormatter->getPrefixedDBkey( $page );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the given page, if information about this page has been cached.
|
||||
*
|
||||
* @param LinkTarget|PageReference|string $page The page to get the ID for,
|
||||
* as an object or a prefixed DB key.
|
||||
* In MediaWiki 1.36 and earlier, only a string was accepted.
|
||||
* @return int Page ID, or zero if the page was not cached or does not exist or is not a
|
||||
* proper page (e.g. a special page or an interwiki link).
|
||||
*/
|
||||
public function getGoodLinkID( $page ) {
|
||||
$key = $this->getCacheKey( $page, true );
|
||||
|
||||
if ( $key === null ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$info = $this->goodLinks->get( $key );
|
||||
|
||||
if ( !$info ) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -122,15 +198,22 @@ class LinkCache {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get a field of a title object from cache.
|
||||
* Get a field of a page from the cache.
|
||||
*
|
||||
* If this link is not a cached good title, it will return NULL.
|
||||
* @param LinkTarget $target
|
||||
* @param string $field ('length','redirect','revision','model')
|
||||
* @return string|int|null
|
||||
* @param LinkTarget|PageReference $page The page to get cached info for.
|
||||
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
||||
* @param string $field ( 'id', 'length', 'redirect', 'revision', 'model', 'lang', 'restrictions' )
|
||||
* @return string|int|null The field value, or null if the page was not cached or does not exist
|
||||
* or is not a proper page (e.g. a special page or interwiki link).
|
||||
*/
|
||||
public function getGoodLinkFieldObj( LinkTarget $target, $field ) {
|
||||
$dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
|
||||
$info = $this->goodLinks->get( $dbkey );
|
||||
public function getGoodLinkFieldObj( $page, string $field ) {
|
||||
$key = $this->getCacheKey( $page );
|
||||
if ( $key === null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$info = $this->goodLinks->get( $key );
|
||||
if ( !$info ) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -138,30 +221,43 @@ class LinkCache {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $title Prefixed DB key
|
||||
* @return bool
|
||||
* Returns true if the fact that this page does not exist had been added to the cache.
|
||||
*
|
||||
* @param LinkTarget|PageReference|string $page The page to get cached info for,
|
||||
* as an object or a prefixed DB key.
|
||||
* In MediaWiki 1.36 and earlier, only a string was accepted.
|
||||
* @return bool True if the page is known to not exist.
|
||||
*/
|
||||
public function isBadLink( $title ) {
|
||||
// Use get() to ensure it records as used for LRU.
|
||||
return $this->badLinks->has( $title );
|
||||
public function isBadLink( $page ) {
|
||||
$key = $this->getCacheKey( $page, true );
|
||||
|
||||
return $key !== null && $this->badLinks->has( $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a link for the title to the link cache
|
||||
* Add information about an existing page to the cache.
|
||||
*
|
||||
* @see addGoodLinkObjFromRow()
|
||||
*
|
||||
* @param int $id Page's ID
|
||||
* @param LinkTarget $target
|
||||
* @param LinkTarget|PageReference $page The page to set cached info for.
|
||||
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
||||
* @param int $len Text's length
|
||||
* @param int|null $redir Whether the page is a redirect
|
||||
* @param int $revision Latest revision's ID
|
||||
* @param string|null $model Latest revision's content model ID
|
||||
* @param string|null $lang Language code of the page, if not the content language
|
||||
*/
|
||||
public function addGoodLinkObj( $id, LinkTarget $target, $len = -1, $redir = null,
|
||||
public function addGoodLinkObj( $id, $page, $len = -1, $redir = null,
|
||||
$revision = 0, $model = null, $lang = null
|
||||
) {
|
||||
$dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
|
||||
$this->goodLinks->set( $dbkey, [
|
||||
$key = $this->getCacheKey( $page );
|
||||
|
||||
if ( $key === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->goodLinks->set( $key, [
|
||||
'id' => (int)$id,
|
||||
'length' => (int)$len,
|
||||
'redirect' => (int)$redir,
|
||||
|
|
@ -170,18 +266,26 @@ class LinkCache {
|
|||
'lang' => $lang ? (string)$lang : null,
|
||||
'restrictions' => null
|
||||
] );
|
||||
$this->badLinks->clear( $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as above with better interface.
|
||||
* @since 1.19
|
||||
* @param LinkTarget $target
|
||||
* @param stdClass $row Object which has the fields page_id, page_is_redirect,
|
||||
* page_latest and page_content_model
|
||||
*
|
||||
* @param LinkTarget|PageReference $page The page to set cached info for.
|
||||
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
||||
* @param stdClass $row Object which has all fields returned by getSelectFields().
|
||||
*
|
||||
*/
|
||||
public function addGoodLinkObjFromRow( LinkTarget $target, $row ) {
|
||||
$dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
|
||||
$this->goodLinks->set( $dbkey, [
|
||||
public function addGoodLinkObjFromRow( $page, stdClass $row ) {
|
||||
$key = $this->getCacheKey( $page );
|
||||
|
||||
if ( $key === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->goodLinks->set( $key, [
|
||||
'id' => intval( $row->page_id ),
|
||||
'length' => intval( $row->page_len ),
|
||||
'redirect' => intval( $row->page_is_redirect ),
|
||||
|
|
@ -196,32 +300,45 @@ class LinkCache {
|
|||
? strval( $row->page_restrictions )
|
||||
: null
|
||||
] );
|
||||
$this->badLinks->clear( $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTarget $target
|
||||
* @param LinkTarget|PageReference $page The page to set cached info for.
|
||||
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
||||
*/
|
||||
public function addBadLinkObj( LinkTarget $target ) {
|
||||
$dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
|
||||
if ( !$this->isBadLink( $dbkey ) ) {
|
||||
$this->badLinks->set( $dbkey, 1 );
|
||||
public function addBadLinkObj( $page ) {
|
||||
$key = $this->getCacheKey( $page );
|
||||
if ( $key !== null && !$this->isBadLink( $key ) ) {
|
||||
$this->badLinks->set( $key, 1 );
|
||||
$this->goodLinks->clear( $key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title Prefixed DB key
|
||||
* @param LinkTarget|PageReference|string $page The page to clear cached info for,
|
||||
* as an object or a prefixed DB key.
|
||||
* In MediaWiki 1.36 and earlier, only a string was accepted.
|
||||
*/
|
||||
public function clearBadLink( $title ) {
|
||||
$this->badLinks->clear( $title );
|
||||
public function clearBadLink( $page ) {
|
||||
$key = $this->getCacheKey( $page, true );
|
||||
|
||||
if ( $key !== null ) {
|
||||
$this->badLinks->clear( $key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTarget $target
|
||||
* @param LinkTarget|PageReference $page The page to clear cached info for.
|
||||
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
||||
*/
|
||||
public function clearLink( LinkTarget $target ) {
|
||||
$dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
|
||||
$this->badLinks->clear( $dbkey );
|
||||
$this->goodLinks->clear( $dbkey );
|
||||
public function clearLink( $page ) {
|
||||
$key = $this->getCacheKey( $page );
|
||||
|
||||
if ( $key !== null ) {
|
||||
$this->badLinks->clear( $key );
|
||||
$this->goodLinks->clear( $key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -250,25 +367,37 @@ class LinkCache {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add a title to the link cache, return the page_id or zero if non-existent
|
||||
* Add a title to the link cache, return the page_id or zero if non-existent.
|
||||
* This causes the link to be looked up in the database if it is not yet cached.
|
||||
*
|
||||
* @param LinkTarget|PageReference $page The page to load.
|
||||
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
||||
*
|
||||
* @param LinkTarget $nt LinkTarget object to add
|
||||
* @return int Page ID or zero
|
||||
*/
|
||||
public function addLinkObj( LinkTarget $nt ) {
|
||||
$key = $this->titleFormatter->getPrefixedDBkey( $nt );
|
||||
if ( $this->isBadLink( $key ) || $nt->isExternal() || $nt->getNamespace() < 0 ) {
|
||||
return 0;
|
||||
}
|
||||
$id = $this->getGoodLinkID( $key );
|
||||
if ( $id != 0 ) {
|
||||
return $id;
|
||||
public function addLinkObj( $page ) {
|
||||
if ( $page instanceof LinkTarget ) {
|
||||
$nt = $page;
|
||||
} else {
|
||||
$nt = TitleValue::castPageToLinkTarget( $page );
|
||||
}
|
||||
|
||||
if ( $key === '' ) {
|
||||
$key = $this->getCacheKey( $nt );
|
||||
if ( $key === null ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( !$this->mForUpdate ) {
|
||||
$id = $this->getGoodLinkID( $key );
|
||||
if ( $id != 0 ) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
if ( $this->isBadLink( $key ) ) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Only query database, when load balancer is provided by service wiring
|
||||
// This maybe not happen when running as part of the installer
|
||||
if ( $this->loadBalancer === null ) {
|
||||
|
|
@ -312,20 +441,32 @@ class LinkCache {
|
|||
|
||||
/**
|
||||
* @param WANObjectCache $cache
|
||||
* @param LinkTarget $t
|
||||
* @param LinkTarget|Pagereference $page
|
||||
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
||||
* @return string[]
|
||||
* @since 1.28
|
||||
*/
|
||||
public function getMutableCacheKeys( WANObjectCache $cache, LinkTarget $t ) {
|
||||
if ( $this->isCacheable( $t ) ) {
|
||||
return [ $cache->makeKey( 'page', $t->getNamespace(), sha1( $t->getDBkey() ) ) ];
|
||||
public function getMutableCacheKeys( WANObjectCache $cache, $page ) {
|
||||
$key = $this->getCacheKey( $page );
|
||||
// if no key can be derived, the page isn't cacheable
|
||||
if ( $key === null ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( $this->isCacheable( $page ) ) {
|
||||
return [ $cache->makeKey( 'page', $page->getNamespace(), sha1( $page->getDBkey() ) ) ];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function isCacheable( LinkTarget $title ) {
|
||||
$ns = $title->getNamespace();
|
||||
/**
|
||||
* @param LinkTarget|PageReference $page
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isCacheable( $page ) {
|
||||
$ns = $page->getNamespace();
|
||||
if ( in_array( $ns, [ NS_TEMPLATE, NS_FILE, NS_CATEGORY, NS_MEDIAWIKI ] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -337,16 +478,22 @@ class LinkCache {
|
|||
return ( $ns >= 100 && $this->nsInfo->isSubject( $ns ) );
|
||||
}
|
||||
|
||||
private function fetchPageRow( IDatabase $db, LinkTarget $nt ) {
|
||||
/**
|
||||
* @param IDatabase $db
|
||||
* @param LinkTarget|PageReference $page
|
||||
*
|
||||
* @return stdClass|false
|
||||
*/
|
||||
private function fetchPageRow( IDatabase $db, $page ) {
|
||||
$fields = self::getSelectFields();
|
||||
if ( $this->isCacheable( $nt ) ) {
|
||||
if ( $this->isCacheable( $page ) ) {
|
||||
$fields[] = 'page_touched';
|
||||
}
|
||||
|
||||
return $db->selectRow(
|
||||
'page',
|
||||
$fields,
|
||||
[ 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ],
|
||||
[ 'page_namespace' => $page->getNamespace(), 'page_title' => $page->getDBkey() ],
|
||||
__METHOD__
|
||||
);
|
||||
}
|
||||
|
|
@ -354,16 +501,19 @@ class LinkCache {
|
|||
/**
|
||||
* Purge the link cache for a title
|
||||
*
|
||||
* @param LinkTarget $title
|
||||
* @param LinkTarget|PageReference $page
|
||||
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
||||
* @since 1.28
|
||||
*/
|
||||
public function invalidateTitle( LinkTarget $title ) {
|
||||
if ( $this->isCacheable( $title ) ) {
|
||||
public function invalidateTitle( $page ) {
|
||||
if ( $this->isCacheable( $page ) ) {
|
||||
$cache = $this->wanCache;
|
||||
$cache->delete(
|
||||
$cache->makeKey( 'page', $title->getNamespace(), sha1( $title->getDBkey() ) )
|
||||
$cache->makeKey( 'page', $page->getNamespace(), sha1( $page->getDBkey() ) )
|
||||
);
|
||||
}
|
||||
|
||||
$this->clearLink( $page );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -373,4 +523,5 @@ class LinkCache {
|
|||
$this->goodLinks->clear();
|
||||
$this->badLinks->clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
347
tests/phpunit/includes/cache/LinkCacheTest.php
vendored
Normal file
347
tests/phpunit/includes/cache/LinkCacheTest.php
vendored
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Page\PageReference;
|
||||
use MediaWiki\Page\PageReferenceValue;
|
||||
|
||||
/**
|
||||
* @group Database
|
||||
* @group Cache
|
||||
* @covers LinkCache
|
||||
*/
|
||||
class LinkCacheTest extends MediaWikiIntegrationTestCase {
|
||||
|
||||
private function newLinkCache( WANObjectCache $wanCache = null ) {
|
||||
if ( !$wanCache ) {
|
||||
$wanCache = new WANObjectCache( [ 'cache' => new EmptyBagOStuff() ] );
|
||||
}
|
||||
|
||||
return new LinkCache(
|
||||
$this->getServiceContainer()->getTitleFormatter(),
|
||||
$wanCache,
|
||||
$this->getServiceContainer()->getNamespaceInfo(),
|
||||
$this->getServiceContainer()->getDBLoadBalancer()
|
||||
);
|
||||
}
|
||||
|
||||
public function providePageAndLink() {
|
||||
return [
|
||||
[ new PageReferenceValue( NS_USER, __METHOD__, PageReference::LOCAL ) ],
|
||||
[ new TitleValue( NS_USER, __METHOD__ ) ],
|
||||
];
|
||||
}
|
||||
|
||||
public function providePageAndLinkAndString() {
|
||||
return [
|
||||
[ new PageReferenceValue( NS_USER, __METHOD__, PageReference::LOCAL ) ],
|
||||
[ new TitleValue( NS_USER, __METHOD__ ) ],
|
||||
[ 'User:' . __METHOD__ ],
|
||||
];
|
||||
}
|
||||
|
||||
private function getPageRow() {
|
||||
return (object)[
|
||||
'page_id' => 8,
|
||||
'page_len' => 18,
|
||||
'page_is_redirect' => 0,
|
||||
'page_latest' => 118,
|
||||
'page_content_model' => CONTENT_MODEL_TEXT,
|
||||
'page_lang' => 'xyz',
|
||||
'page_restrictions' => 'test'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providePageAndLink
|
||||
* @covers LinkCache::addGoodLinkObjFromRow()
|
||||
* @covers LinkCache::getGoodLinkID()
|
||||
* @covers LinkCache::getGoodLinkFieldObj()
|
||||
* @covers LinkCache::clearLink()
|
||||
*/
|
||||
public function testAddGoodLinkObjFromRow( $page ) {
|
||||
$linkCache = $this->newLinkCache();
|
||||
|
||||
$row = $this->getPageRow();
|
||||
|
||||
$page = new PageReferenceValue( NS_USER, __METHOD__, PageReference::LOCAL );
|
||||
$linkCache->addBadLinkObj( $page );
|
||||
$linkCache->addGoodLinkObjFromRow( $page, $row );
|
||||
|
||||
$this->assertSame( $row->page_id, $linkCache->getGoodLinkID( $page ) );
|
||||
$this->assertFalse( $linkCache->isBadLink( $page ) );
|
||||
|
||||
$this->assertSame(
|
||||
$row->page_id,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'id' )
|
||||
);
|
||||
$this->assertSame(
|
||||
$row->page_len,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'length' )
|
||||
);
|
||||
$this->assertSame(
|
||||
$row->page_is_redirect,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'redirect' )
|
||||
);
|
||||
$this->assertSame(
|
||||
$row->page_latest,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'revision' )
|
||||
);
|
||||
$this->assertSame(
|
||||
$row->page_content_model,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'model' )
|
||||
);
|
||||
$this->assertSame(
|
||||
$row->page_lang,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'lang' )
|
||||
);
|
||||
$this->assertSame(
|
||||
$row->page_restrictions,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'restrictions' )
|
||||
);
|
||||
|
||||
$linkCache->clearBadLink( $page );
|
||||
$this->assertNotNull( $linkCache->getGoodLinkID( $page ) );
|
||||
$this->assertNotNull( $linkCache->getGoodLinkFieldObj( $page, 'length' ) );
|
||||
|
||||
$linkCache->clearLink( $page );
|
||||
$this->assertSame( 0, $linkCache->getGoodLinkID( $page ) );
|
||||
$this->assertNull( $linkCache->getGoodLinkFieldObj( $page, 'length' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providePageAndLink
|
||||
* @covers LinkCache::addGoodLinkObj()
|
||||
* @covers LinkCache::getGoodLinkID()
|
||||
* @covers LinkCache::getGoodLinkFieldObj()
|
||||
*/
|
||||
public function testAddGoodLinkObjWithAllParameters( $page ) {
|
||||
$linkCache = $this->newLinkCache();
|
||||
|
||||
$linkCache->addGoodLinkObj(
|
||||
8,
|
||||
$page,
|
||||
18,
|
||||
0,
|
||||
118,
|
||||
CONTENT_MODEL_TEXT,
|
||||
'xyz'
|
||||
);
|
||||
|
||||
$this->assertSame( 8, $linkCache->getGoodLinkID( $page ) );
|
||||
$this->assertSame( 8, $linkCache->getGoodLinkFieldObj( $page, 'id' ) );
|
||||
|
||||
$this->assertSame(
|
||||
18,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'length' )
|
||||
);
|
||||
$this->assertSame(
|
||||
0,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'redirect' )
|
||||
);
|
||||
$this->assertSame(
|
||||
118,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'revision' )
|
||||
);
|
||||
$this->assertSame(
|
||||
CONTENT_MODEL_TEXT,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'model' )
|
||||
);
|
||||
$this->assertSame(
|
||||
'xyz',
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'lang' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers LinkCache::addGoodLinkObj()
|
||||
* @covers LinkCache::getGoodLinkID()
|
||||
* @covers LinkCache::getGoodLinkFieldObj()
|
||||
*/
|
||||
public function testAddGoodLinkObjWithMinimalParameters() {
|
||||
$linkCache = $this->newLinkCache();
|
||||
|
||||
$page = new PageReferenceValue( NS_USER, __METHOD__, PageReference::LOCAL );
|
||||
$linkCache->addGoodLinkObj(
|
||||
8,
|
||||
$page
|
||||
);
|
||||
|
||||
$this->assertSame( 8, $linkCache->getGoodLinkID( $page ) );
|
||||
$this->assertSame( 8, $linkCache->getGoodLinkFieldObj( $page, 'id' ) );
|
||||
|
||||
$this->assertSame(
|
||||
-1,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'length' )
|
||||
);
|
||||
$this->assertSame(
|
||||
0,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'redirect' )
|
||||
);
|
||||
$this->assertSame(
|
||||
0,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'revision' )
|
||||
);
|
||||
$this->assertSame(
|
||||
null,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'model' )
|
||||
);
|
||||
$this->assertSame(
|
||||
null,
|
||||
$linkCache->getGoodLinkFieldObj( $page, 'lang' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers LinkCache::addGoodLinkObj()
|
||||
*/
|
||||
public function testAddGoodLinkObjWithInterwikiLink() {
|
||||
$linkCache = $this->newLinkCache();
|
||||
|
||||
$page = new TitleValue( NS_USER, __METHOD__, '', 'acme' );
|
||||
$linkCache->addGoodLinkObj( 8, $page );
|
||||
|
||||
$this->assertSame( 0, $linkCache->getGoodLinkID( $page ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providePageAndLink
|
||||
* @covers LinkCache::addBadLinkObj()
|
||||
* @covers LinkCache::isBadLink()
|
||||
* @covers LinkCache::clearLink()
|
||||
*/
|
||||
public function testAddBadLinkObj( $key ) {
|
||||
$linkCache = $this->newLinkCache();
|
||||
$this->assertFalse( $linkCache->isBadLink( $key ) );
|
||||
|
||||
$linkCache->addGoodLinkObj( 17, $key );
|
||||
|
||||
$linkCache->addBadLinkObj( $key );
|
||||
$this->assertTrue( $linkCache->isBadLink( $key ) );
|
||||
$this->assertSame( 0, $linkCache->getGoodLinkID( $key ) );
|
||||
|
||||
$linkCache->clearLink( $key );
|
||||
$this->assertFalse( $linkCache->isBadLink( $key ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers LinkCache::addBadLinkObj()
|
||||
*/
|
||||
public function testAddBadLinkObjWithInterwikiLink() {
|
||||
$linkCache = $this->newLinkCache();
|
||||
|
||||
$page = new TitleValue( NS_USER, __METHOD__, '', 'acme' );
|
||||
$linkCache->addBadLinkObj( $page );
|
||||
|
||||
$this->assertFalse( $linkCache->isBadLink( $page ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers LinkCache::addLinkObj()
|
||||
* @covers LinkCache::getGoodLinkFieldObj
|
||||
*/
|
||||
public function testAddLinkObj() {
|
||||
$existing = $this->getExistingTestPage();
|
||||
$missing = $this->getNonexistingTestPage();
|
||||
|
||||
$linkCache = $this->newLinkCache();
|
||||
|
||||
$linkCache->addLinkObj( $existing );
|
||||
$linkCache->addLinkObj( $missing );
|
||||
|
||||
$this->assertTrue( $linkCache->isBadLink( $missing ) );
|
||||
$this->assertFalse( $linkCache->isBadLink( $existing ) );
|
||||
|
||||
$this->assertSame( $existing->getId(), $linkCache->getGoodLinkID( $existing ) );
|
||||
|
||||
// Make sure nothing explodes when getting a field from a non-existing entry
|
||||
$this->assertNull( $linkCache->getGoodLinkFieldObj( $missing, 'length' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers LinkCache::addLinkObj()
|
||||
*/
|
||||
public function testAddLinkObjUsesCachedInfo() {
|
||||
$existing = $this->getExistingTestPage();
|
||||
$missing = $this->getNonexistingTestPage();
|
||||
|
||||
$row = $this->getPageRow();
|
||||
|
||||
$linkCache = $this->newLinkCache();
|
||||
|
||||
// pretend the existing page is missing, and the missing page exists
|
||||
$linkCache->addGoodLinkObjFromRow( $missing, $row );
|
||||
$linkCache->addBadLinkObj( $existing );
|
||||
|
||||
// the LinkCache should use the cached info and not look into the database
|
||||
$this->assertSame( (int)$row->page_id, $linkCache->addLinkObj( $missing ) );
|
||||
$this->assertSame( 0, $linkCache->addLinkObj( $existing ) );
|
||||
|
||||
// now set the "for update" flag and try again
|
||||
$linkCache->forUpdate( true );
|
||||
$this->assertSame( 0, $linkCache->addLinkObj( $missing ) );
|
||||
$this->assertSame( $existing->getId(), $linkCache->addLinkObj( $existing ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers LinkCache::addLinkObj()
|
||||
* @covers LinkCache::getMutableCacheKeys()
|
||||
*/
|
||||
public function testAddLinkObjUsesWANCache() {
|
||||
// Pages in some namespaces use the WAN cache: Template, File, Category, MediaWiki
|
||||
$existing = $this->getExistingTestPage( Title::makeTitle( NS_TEMPLATE, __METHOD__ ) );
|
||||
|
||||
$fakeRow = $this->getPageRow();
|
||||
|
||||
$cache = new HashBagOStuff();
|
||||
$wanCache = new WANObjectCache( [ 'cache' => $cache ] );
|
||||
$linkCache = $this->newLinkCache( $wanCache );
|
||||
|
||||
// load the page row into the cache
|
||||
$linkCache->addLinkObj( $existing );
|
||||
|
||||
$keys = $linkCache->getMutableCacheKeys( $wanCache, $existing );
|
||||
$this->assertNotEmpty( $keys );
|
||||
|
||||
foreach ( $keys as $key ) {
|
||||
$this->assertNotFalse( $wanCache->get( $key ) );
|
||||
}
|
||||
|
||||
// replace real row data with fake, and assert that it gets used
|
||||
$wanCache->set( $key, $fakeRow );
|
||||
$linkCache->clearLink( $existing ); // clear local cache
|
||||
$this->assertSame( (int)$fakeRow->page_id, $linkCache->addLinkObj( $existing ) );
|
||||
|
||||
// set the "for update" flag and try again
|
||||
$linkCache->forUpdate( true );
|
||||
$this->assertSame( $existing->getId(), $linkCache->addLinkObj( $existing ) );
|
||||
}
|
||||
|
||||
public function testFalsyPageName() {
|
||||
$linkCache = $this->newLinkCache();
|
||||
|
||||
// The stringified value is "0", which is falsy in PHP!
|
||||
$link = new TitleValue( NS_MAIN, '0' );
|
||||
|
||||
$linkCache->addBadLinkObj( $link );
|
||||
$this->assertTrue( $linkCache->isBadLink( $link ) );
|
||||
|
||||
$row = $this->getPageRow();
|
||||
$linkCache->addGoodLinkObjFromRow( $link, $row );
|
||||
$this->assertGreaterThan( 0, $linkCache->getGoodLinkID( $link ) );
|
||||
}
|
||||
|
||||
public function testClearBadLinkWithString() {
|
||||
$linkCache = $this->newLinkCache();
|
||||
$linkCache->clearBadLink( 'Xyzzy' );
|
||||
$this->addToAssertionCount( 1 );
|
||||
}
|
||||
|
||||
public function testIsBadLinkWithString() {
|
||||
$linkCache = $this->newLinkCache();
|
||||
$this->assertFalse( $linkCache->isBadLink( 'Xyzzy' ) );
|
||||
}
|
||||
|
||||
public function testGetGoodLinkIdWithString() {
|
||||
$linkCache = $this->newLinkCache();
|
||||
$this->assertSame( 0, $linkCache->getGoodLinkID( 'Xyzzy' ) );
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue