*/
private $loadBalancer;
+ /** @var WatchedItemQueryServiceExtension[]|null */
+ private $extensions = null;
+
public function __construct( LoadBalancer $loadBalancer ) {
$this->loadBalancer = $loadBalancer;
}
/**
- * @return DatabaseBase
- * @throws MWException
+ * @return WatchedItemQueryServiceExtension[]
*/
- private function getConnection() {
- return $this->loadBalancer->getConnection( DB_REPLICA, [ 'watchlist' ] );
+ private function getExtensions() {
+ if ( $this->extensions === null ) {
+ $this->extensions = [];
+ Hooks::run( 'WatchedItemQueryServiceExtensions', [ &$this->extensions, $this ] );
+ }
+ return $this->extensions;
}
/**
- * @param Database $connection
+ * @return IDatabase
* @throws MWException
*/
- private function reuseConnection( Database $connection ) {
- $this->loadBalancer->reuseConnection( $connection );
+ private function getConnection() {
+ return $this->loadBalancer->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
}
/**
* timestamp to start enumerating from
* 'end' => string (format accepted by wfTimestamp) requires 'dir' option,
* timestamp to end enumerating
- * 'startFrom' => [ string $rcTimestamp, int $rcId ] requires 'dir' option,
- * return items starting from the RecentChange specified by this,
- * $rcTimestamp should be in the format accepted by wfTimestamp
* 'watchlistOwner' => User user whose watchlist items should be listed if different
* than the one specified with $user param,
* requires 'watchlistOwnerToken' option
* generator ('rc_cur_id' or 'rc_this_oldid') if true, or all
* id fields ('rc_cur_id', 'rc_this_oldid', 'rc_last_oldid')
* if false (default)
+ * @param array|null &$startFrom Continuation value: [ string $rcTimestamp, int $rcId ]
* @return array of pairs ( WatchedItem $watchedItem, string[] $recentChangeInfo ),
* where $recentChangeInfo contains the following keys:
* - 'rc_id',
* - 'rc_deleted',
* Additional keys could be added by specifying the 'includeFields' option
*/
- public function getWatchedItemsWithRecentChangeInfo( User $user, array $options = [] ) {
+ public function getWatchedItemsWithRecentChangeInfo(
+ User $user, array $options = [], &$startFrom = null
+ ) {
$options += [
'includeFields' => [],
'namespaceIds' => [],
'must be DIR_OLDER or DIR_NEWER'
);
Assert::parameter(
- !isset( $options['start'] ) && !isset( $options['end'] ) && !isset( $options['startFrom'] )
+ !isset( $options['start'] ) && !isset( $options['end'] ) && $startFrom === null
|| isset( $options['dir'] ),
'$options[\'dir\']',
- 'must be provided when providing any of options: start, end, startFrom'
+ 'must be provided when providing the "start" or "end" options or the $startFrom parameter'
);
Assert::parameter(
- !isset( $options['startFrom'] )
- || ( is_array( $options['startFrom'] ) && count( $options['startFrom'] ) === 2 ),
+ !isset( $options['startFrom'] ),
'$options[\'startFrom\']',
+ 'must not be provided, use $startFrom instead'
+ );
+ Assert::parameter(
+ !isset( $startFrom ) || ( is_array( $startFrom ) && count( $startFrom ) === 2 ),
+ '$startFrom',
'must be a two-element array'
);
if ( array_key_exists( 'watchlistOwner', $options ) ) {
$dbOptions = $this->getWatchedItemsWithRCInfoQueryDbOptions( $options );
$joinConds = $this->getWatchedItemsWithRCInfoQueryJoinConds( $options );
+ if ( $startFrom !== null ) {
+ $conds[] = $this->getStartFromConds( $db, $options, $startFrom );
+ }
+
+ foreach ( $this->getExtensions() as $extension ) {
+ $extension->modifyWatchedItemsWithRCInfoQuery(
+ $user, $options, $db,
+ $tables,
+ $fields,
+ $conds,
+ $dbOptions,
+ $joinConds
+ );
+ }
+
$res = $db->select(
$tables,
$fields,
$joinConds
);
- $this->reuseConnection( $db );
-
+ $limit = isset( $dbOptions['LIMIT'] ) ? $dbOptions['LIMIT'] : INF;
$items = [];
+ $startFrom = null;
foreach ( $res as $row ) {
+ if ( --$limit <= 0 ) {
+ $startFrom = [ $row->rc_timestamp, $row->rc_id ];
+ break;
+ }
+
$items[] = [
new WatchedItem(
$user,
];
}
+ foreach ( $this->getExtensions() as $extension ) {
+ $extension->modifyWatchedItemsWithRCInfo( $user, $options, $db, $items, $res, $startFrom );
+ }
+
return $items;
}
$dbOptions
);
- $this->reuseConnection( $db );
-
$watchedItems = [];
foreach ( $res as $row ) {
// todo these could all be cached at some point?
}
private function getWatchedItemsWithRCInfoQueryConds(
- Database $db,
+ IDatabase $db,
User $user,
array $options
) {
$conds[] = $deletedPageLogCond;
}
- if ( array_key_exists( 'startFrom', $options ) ) {
- $conds[] = $this->getStartFromConds( $db, $options );
- }
-
return $conds;
}
return $conds;
}
- private function getStartEndConds( Database $db, array $options ) {
+ private function getStartEndConds( IDatabase $db, array $options ) {
if ( !isset( $options['start'] ) && ! isset( $options['end'] ) ) {
return [];
}
if ( isset( $options['start'] ) ) {
$after = $options['dir'] === self::DIR_OLDER ? '<=' : '>=';
- $conds[] = 'rc_timestamp ' . $after . ' ' . $db->addQuotes( $options['start'] );
+ $conds[] = 'rc_timestamp ' . $after . ' ' .
+ $db->addQuotes( $db->timestamp( $options['start'] ) );
}
if ( isset( $options['end'] ) ) {
$before = $options['dir'] === self::DIR_OLDER ? '>=' : '<=';
- $conds[] = 'rc_timestamp ' . $before . ' ' . $db->addQuotes( $options['end'] );
+ $conds[] = 'rc_timestamp ' . $before . ' ' .
+ $db->addQuotes( $db->timestamp( $options['end'] ) );
}
return $conds;
}
- private function getUserRelatedConds( Database $db, User $user, array $options ) {
+ private function getUserRelatedConds( IDatabase $db, User $user, array $options ) {
if ( !array_key_exists( 'onlyByUser', $options ) && !array_key_exists( 'notByUser', $options ) ) {
return [];
}
return $conds;
}
- private function getExtraDeletedPageLogEntryRelatedCond( Database $db, User $user ) {
+ private function getExtraDeletedPageLogEntryRelatedCond( IDatabase $db, User $user ) {
// LogPage::DELETED_ACTION hides the affected page, too. So hide those
// entirely from the watchlist, or someone could guess the title.
$bitmask = 0;
return '';
}
- private function getStartFromConds( Database $db, array $options ) {
+ private function getStartFromConds( IDatabase $db, array $options, array $startFrom ) {
$op = $options['dir'] === self::DIR_OLDER ? '<' : '>';
- list( $rcTimestamp, $rcId ) = $options['startFrom'];
+ list( $rcTimestamp, $rcId ) = $startFrom;
$rcTimestamp = $db->addQuotes( $db->timestamp( $rcTimestamp ) );
$rcId = (int)$rcId;
return $db->makeList(
);
}
- private function getWatchedItemsForUserQueryConds( Database $db, User $user, array $options ) {
+ private function getWatchedItemsForUserQueryConds( IDatabase $db, User $user, array $options ) {
$conds = [ 'wl_user' => $user->getId() ];
if ( $options['namespaceIds'] ) {
$conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
* Creates a query condition part for getting only items before or after the given link target
* (while ordering using $sort mode)
*
- * @param Database $db
+ * @param IDatabase $db
* @param LinkTarget $target
* @param string $op comparison operator to use in the conditions
* @return string
*/
- private function getFromUntilTargetConds( Database $db, LinkTarget $target, $op ) {
+ private function getFromUntilTargetConds( IDatabase $db, LinkTarget $target, $op ) {
return $db->makeList(
[
"wl_namespace $op " . $target->getNamespace(),
}
if ( array_key_exists( 'limit', $options ) ) {
- $dbOptions['LIMIT'] = (int)$options['limit'];
+ $dbOptions['LIMIT'] = (int)$options['limit'] + 1;
}
return $dbOptions;