<?php
use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
-use MediaWiki\MediaWikiServices;
use MediaWiki\Linker\LinkTarget;
use Wikimedia\Assert\Assert;
* @return ScopedCallback to reset the overridden value
* @throws MWException
*/
- public function overrideDeferredUpdatesAddCallableUpdateCallback( $callback ) {
+ public function overrideDeferredUpdatesAddCallableUpdateCallback( callable $callback ) {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
throw new MWException(
'Cannot override DeferredUpdates::addCallableUpdate callback in operation.'
);
}
- Assert::parameterType( 'callable', $callback, '$callback' );
-
$previousValue = $this->deferredUpdatesAddCallableUpdateCallback;
$this->deferredUpdatesAddCallableUpdateCallback = $callback;
return new ScopedCallback( function() use ( $previousValue ) {
* @return ScopedCallback to reset the overridden value
* @throws MWException
*/
- public function overrideRevisionGetTimestampFromIdCallback( $callback ) {
+ public function overrideRevisionGetTimestampFromIdCallback( callable $callback ) {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
throw new MWException(
'Cannot override Revision::getTimestampFromId callback in operation.'
);
}
- Assert::parameterType( 'callable', $callback, '$callback' );
-
$previousValue = $this->revisionGetTimestampFromIdCallback;
$this->revisionGetTimestampFromIdCallback = $callback;
return new ScopedCallback( function() use ( $previousValue ) {
}
private function uncacheLinkTarget( LinkTarget $target ) {
+ $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget' );
if ( !isset( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] ) ) {
return;
}
- $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget' );
foreach ( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] as $key ) {
$this->stats->increment( 'WatchedItemStore.uncacheLinkTarget.items' );
$this->cache->delete( $key );
}
}
+ private function uncacheUser( User $user ) {
+ $this->stats->increment( 'WatchedItemStore.uncacheUser' );
+ foreach ( $this->cacheIndex as $ns => $dbKeyArray ) {
+ foreach ( $dbKeyArray as $dbKey => $userArray ) {
+ if ( isset( $userArray[$user->getId()] ) ) {
+ $this->stats->increment( 'WatchedItemStore.uncacheUser.items' );
+ $this->cache->delete( $userArray[$user->getId()] );
+ }
+ }
+ }
+ }
+
/**
* @param User $user
* @param LinkTarget $target
}
/**
- * @param int $slaveOrMaster DB_MASTER or DB_SLAVE
+ * @param int $dbIndex DB_MASTER or DB_REPLICA
*
* @return DatabaseBase
* @throws MWException
*/
- private function getConnection( $slaveOrMaster ) {
- return $this->loadBalancer->getConnection( $slaveOrMaster, [ 'watchlist' ] );
+ private function getConnection( $dbIndex ) {
+ return $this->loadBalancer->getConnection( $dbIndex, [ 'watchlist' ] );
}
/**
* @return int
*/
public function countWatchedItems( User $user ) {
- $dbr = $this->getConnection( DB_SLAVE );
+ $dbr = $this->getConnection( DB_REPLICA );
$return = (int)$dbr->selectField(
'watchlist',
'COUNT(*)',
* @return int
*/
public function countWatchers( LinkTarget $target ) {
- $dbr = $this->getConnection( DB_SLAVE );
+ $dbr = $this->getConnection( DB_REPLICA );
$return = (int)$dbr->selectField(
'watchlist',
'COUNT(*)',
* @throws MWException
*/
public function countVisitingWatchers( LinkTarget $target, $threshold ) {
- $dbr = $this->getConnection( DB_SLAVE );
+ $dbr = $this->getConnection( DB_REPLICA );
$visitingWatchers = (int)$dbr->selectField(
'watchlist',
'COUNT(*)',
public function countWatchersMultiple( array $targets, array $options = [] ) {
$dbOptions = [ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ] ];
- $dbr = $this->getConnection( DB_SLAVE );
+ $dbr = $this->getConnection( DB_REPLICA );
if ( array_key_exists( 'minimumWatchers', $options ) ) {
$dbOptions['HAVING'] = 'COUNT(*) >= ' . (int)$options['minimumWatchers'];
array $targetsWithVisitThresholds,
$minimumWatchers = null
) {
- $dbr = $this->getConnection( DB_SLAVE );
+ $dbr = $this->getConnection( DB_REPLICA );
$conds = $this->getVisitingWatchersCondition( $dbr, $targetsWithVisitThresholds );
return false;
}
- $dbr = $this->getConnection( DB_SLAVE );
+ $dbr = $this->getConnection( DB_REPLICA );
$row = $dbr->selectRow(
'watchlist',
'wl_notificationtimestamp',
"wl_title {$options['sort']}"
];
}
- $db = $this->getConnection( $options['forWrite'] ? DB_MASTER : DB_SLAVE );
+ $db = $this->getConnection( $options['forWrite'] ? DB_MASTER : DB_REPLICA );
$res = $db->select(
'watchlist',
return $timestamps;
}
- $dbr = $this->getConnection( DB_SLAVE );
+ $dbr = $this->getConnection( DB_REPLICA );
$lb = new LinkBatch( $targetsToLoad );
$res = $dbr->select(
return $success;
}
+ /**
+ * @param User $user The user to set the timestamp for
+ * @param string $timestamp Set the update timestamp to this value
+ * @param LinkTarget[] $targets List of targets to update. Default to all targets
+ *
+ * @return bool success
+ */
+ public function setNotificationTimestampsForUser( User $user, $timestamp, array $targets = [] ) {
+ // Only loggedin user can have a watchlist
+ if ( $user->isAnon() ) {
+ return false;
+ }
+
+ $dbw = $this->getConnection( DB_MASTER );
+
+ $conds = [ 'wl_user' => $user->getId() ];
+ if ( $targets ) {
+ $batch = new LinkBatch( $targets );
+ $conds[] = $batch->constructSet( 'wl', $dbw );
+ }
+
+ $success = $dbw->update(
+ 'watchlist',
+ [ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp ) ],
+ $conds,
+ __METHOD__
+ );
+
+ $this->reuseConnection( $dbw );
+
+ $this->uncacheUser( $user );
+
+ return $success;
+ }
+
/**
* @param User $editor The editor that triggered the update. Their notification
* timestamp will not be updated(they have already seen it)
*/
public function updateNotificationTimestamp( User $editor, LinkTarget $target, $timestamp ) {
$dbw = $this->getConnection( DB_MASTER );
- $res = $dbw->select( [ 'watchlist' ],
- [ 'wl_user' ],
+ $uids = $dbw->selectFieldValues(
+ 'watchlist',
+ 'wl_user',
[
'wl_user != ' . intval( $editor->getId() ),
'wl_namespace' => $target->getNamespace(),
'wl_title' => $target->getDBkey(),
'wl_notificationtimestamp IS NULL',
- ], __METHOD__
+ ],
+ __METHOD__
);
+ $this->reuseConnection( $dbw );
- $watchers = [];
- foreach ( $res as $row ) {
- $watchers[] = intval( $row->wl_user );
- }
-
+ $watchers = array_map( 'intval', $uids );
if ( $watchers ) {
// Update wl_notificationtimestamp for all watching users except the editor
$fname = __METHOD__;
- $dbw->onTransactionIdle(
- function () use ( $dbw, $timestamp, $watchers, $target, $fname ) {
- $dbw->update( 'watchlist',
- [ /* SET */
- 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
- ], [ /* WHERE */
- 'wl_user' => $watchers,
- 'wl_namespace' => $target->getNamespace(),
- 'wl_title' => $target->getDBkey(),
- ], $fname
- );
+ DeferredUpdates::addCallableUpdate(
+ function () use ( $timestamp, $watchers, $target, $fname ) {
+ global $wgUpdateRowsPerQuery;
+
+ $dbw = $this->getConnection( DB_MASTER );
+ $factory = wfGetLBFactory();
+ $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
+
+ $watchersChunks = array_chunk( $watchers, $wgUpdateRowsPerQuery );
+ foreach ( $watchersChunks as $watchersChunk ) {
+ $dbw->update( 'watchlist',
+ [ /* SET */
+ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
+ ], [ /* WHERE - TODO Use wl_id T130067 */
+ 'wl_user' => $watchersChunk,
+ 'wl_namespace' => $target->getNamespace(),
+ 'wl_title' => $target->getDBkey(),
+ ], $fname
+ );
+ if ( count( $watchersChunks ) > 1 ) {
+ $factory->commitAndWaitForReplication(
+ __METHOD__, $ticket, [ 'wiki' => $dbw->getWikiID() ]
+ );
+ }
+ }
$this->uncacheLinkTarget( $target );
- }
+
+ $this->reuseConnection( $dbw );
+ },
+ DeferredUpdates::POSTSEND,
+ $dbw
);
}
- $this->reuseConnection( $dbw );
-
return $watchers;
}
$queryOptions['LIMIT'] = $unreadLimit;
}
- $dbr = $this->getConnection( DB_SLAVE );
+ $dbr = $this->getConnection( DB_REPLICA );
$rowCount = $dbr->selectRowCount(
'watchlist',
'1',