Addshore has uploaded a new change for review. https://gerrit.wikimedia.org/r/266572
Change subject: WIP Move counting of watchers to WatchedItemStore ...................................................................... WIP Move counting of watchers to WatchedItemStore Also adds tests Still not sure if this will stick hence WIP Change-Id: I5a465773599cce9f8c9e94847cede6d12282c827 --- M includes/WatchedItem.php M includes/WatchedItemStore.php M includes/actions/InfoAction.php M includes/api/ApiQueryInfo.php M tests/phpunit/includes/WatchedItemStoreTest.php 5 files changed, 170 insertions(+), 41 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core refs/changes/72/266572/1 diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php index 1754f09..09df404 100644 --- a/includes/WatchedItem.php +++ b/includes/WatchedItem.php @@ -409,7 +409,7 @@ * @deprecated since 1.27. Use WatchedItemStore::duplicateEntries */ public static function duplicateEntries( $oldTitle, $newTitle ) { - $store = new WatchedItemStore( wfGetDB( DB_MASTER ) ); + $store = WatchedItemStore::newFromGlobalState(); $store->duplicateEntries( $oldTitle->getTitleValue(), $newTitle->getTitleValue() ); } diff --git a/includes/WatchedItemStore.php b/includes/WatchedItemStore.php index 52110e4..c41b6ee 100644 --- a/includes/WatchedItemStore.php +++ b/includes/WatchedItemStore.php @@ -12,8 +12,81 @@ */ private $masterDb; - public function __construct( IDatabase $masterDb ) { + /** + * @var IDatabase + */ + private $slaveDb; + + /** + * @var Config + */ + private $config; + + public function __construct( IDatabase $masterDb, IDatabase $slaveDb, Config $config ) { $this->masterDb = $masterDb; + $this->slaveDb = $slaveDb; + $this->config = $config; + } + + public static function newFromGlobalState() { + return new self( + wfGetDB( DB_MASTER, 'watchlist' ), + wfGetDB( DB_SLAVE, 'watchlist' ), + RequestContext::getMain()->getConfig() + ); + } + + /** + * @param TitleValue $titleValue + * + * @return int + */ + public function countWatchers( TitleValue $titleValue ) { + return (int)$this->slaveDb->selectField( + 'watchlist', + 'COUNT(*)', + array( + 'wl_namespace' => $titleValue->getNamespace(), + 'wl_title' => $titleValue->getDBkey(), + ), + __METHOD__ + ); + } + + /** + * @param TitleValue[]|Title[] $titleValues + * @param bool $unwatchedPages show unwatched pages obeying UnwatchedPageThreshold setting + * + * @return array multi dimensional like $return[$namespaceId][$titleString] = $watchers + */ + public function countWatchersMultiple( array $titleValues, $unwatchedPages = false ) { + $unwatchedPageThreshold = $this->config->get( 'UnwatchedPageThreshold' ); + + if ( !$unwatchedPages && !is_int( $unwatchedPageThreshold ) ) { + // TODO throw exception? + return array(); + } + + $options = array( 'GROUP BY' => array( 'wl_namespace', 'wl_title' ) ); + if ( !$unwatchedPages ) { + $options['HAVING'] = "COUNT(*) >= $unwatchedPageThreshold"; + } + + $lb = new LinkBatch( $titleValues ); + $res = $this->slaveDb->select( + 'watchlist', + array( 'wl_title', 'wl_namespace', 'count' => 'COUNT(*)' ), + array( $lb->constructSet( 'wl', $this->slaveDb ) ), + __METHOD__, + $options + ); + + $watchCounts = array(); + foreach ( $res as $row ) { + $watchCounts[$row->wl_namespace][$row->wl_title] = (int)$row->count; + } + + return $watchCounts; } /** diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php index 6f33db7..b2ac800 100644 --- a/includes/actions/InfoAction.php +++ b/includes/actions/InfoAction.php @@ -670,6 +670,7 @@ 86400 * 7, function ( $oldValue, &$ttl, &$setOpts ) use ( $page, $config, $fname ) { $title = $page->getTitle(); + $titleValue = $title->getTitleValue(); $id = $title->getArticleID(); $dbr = wfGetDB( DB_SLAVE ); @@ -678,18 +679,7 @@ $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist ); $result = array(); - - // Number of page watchers - $watchers = (int)$dbrWatchlist->selectField( - 'watchlist', - 'COUNT(*)', - array( - 'wl_namespace' => $title->getNamespace(), - 'wl_title' => $title->getDBkey(), - ), - $fname - ); - $result['watchers'] = $watchers; + $result['watchers'] = WatchedItemStore::newFromGlobalState()->countWatchers( $titleValue ); if ( $config->get( 'ShowUpdatedMarker' ) ) { // Threshold: last visited about 26 weeks before latest edit diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 6f1e2e5..45e4bc2 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -778,28 +778,12 @@ return; } - $this->watchers = array(); $this->showZeroWatchers = $canUnwatchedpages; - $db = $this->getDB(); - $lb = new LinkBatch( $this->everything ); - - $this->resetQueryParams(); - $this->addTables( array( 'watchlist' ) ); - $this->addFields( array( 'wl_title', 'wl_namespace', 'count' => 'COUNT(*)' ) ); - $this->addWhere( array( - $lb->constructSet( 'wl', $db ) - ) ); - $this->addOption( 'GROUP BY', array( 'wl_namespace', 'wl_title' ) ); - if ( !$canUnwatchedpages ) { - $this->addOption( 'HAVING', "COUNT(*) >= $unwatchedPageThreshold" ); - } - - $res = $this->select( __METHOD__ ); - - foreach ( $res as $row ) { - $this->watchers[$row->wl_namespace][$row->wl_title] = (int)$row->count; - } + $this->watchers = WatchedItemStore::newFromGlobalState()->countWatchersMultiple( + $this->everything, + $canUnwatchedpages + ); } public function getCacheMode( $params ) { diff --git a/tests/phpunit/includes/WatchedItemStoreTest.php b/tests/phpunit/includes/WatchedItemStoreTest.php index a1ba3f5..97e50cd 100644 --- a/tests/phpunit/includes/WatchedItemStoreTest.php +++ b/tests/phpunit/includes/WatchedItemStoreTest.php @@ -14,11 +14,93 @@ return $this->getMock( 'IDatabase' ); } - private function getFakeRow( $userId, $timestamp ) { + /** + * @return PHPUnit_Framework_MockObject_MockObject|Config + */ + private function getMockConfig() { + return $this->getMock( 'Config' ); + } + + private function getFakeRow( array $rowValues ) { $fakeRow = new stdClass(); - $fakeRow->wl_user = $userId; - $fakeRow->wl_notificationtimestamp = $timestamp; + foreach ( $rowValues as $valueName => $value ) { + $fakeRow->$valueName = $value; + } return $fakeRow; + } + + public function testCountWatchers() { + $titleValue = new TitleValue( 0, 'SomeDbKey' ); + + $mockDb = $this->getMockDb(); + $mockDb->expects( $this->exactly( 1 ) ) + ->method( 'selectField' ) + ->with( + 'watchlist', + 'COUNT(*)', + array( + 'wl_namespace' => $titleValue->getNamespace(), + 'wl_title' => $titleValue->getDBkey(), + ), + $this->isType( 'string' ) + ) + ->will( $this->returnValue( 7 ) ); + + $store = new WatchedItemStore( $this->getMockDb(), $mockDb, $this->getMockConfig() ); + + $this->assertEquals( 7, $store->countWatchers( $titleValue ) ); + } + + public function testCountWatchersMultiple() { + $titleValues = array( + new TitleValue( 0, 'SomeDbKey' ), + new TitleValue( 0, 'OtherDbKey' ), + new TitleValue( 1, 'AnotherDbKey' ), + ); + + $mockDb = $this->getMockDb(); + $mockConfig = $this->getMockConfig(); + + $dbResult = array( + $this->getFakeRow( array( 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'count' => 100 ) ), + $this->getFakeRow( array( 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'count' => 300 ) ), + $this->getFakeRow( array( 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'count' => 500 ) ), + ); + $mockDb->expects( $this->once() ) + ->method( 'makeWhereFrom2d' ) + ->with( + array( array( 'SomeDbKey' => 1, 'OtherDbKey' => 1 ), array( 'AnotherDbKey' => 1 ) ), + $this->isType( 'string' ), + $this->isType( 'string' ) + ) + ->will( $this->returnValue( 'makeWhereFrom2d return value' ) ); + $mockDb->expects( $this->once() ) + ->method( 'select' ) + ->with( + 'watchlist', + array( 'wl_title', 'wl_namespace', 'count' => 'COUNT(*)' ), + array( 'makeWhereFrom2d return value' ), + $this->isType( 'string' ), + array( + 'GROUP BY' => array( 'wl_namespace', 'wl_title' ), + 'HAVING' => 'COUNT(*) >= 60', + ) + ) + ->will( + $this->returnValue( $dbResult ) + ); + $mockConfig->expects( $this->exactly( 1 ) ) + ->method( 'get' ) + ->with( 'UnwatchedPageThreshold' ) + ->will( $this->returnValue( 60 ) ); + + $store = new WatchedItemStore( $this->getMockDb(), $mockDb, $mockConfig ); + + $expected = array( + 0 => array( 'SomeDbKey' => 100, 'OtherDbKey' => 300 ), + 1 => array( 'AnotherDbKey' => 500 ), + ); + $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) ); } public function testDuplicateEntries_nothingToDuplicate() { @@ -27,7 +109,7 @@ ->method( 'select' ) ->will( $this->returnValue( new FakeResultWrapper( array() ) ) ); - $store = new WatchedItemStore( $mockDb ); + $store = new WatchedItemStore( $mockDb, $this->getMockDb(), $this->getMockConfig() ); $store->duplicateEntries( new TitleValue( 0, 'Old_Title' ), @@ -37,8 +119,8 @@ public function testDuplicateEntries_somethingToDuplicate() { $fakeRows = array( - $this->getFakeRow( 1, '20151212010101' ), - $this->getFakeRow( 2, null ), + $this->getFakeRow( array( 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ) ), + $this->getFakeRow( array( 'wl_user' => 2, 'wl_notificationtimestamp' => null ) ), ); $mockDb = $this->getMockDb(); @@ -91,7 +173,7 @@ $this->isType( 'string' ) ); - $store = new WatchedItemStore( $mockDb ); + $store = new WatchedItemStore( $mockDb, $this->getMockDb(), $this->getMockConfig() ); $store->duplicateEntries( new TitleValue( 0, 'Old_Title' ), -- To view, visit https://gerrit.wikimedia.org/r/266572 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I5a465773599cce9f8c9e94847cede6d12282c827 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: Addshore <addshorew...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits