90 public function run() {
92 $lbFactory = $services->getDBLoadBalancerFactory();
93 $lb = $lbFactory->getMainLB();
96 $this->ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
98 $page = $services->getWikiPageFactory()->newFromID( $this->params[
'pageId'], IDBAccessObject::READ_LATEST );
100 $this->
setLastError(
"Could not find page #{$this->params['pageId']}" );
106 if ( !$lb->waitForPrimaryPos( $dbr ) ) {
107 $this->
setLastError(
"Timed out while pre-waiting for replica DB to catch up" );
112 $lockKey =
"{$dbw->getDomainID()}:CategoryMembershipChange:{$page->getId()}";
113 $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 1 );
114 if ( !$scopedLock ) {
115 $this->
setLastError(
"Could not acquire lock '$lockKey'" );
120 if ( !$lb->waitForPrimaryPos( $dbr ) ) {
121 $this->
setLastError(
"Timed out while waiting for replica DB to catch up" );
125 $dbr->flushSnapshot( __METHOD__ );
127 $cutoffUnix =
wfTimestamp( TS::UNIX, $this->params[
'revTimestamp'] );
130 $cutoffUnix -= self::ENQUEUE_FUDGE_SEC;
134 $subQuery = $dbr->newSelectQueryBuilder()
136 ->from(
'recentchanges' )
137 ->where(
'rc_this_oldid = rev_id' )
139 $row = $dbr->newSelectQueryBuilder()
140 ->select( [
'rev_timestamp',
'rev_id' ] )
142 ->where( [
'rev_page' => $page->getId() ] )
143 ->andWhere( $dbr->expr(
'rev_timestamp',
'>=', $dbr->timestamp( $cutoffUnix ) ) )
144 ->andWhere(
new RawSQLExpression(
'EXISTS (' . $subQuery->getSQL() .
')' ) )
145 ->orderBy( [
'rev_timestamp',
'rev_id' ], SelectQueryBuilder::SORT_DESC )
146 ->caller( __METHOD__ )->fetchRow();
150 $cutoffUnix =
wfTimestamp( TS::UNIX, $row->rev_timestamp );
151 $lastRevId = (int)$row->rev_id;
158 $revisionStore = $services->getRevisionStore();
159 $res = $revisionStore->newSelectQueryBuilder( $dbr )
162 'rev_page' => $page->getId(),
163 $dbr->buildComparison(
'>', [
164 'rev_timestamp' => $dbr->timestamp( $cutoffUnix ),
165 'rev_id' => $lastRevId,
168 ->orderBy( [
'rev_timestamp',
'rev_id' ], SelectQueryBuilder::SORT_ASC )
169 ->caller( __METHOD__ )->fetchResultSet();
172 foreach ( $res as $row ) {
190 if ( $newRev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
197 $oldRev = $services->getRevisionLookup()
198 ->getRevisionById( $newRev->
getParentId(), IDBAccessObject::READ_LATEST );
199 if ( !$oldRev || $oldRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
207 $categoryChanges = $this->getExplicitCategoriesChanges( $page, $newRev, $oldRev );
208 [ $categoryInserts, $categoryDeletes ] = $categoryChanges;
209 if ( !$categoryInserts && !$categoryDeletes ) {
213 $blc = $services->getBacklinkCacheFactory()->getBacklinkCache(
$title );
218 $this->recentChangeFactory,
219 $this->params[
'forImport'] ??
false
221 $catMembChange->checkTemplateLinks();
226 foreach ( $categoryInserts as $categoryName ) {
228 $catMembChange->triggerCategoryAddedNotification( $categoryTitle );
229 if ( $insertCount++ && ( $insertCount % $batchSize ) == 0 ) {
234 foreach ( $categoryDeletes as $categoryName ) {
236 $catMembChange->triggerCategoryRemovedNotification( $categoryTitle );
237 if ( $insertCount++ && ( $insertCount++ % $batchSize ) == 0 ) {