Use PageStore in RevisionStore

This introduces PageStoreFactory as well.

Change-Id: I7abd3c6a8ea70a68e8f8e9460d3bdd8c6f45338a
This commit is contained in:
daniel 2021-03-17 23:13:35 +01:00
parent d4b380ab27
commit fe9430947d
12 changed files with 386 additions and 200 deletions

View file

@ -56,6 +56,7 @@ use MediaWiki\Page\ContentModelChangeFactory;
use MediaWiki\Page\MergeHistoryFactory;
use MediaWiki\Page\MovePageFactory;
use MediaWiki\Page\PageStore;
use MediaWiki\Page\PageStoreFactory;
use MediaWiki\Page\ParserOutputAccess;
use MediaWiki\Page\WikiPageFactory;
use MediaWiki\Parser\ParserCacheFactory;
@ -1155,10 +1156,18 @@ class MediaWikiServices extends ServiceContainer {
* @return PageStore
* @since 1.36
*/
public function getPageStore(): PageStore {
public function getPageStore() : PageStore {
return $this->getService( 'PageStore' );
}
/**
* @return PageStoreFactory
* @since 1.36
*/
public function getPageStoreFactory() : PageStoreFactory {
return $this->getService( 'PageStoreFactory' );
}
/**
* @since 1.29
* @return Parser

View file

@ -43,6 +43,7 @@ use MediaWiki\Linker\LinkTarget;
use MediaWiki\Page\LegacyArticleIdAccess;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Page\PageStore;
use MediaWiki\Permissions\Authority;
use MediaWiki\Storage\BlobAccessException;
use MediaWiki\Storage\BlobStore;
@ -63,7 +64,6 @@ use RecentChange;
use Revision;
use RuntimeException;
use StatusValue;
use stdClass;
use Title;
use TitleFactory;
use Traversable;
@ -160,9 +160,10 @@ class RevisionStore
/** @var HookRunner */
private $hookRunner;
/**
* @var TitleFactory
*/
/** @var PageStore */
private $pageStore;
/** @var TitleFactory */
private $titleFactory;
/**
@ -181,6 +182,7 @@ class RevisionStore
* @param ActorMigration $actorMigration
* @param ActorStore $actorStore
* @param IContentHandlerFactory $contentHandlerFactory
* @param PageStore $pageStore
* @param TitleFactory $titleFactory
* @param HookContainer $hookContainer
* @param false|string $wikiId Relevant wiki id or WikiAwareEntity::LOCAL for the current one
@ -199,6 +201,7 @@ class RevisionStore
ActorMigration $actorMigration,
ActorStore $actorStore,
IContentHandlerFactory $contentHandlerFactory,
PageStore $pageStore,
TitleFactory $titleFactory,
HookContainer $hookContainer,
$wikiId = WikiAwareEntity::LOCAL
@ -217,6 +220,7 @@ class RevisionStore
$this->wikiId = $wikiId;
$this->logger = new NullLogger();
$this->contentHandlerFactory = $contentHandlerFactory;
$this->pageStore = $pageStore;
$this->titleFactory = $titleFactory;
$this->hookContainer = $hookContainer;
$this->hookRunner = new HookRunner( $hookContainer );
@ -318,28 +322,12 @@ class RevisionStore
}
$canUsePageId = ( $pageId !== null && $pageId > 0 );
list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
// Loading by ID is best
if ( $canUsePageId ) {
// TODO: use PageStore once we have that, return a PageRecord! T195069
$dbr = $this->getDBConnectionRef( $dbMode );
$row = $dbr->selectRow(
'page',
[
'page_namespace',
'page_title',
'page_id',
'page_latest',
'page_is_redirect',
'page_len',
],
[ 'page_id' => $pageId ],
__METHOD__,
$dbOptions
);
if ( $row ) {
return $this->newPageFromRow( $row );
$page = $this->pageStore->getPageById( $pageId, $queryFlags );
if ( $page ) {
return $this->wrapPage( $page );
}
}
@ -347,30 +335,19 @@ class RevisionStore
$canUseRevId = ( $revId !== null && $revId > 0 );
if ( $canUseRevId ) {
// TODO: use PageStore once we have that, return a PageRecord! T195069
$dbr = $this->getDBConnectionRef( $dbMode );
$row = $dbr->selectRow(
[ 'revision', 'page' ],
[
'page_namespace',
'page_title',
'page_id',
'page_latest',
'page_is_redirect',
'page_len',
],
[ 'rev_id' => $revId ],
__METHOD__,
$dbOptions,
[ 'page' => [ 'JOIN', 'page_id=rev_page' ] ]
);
if ( $row ) {
return $this->newPageFromRow( $row );
$pageQuery = $this->pageStore->newSelectQueryBuilder( $queryFlags )
->join( 'revision', null, 'page_id=rev_page' )
->conds( [ 'rev_id' => $revId ] )
->caller( __METHOD__ );
$page = $pageQuery->fetchPageRecord();
if ( $page ) {
return $this->wrapPage( $page );
}
}
// If we still don't have a title, fallback to master if that wasn't already happening.
if ( $dbMode !== DB_MASTER ) {
if ( $queryFlags === self::READ_NORMAL ) {
$title = $this->getPage( $pageId, $revId, self::READ_LATEST );
if ( $title ) {
$this->logger->info(
@ -387,11 +364,11 @@ class RevisionStore
}
/**
* @param stdClass $row
* @param PageIdentity $page
*
* @return PageIdentity
*/
private function newPageFromRow( stdClass $row ): PageIdentity {
private function wrapPage( PageIdentity $page ): PageIdentity {
if ( $this->wikiId === WikiAwareEntity::LOCAL ) {
// NOTE: since there is still a lot of code that needs a full Title,
// and uses Title::castFromPageIdentity() to get one, it's beneficial
@ -399,14 +376,9 @@ class RevisionStore
// over and over later on.
// When there is less need to convert to Title, this special case can
// be removed.
return $this->titleFactory->newFromRow( $row );
return $this->titleFactory->castFromPageIdentity( $page );
} else {
return new PageIdentityValue(
(int)$row->page_id,
(int)$row->page_namespace,
$row->page_title,
$this->wikiId
);
return $page;
}
}
@ -1709,7 +1681,14 @@ class RevisionStore
&& isset( $row->page_namespace )
&& isset( $row->page_title )
) {
$page = $this->newPageFromRow( $row );
$page = new PageIdentityValue(
(int)$row->page_id,
(int)$row->page_namespace,
$row->page_title,
$this->wikiId
);
$page = $this->wrapPage( $page );
} else {
$pageId = (int)( $row->rev_page ?? 0 );
$revId = (int)( $row->rev_id ?? 0 );

View file

@ -29,6 +29,7 @@ use ActorMigration;
use CommentStore;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\Page\PageStoreFactory;
use MediaWiki\Storage\BlobStoreFactory;
use MediaWiki\Storage\NameTableStoreFactory;
use MediaWiki\User\ActorStoreFactory;
@ -76,6 +77,9 @@ class RevisionStoreFactory {
/** @var IContentHandlerFactory */
private $contentHandlerFactory;
/** @var PageStoreFactory */
private $pageStoreFactory;
/** @var TitleFactory */
private $titleFactory;
@ -93,6 +97,7 @@ class RevisionStoreFactory {
* @param ActorStoreFactory $actorStoreFactory
* @param LoggerInterface $logger
* @param IContentHandlerFactory $contentHandlerFactory
* @param PageStoreFactory $pageStoreFactory
* @param TitleFactory $titleFactory
* @param HookContainer $hookContainer
*/
@ -107,6 +112,7 @@ class RevisionStoreFactory {
ActorStoreFactory $actorStoreFactory,
LoggerInterface $logger,
IContentHandlerFactory $contentHandlerFactory,
PageStoreFactory $pageStoreFactory,
TitleFactory $titleFactory,
HookContainer $hookContainer
) {
@ -120,6 +126,7 @@ class RevisionStoreFactory {
$this->actorStoreFactory = $actorStoreFactory;
$this->logger = $logger;
$this->contentHandlerFactory = $contentHandlerFactory;
$this->pageStoreFactory = $pageStoreFactory;
$this->titleFactory = $titleFactory;
$this->hookContainer = $hookContainer;
}
@ -145,6 +152,7 @@ class RevisionStoreFactory {
$this->actorMigration,
$this->actorStoreFactory->getActorStore( $dbDomain ),
$this->contentHandlerFactory,
$this->pageStoreFactory->getPageStore( $dbDomain ),
$this->titleFactory,
$this->hookContainer,
$dbDomain

View file

@ -89,6 +89,7 @@ use MediaWiki\Page\MergeHistoryFactory;
use MediaWiki\Page\MovePageFactory;
use MediaWiki\Page\PageCommandFactory;
use MediaWiki\Page\PageStore;
use MediaWiki\Page\PageStoreFactory;
use MediaWiki\Page\ParserOutputAccess;
use MediaWiki\Page\WikiPageFactory;
use MediaWiki\Parser\ParserCacheFactory;
@ -955,10 +956,18 @@ return [
},
'PageStore' => static function ( MediaWikiServices $services ) : PageStore {
$options = new ServiceOptions( PageStore::CONSTRUCTOR_OPTIONS, $services->getMainConfig() );
return new PageStore(
return $services->getPageStoreFactory()->getPageStore();
},
'PageStoreFactory' => static function ( MediaWikiServices $services ) : PageStoreFactory {
$options = new ServiceOptions(
PageStoreFactory::CONSTRUCTOR_OPTIONS,
$services->getMainConfig()
);
return new PageStoreFactory(
$options,
$services->getDBLoadBalancer(),
$services->getDBLoadBalancerFactory(),
$services->getNamespaceInfo()
);
},
@ -1238,6 +1247,7 @@ return [
$services->getActorStoreFactory(),
LoggerFactory::getInstance( 'RevisionStore' ),
$services->getContentHandlerFactory(),
$services->getPageStoreFactory(),
$services->getTitleFactory(),
$services->getHookContainer()
);

View file

@ -17,6 +17,8 @@ use Wikimedia\Rdbms\SelectQueryBuilder;
/**
* @since 1.36
*
* @unstable
*/
class PageStore implements PageLookup {
@ -170,11 +172,7 @@ class PageStore implements PageLookup {
array $conds,
int $queryFlags = self::READ_NORMAL
): ?ExistingPageRecord {
[ $mode, $options ] = DBAccessObjectUtils::getDBOptions( $queryFlags );
$dbr = $this->getDBConnectionRef( $mode );
$queryBuilder = $this->newSelectQueryBuilder( $dbr )
->options( $options )
$queryBuilder = $this->newSelectQueryBuilder( $queryFlags )
->conds( $conds )
->caller( __METHOD__ );
@ -228,14 +226,22 @@ class PageStore implements PageLookup {
/**
* @unstable
*
* @param IDatabase|null $db
* @param IDatabase|int|null $dbOrFlags The database connection to use, or a READ_XXX constant
* indicating what kind of database connection to use.
*
* @return PageSelectQueryBuilder
*/
public function newSelectQueryBuilder( IDatabase $db = null ): SelectQueryBuilder {
$db = $db ?: $this->getDBConnectionRef();
public function newSelectQueryBuilder( $dbOrFlags = self::READ_NORMAL ): SelectQueryBuilder {
if ( $dbOrFlags instanceof IDatabase ) {
$db = $dbOrFlags;
$options = [];
} else {
[ $mode, $options ] = DBAccessObjectUtils::getDBOptions( $dbOrFlags );
$db = $this->getDBConnectionRef( $mode );
}
$queryBuilder = new PageSelectQueryBuilder( $db, $this );
$queryBuilder->options( $options );
return $queryBuilder;
}

View file

@ -0,0 +1,60 @@
<?php
namespace MediaWiki\Page;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\DAO\WikiAwareEntity;
use NamespaceInfo;
use Wikimedia\Rdbms\ILBFactory;
/**
* @since 1.36
*/
class PageStoreFactory {
/**
* @internal for use by service wiring
*/
public const CONSTRUCTOR_OPTIONS = PageStore::CONSTRUCTOR_OPTIONS;
/** @var ServiceOptions */
private $options;
/** @var ILBFactory */
private $dbLoadBalancerFactory;
/** @var NamespaceInfo */
private $namespaceInfo;
/**
* @param ServiceOptions $options
* @param ILBFactory $dbLoadBalancerFactory
* @param NamespaceInfo $namespaceInfo
*/
public function __construct(
ServiceOptions $options,
ILBFactory $dbLoadBalancerFactory,
NamespaceInfo $namespaceInfo
) {
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
$this->options = $options;
$this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
$this->namespaceInfo = $namespaceInfo;
}
/**
* @param string|false $wikiId
*
* @return PageStore
*/
public function getPageStore( $wikiId = WikiAwareEntity::LOCAL ): PageStore {
return new PageStore(
$this->options,
$this->dbLoadBalancerFactory->getMainLB( $wikiId ),
$this->namespaceInfo,
$wikiId
);
}
}

View file

@ -18,6 +18,7 @@ use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Revision\IncompleteRevisionException;
use MediaWiki\Revision\MutableRevisionRecord;
use MediaWiki\Revision\RevisionAccessException;
use MediaWiki\Revision\RevisionArchiveRecord;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionSlots;
@ -235,6 +236,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiIntegrationTestCase {
MediaWikiServices::getInstance()->getActorMigration(),
MediaWikiServices::getInstance()->getActorStoreFactory()->getActorStore( $dbDomain ),
MediaWikiServices::getInstance()->getContentHandlerFactory(),
MediaWikiServices::getInstance()->getPageStore(),
MediaWikiServices::getInstance()->getTitleFactory(),
MediaWikiServices::getInstance()->getHookContainer(),
$dbDomain
@ -910,6 +912,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiIntegrationTestCase {
$services->getActorMigration(),
$services->getActorStoreFactory()->getActorStore( $dbDomain ),
$services->getContentHandlerFactory(),
$services->getPageStore(),
$services->getTitleFactory(),
$services->getHookContainer(),
$dbDomain
@ -1519,7 +1522,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiIntegrationTestCase {
/**
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
* @covers \MediaWiki\Revision\RevisionStore::getPage
* @covers \MediaWiki\Revision\RevisionStore::newPageFromRow
* @covers \MediaWiki\Revision\RevisionStore::wrapPage
*/
public function testNewRevisionFromRow_noPage() {
$store = MediaWikiServices::getInstance()->getRevisionStore();
@ -1548,7 +1551,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiIntegrationTestCase {
/**
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
* @covers \MediaWiki\Revision\RevisionStore::getPage
* @covers \MediaWiki\Revision\RevisionStore::newPageFromRow
* @covers \MediaWiki\Revision\RevisionStore::wrapPage
*/
public function testNewRevisionFromRow_noPage_crossWiki() {
// Make TitleFactory always fail, since it should not be used for the cross-wiki case.
@ -3136,4 +3139,39 @@ abstract class RevisionStoreDbTestBase extends MediaWikiIntegrationTestCase {
$this->assertSame( $ip, $return->getUser()->getName() );
$this->assertNotSame( 0, $return->getUser()->getActorId() );
}
/**
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromPageId() {
$page = $this->getExistingTestPage();
$store = $this->getServiceContainer()->getRevisionStore();
$title = $store->getTitle( $page->getId(), 0, RevisionStore::READ_NORMAL );
$this->assertTrue( $page->isSamePageAs( $title ) );
}
/**
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromRevId() {
$page = $this->getExistingTestPage();
$store = $this->getServiceContainer()->getRevisionStore();
$title = $store->getTitle( 0, $page->getLatest(), RevisionStore::READ_NORMAL );
$this->assertTrue( $page->isSamePageAs( $title ) );
}
/**
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_failure() {
$store = $this->getServiceContainer()->getRevisionStore();
$this->expectException( RevisionAccessException::class );
$store->getTitle( 113349857, 897234779, RevisionStore::READ_NORMAL );
}
}

View file

@ -6,6 +6,8 @@ use ActorMigration;
use CommentStore;
use HashBagOStuff;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\Page\PageStore;
use MediaWiki\Page\PageStoreFactory;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\RevisionStoreFactory;
use MediaWiki\Revision\SlotRoleRegistry;
@ -44,6 +46,7 @@ class RevisionStoreFactoryTest extends MediaWikiIntegrationTestCase {
$this->getMockActorStoreFactory(),
new NullLogger(),
$this->getContentHandlerFactory(),
$this->getPageStoreFactory(),
$this->getTitleFactory(),
$this->createHookContainer()
);
@ -71,6 +74,7 @@ class RevisionStoreFactoryTest extends MediaWikiIntegrationTestCase {
$actorStoreFactory = $this->getMockActorStoreFactory();
$logger = new NullLogger();
$contentHandlerFactory = $this->getContentHandlerFactory();
$pageStoreFactory = $this->getPageStoreFactory();
$titleFactory = $this->getTitleFactory();
$hookContainer = $this->createHookContainer();
@ -85,6 +89,7 @@ class RevisionStoreFactoryTest extends MediaWikiIntegrationTestCase {
$actorStoreFactory,
$logger,
$contentHandlerFactory,
$pageStoreFactory,
$titleFactory,
$hookContainer
);
@ -171,6 +176,25 @@ class RevisionStoreFactoryTest extends MediaWikiIntegrationTestCase {
return $this->createMock( IContentHandlerFactory::class );
}
/**
* @return PageStore|MockObject
*/
private function getMockPageStore(): PageStore {
return $this->createMock( PageStore::class );
}
/**
* @return PageStoreFactory|MockObject
*/
private function getPageStoreFactory(): PageStoreFactory {
$mock = $this->createMock( PageStoreFactory::class );
$mock->method( 'getPageStore' )
->willReturn( $this->getMockPageStore() );
return $mock;
}
/**
* @return TitleFactory|MockObject
*/

View file

@ -2,17 +2,14 @@
namespace MediaWiki\Tests\Revision;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\RevisionAccessException;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Storage\SqlBlobStore;
use MediaWikiIntegrationTestCase;
use MWTimestamp;
use PHPUnit\Framework\MockObject\MockObject;
use WANObjectCache;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Rdbms\LoadBalancer;
use Wikimedia\Rdbms\LBFactory;
use Wikimedia\Rdbms\MaintainableDBConnRef;
/**
@ -21,39 +18,42 @@ use Wikimedia\Rdbms\MaintainableDBConnRef;
class RevisionStoreTest extends MediaWikiIntegrationTestCase {
/**
* @param LoadBalancer|null $loadBalancer
* @param SqlBlobStore|null $blobStore
* @param WANObjectCache|null $WANObjectCache
*
* @return RevisionStore
*/
private function getRevisionStore(
$loadBalancer = null,
$blobStore = null,
$WANObjectCache = null
) {
return new RevisionStore(
$loadBalancer ?: $this->getMockLoadBalancer(),
$blobStore ?: $this->getMockSqlBlobStore(),
$WANObjectCache ?: $this->getHashWANObjectCache(),
MediaWikiServices::getInstance()->getCommentStore(),
MediaWikiServices::getInstance()->getContentModelStore(),
MediaWikiServices::getInstance()->getSlotRoleStore(),
MediaWikiServices::getInstance()->getSlotRoleRegistry(),
MediaWikiServices::getInstance()->getActorMigration(),
MediaWikiServices::getInstance()->getActorStore(),
$this->getMockContentHandlerFactory(),
MediaWikiServices::getInstance()->getTitleFactory(),
MediaWikiServices::getInstance()->getHookContainer()
);
private function getRevisionStore() {
return $this->getServiceContainer()->getRevisionStore();
}
/**
* @return MockObject|LoadBalancer
* @param IDatabase $db
*
* @return MockObject|ILoadBalancer
*/
private function getMockLoadBalancer() {
return $this->getMockBuilder( LoadBalancer::class )
private function installMockLoadBalancer( IDatabase $db ) {
$lb = $this->createNoOpMock( ILoadBalancer::class, [ 'getConnectionRef', 'getLocalDomainID' ] );
$dbRef = new MaintainableDBConnRef( $lb, $db, DB_MASTER );
$lb->method( 'getConnectionRef' )->willReturn( $dbRef );
$lb->method( 'getLocalDomainID' )->willReturn( 'fake' );
$lbf = $this->createNoOpMock( LBFactory::class, [ 'getMainLB', 'getLocalDomainID' ] );
$lbf->method( 'getMainLB' )->willReturn( $lb );
$lbf->method( 'getLocalDomainID' )->willReturn( 'fake' );
$this->setService( 'DBLoadBalancerFactory', $lbf );
return $lb;
}
/**
* @return MockObject|IDatabase
*/
private function installMockDatabase() {
$db = $this->getMockBuilder( IDatabase::class )
->disableAutoReturnValueGeneration()
->disableOriginalConstructor()->getMock();
$this->installMockLoadBalancer( $db );
return $db;
}
/**
@ -64,67 +64,48 @@ class RevisionStoreTest extends MediaWikiIntegrationTestCase {
->disableOriginalConstructor()->getMock();
}
/**
* @param ILoadBalancer $mockLoadBalancer
* @param Database $db
* @return callable
*/
private function getMockDBConnRefCallback( ILoadBalancer $mockLoadBalancer, IDatabase $db ) {
return static function ( $i, $g, $domain, $flg ) use ( $mockLoadBalancer, $db ) {
return new MaintainableDBConnRef( $mockLoadBalancer, $db, $i );
};
}
/**
* @return MockObject|SqlBlobStore
*/
private function getMockSqlBlobStore() {
return $this->getMockBuilder( SqlBlobStore::class )
->disableOriginalConstructor()->getMock();
}
private function getHashWANObjectCache() {
return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
}
/**
* @return IContentHandlerFactory|MockObject
*/
public function getMockContentHandlerFactory(): IContentHandlerFactory {
return $this->createMock( IContentHandlerFactory::class );
private function getDummyPageRow( $extra = [] ) {
return (object)( $extra + [
'page_id' => 1337,
'page_namespace' => 0,
'page_title' => 'Test',
'page_is_redirect' => 0,
'page_is_new' => 0,
'page_touched' => MWTimestamp::now(),
'page_links_updated' => MWTimestamp::now(),
'page_latest' => 23948576,
'page_len' => 2323,
'page_content_model' => CONTENT_MODEL_WIKITEXT
] );
}
/**
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromPageId() {
$mockLoadBalancer = $this->getMockLoadBalancer();
// Title calls wfGetDB() so we have to set the main service
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
$db = $this->installMockDatabase();
$db = $this->getMockDatabase();
// RevisionStore uses getConnectionRef
$mockLoadBalancer->expects( $this->any() )
->method( 'getConnectionRef' )
->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
// First call to Title::newFromID, faking no result (db lag?)
// First query is by page ID. Return result
$db->expects( $this->at( 0 ) )
->method( 'selectRow' )
->with(
'page',
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( (object)[
'page_namespace' => '1',
->willReturn( $this->getDummyPageRow( [
'page_id' => '1',
'page_namespace' => '3',
'page_title' => 'Food',
] );
] ) );
$store = $this->getRevisionStore( $mockLoadBalancer );
$db->method( 'selectRow' )
->willReturn( false );
$store = $this->getRevisionStore();
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
$this->assertSame( 1, $title->getNamespace() );
$this->assertSame( 3, $title->getNamespace() );
$this->assertSame( 'Food', $title->getDBkey() );
}
@ -132,50 +113,43 @@ class RevisionStoreTest extends MediaWikiIntegrationTestCase {
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromPageIdOnFallback() {
$mockLoadBalancer = $this->getMockLoadBalancer();
// Title calls wfGetDB() so we have to set the main service
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
$db = $this->installMockDatabase();
$db = $this->getMockDatabase();
// Assert that the first call uses a REPLICA and the second falls back to master
$mockLoadBalancer->expects( $this->atLeastOnce() )
->method( 'getConnectionRef' )
->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
// First call to Title::newFromID, faking no result (db lag?)
// First query, by page_id, no result
$db->expects( $this->at( 0 ) )
->method( 'selectRow' )
->with(
'page',
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( false );
// First select using rev_id, faking no result (db lag?)
// Second query, by rev_id, no result
$db->expects( $this->at( 1 ) )
->method( 'selectRow' )
->with(
[ 'revision', 'page' ],
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)
->willReturn( false );
// Second call to Title::newFromID, no result
// Retrying on master...
// Third query, by page_id again
$db->expects( $this->at( 2 ) )
->method( 'selectRow' )
->with(
'page',
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( (object)[
->willReturn( $this->getDummyPageRow( [
'page_namespace' => '2',
'page_title' => 'Foodey',
] );
] ) );
$store = $this->getRevisionStore( $mockLoadBalancer );
$store = $this->getRevisionStore();
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
$this->assertSame( 2, $title->getNamespace() );
@ -186,39 +160,32 @@ class RevisionStoreTest extends MediaWikiIntegrationTestCase {
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromRevId() {
$mockLoadBalancer = $this->getMockLoadBalancer();
// Title calls wfGetDB() so we have to set the main service
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
$db = $this->getMockDatabase();
$mockLoadBalancer->expects( $this->atLeastOnce() )
->method( 'getConnectionRef' )
->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
$db = $this->installMockDatabase();
// First call to Title::newFromID, faking no result (db lag?)
$db->expects( $this->at( 0 ) )
->method( 'selectRow' )
->with(
'page',
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( false );
// First select using rev_id, faking no result (db lag?)
// Second select using rev_id, faking no result (db lag?)
$db->expects( $this->at( 1 ) )
->method( 'selectRow' )
->with(
[ 'revision', 'page' ],
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)
->willReturn( (object)[
->willReturn( $this->getDummyPageRow( [
'page_namespace' => '1',
'page_title' => 'Food2',
] );
] ) );
$store = $this->getRevisionStore( $mockLoadBalancer );
$store = $this->getRevisionStore();
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
$this->assertSame( 1, $title->getNamespace() );
@ -229,60 +196,53 @@ class RevisionStoreTest extends MediaWikiIntegrationTestCase {
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromRevIdOnFallback() {
$mockLoadBalancer = $this->getMockLoadBalancer();
// Title calls wfGetDB() so we have to set the main service
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
$db = $this->installMockDatabase();
$db = $this->getMockDatabase();
// Assert that the first call uses a REPLICA and the second falls back to master
$mockLoadBalancer->expects( $this->atLeastOnce() )
->method( 'getConnectionRef' )
->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
// First call to Title::newFromID, faking no result (db lag?)
// First query, by page_id, no result
$db->expects( $this->at( 0 ) )
->method( 'selectRow' )
->with(
'page',
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( false );
// First select using rev_id, faking no result (db lag?)
// Second query, by rev_id, no result
$db->expects( $this->at( 1 ) )
->method( 'selectRow' )
->with(
[ 'revision', 'page' ],
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)
->willReturn( false );
// Second call to Title::newFromID, no result
// Retrying on master...
// Third query, by page_id again, still no result
$db->expects( $this->at( 2 ) )
->method( 'selectRow' )
->with(
'page',
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( false );
// Second select using rev_id, result
// Forth query, by rev_id agin
$db->expects( $this->at( 3 ) )
->method( 'selectRow' )
->with(
[ 'revision', 'page' ],
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)
->willReturn( (object)[
->willReturn( $this->getDummyPageRow( [
'page_namespace' => '2',
'page_title' => 'Foodey',
] );
] ) );
$store = $this->getRevisionStore( $mockLoadBalancer );
$store = $this->getRevisionStore();
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
$this->assertSame( 2, $title->getNamespace() );
@ -293,11 +253,9 @@ class RevisionStoreTest extends MediaWikiIntegrationTestCase {
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_correctFallbackAndthrowsExceptionAfterFallbacks() {
$mockLoadBalancer = $this->getMockLoadBalancer();
// Title calls wfGetDB() so we have to set the main service
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
$db = $this->getMockDatabase();
$mockLoadBalancer = $this->installMockLoadBalancer( $db );
// Assert that the first call uses a REPLICA and the second falls back to master
// RevisionStore getTitle uses getConnectionRef
@ -320,7 +278,7 @@ class RevisionStoreTest extends MediaWikiIntegrationTestCase {
$db->expects( $this->at( $counter ) )
->method( 'selectRow' )
->with(
'page',
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
@ -331,7 +289,7 @@ class RevisionStoreTest extends MediaWikiIntegrationTestCase {
$db->expects( $this->at( $counter ) )
->method( 'selectRow' )
->with(
[ 'revision', 'page' ],
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)

View file

@ -464,6 +464,7 @@ class RevisionDbTest extends MediaWikiIntegrationTestCase {
$services->getActorMigration(),
$services->getActorStore(),
$services->getContentHandlerFactory(),
$services->getPageStore(),
$services->getTitleFactory(),
$services->getHookContainer()
);

View file

@ -2,8 +2,11 @@
namespace MediaWiki\Tests\Page;
use Exception;
use IDatabase;
use InvalidArgumentException;
use LoadBalancer;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\DAO\WikiAwareEntity;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Page\PageRecord;
@ -12,6 +15,7 @@ use MediaWikiIntegrationTestCase;
use MockTitleTrait;
use TitleValue;
use Wikimedia\Assert\PreconditionException;
use Wikimedia\Rdbms\DBConnRef;
/**
* @group Database
@ -461,6 +465,55 @@ class PageStoreTest extends MediaWikiIntegrationTestCase {
$this->assertSamePage( $existingPage, $rec );
}
/**
* @covers \MediaWiki\Page\PageStore::newSelectQueryBuilder
*/
public function testNewSelectQueryBuilder_passDatabase() {
$pageStore = $this->getPageStore();
// Test that the provided DB connection is used.
$db = $this->createMock( IDatabase::class );
$db->expects( $this->atLeastOnce() )->method( 'selectRow' )->willReturn( false );
$pageStore->newSelectQueryBuilder( $db )
->fetchPageRecord();
}
/**
* @covers \MediaWiki\Page\PageStore::newSelectQueryBuilder
*/
public function testNewSelectQueryBuilder_passFlags() {
$services = $this->getServiceContainer();
$serviceOptions = new ServiceOptions(
PageStore::CONSTRUCTOR_OPTIONS,
[
'LanguageCode' => 'qxx',
'PageLanguageUseDB' => true
]
);
// Test that the provided DB connection is used.
$db = $this->createMock( IDatabase::class );
$db->expects( $this->atLeastOnce() )->method( 'selectRow' )->willReturn( false );
// Test that the load balancer is asked for a master connection
$lb = $this->createMock( LoadBalancer::class );
$lb->expects( $this->atLeastOnce() )
->method( 'getConnectionRef' )
->with( DB_MASTER )
->willReturn( new DBConnRef( $lb, $db, DB_MASTER ) );
$pageStore = new PageStore(
$serviceOptions,
$lb,
$services->getNamespaceInfo(),
WikiAwareEntity::LOCAL
);
$pageStore->newSelectQueryBuilder( PageStore::READ_LATEST )
->fetchPageRecord();
}
/**
* @covers \MediaWiki\Page\PageStore::getSubpages
*/

View file

@ -0,0 +1,40 @@
<?php
namespace MediaWiki\Tests\Page;
use LoadBalancer;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Page\PageStore;
use MediaWiki\Page\PageStoreFactory;
use MediaWikiUnitTestCase;
use NamespaceInfo;
use Wikimedia\Rdbms\LBFactory;
/**
* @covers \MediaWiki\Page\PageStoreFactory
*/
class PageStoreFactoryTest extends MediaWikiUnitTestCase {
public function testGetPageStore() {
$options = new ServiceOptions( PageStoreFactory::CONSTRUCTOR_OPTIONS, [
'LanguageCode' => 'fi',
'PageLanguageUseDB' => true,
] );
$lb = $this->createNoOpMock( LoadBalancer::class );
$lbFactory = $this->createNoOpMock( LBFactory::class, [ 'getMainLB' ] );
$lbFactory->method( 'getMainLB' )->willReturn( $lb );
$nsInfo = $this->createNoOpMock( NamespaceInfo::class );
$factory = new PageStoreFactory(
$options,
$lbFactory,
$nsInfo
);
// Just check that nothing explodes.
$this->assertInstanceOf( PageStore::class, $factory->getPageStore() );
}
}