Replace DatabaseBase:: with Database:: and update type hints
[lhc/web/wiklou.git] / includes / user / User.php
index 4ec8d54..6083db9 100644 (file)
@@ -474,7 +474,7 @@ class User implements IDBAccessObject {
                        $this->getCacheKey( $cache ),
                        $cache::TTL_HOUR,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
-                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
+                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
                                wfDebug( "User: cache miss for user {$this->mId}\n" );
 
                                $this->loadFromDatabase( self::READ_NORMAL );
@@ -566,7 +566,7 @@ class User implements IDBAccessObject {
        public static function newFromConfirmationCode( $code, $flags = 0 ) {
                $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
                        ? wfGetDB( DB_MASTER )
-                       : wfGetDB( DB_SLAVE );
+                       : wfGetDB( DB_REPLICA );
 
                $id = $db->selectField(
                        'user',
@@ -897,7 +897,7 @@ class User implements IDBAccessObject {
                        $conds[] = 'ug_user > ' . (int)$after;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $ids = $dbr->selectFieldValues(
                        'user_groups',
                        'ug_user',
@@ -1362,7 +1362,7 @@ class User implements IDBAccessObject {
                if ( is_null( $this->mGroups ) ) {
                        $db = ( $this->queryFlagsUsed & self::READ_LATEST )
                                ? wfGetDB( DB_MASTER )
-                               : wfGetDB( DB_SLAVE );
+                               : wfGetDB( DB_REPLICA );
                        $res = $db->select( 'user_groups',
                                [ 'ug_group' ],
                                [ 'ug_user' => $this->mId ],
@@ -1434,11 +1434,11 @@ class User implements IDBAccessObject {
         * protected against race conditions using a compare-and-set (CAS) mechanism
         * based on comparing $this->mTouched with the user_touched field.
         *
-        * @param DatabaseBase $db
-        * @param array $conditions WHERE conditions for use with DatabaseBase::update
-        * @return array WHERE conditions for use with DatabaseBase::update
+        * @param Database $db
+        * @param array $conditions WHERE conditions for use with Database::update
+        * @return array WHERE conditions for use with Database::update
         */
-       protected function makeUpdateConditions( DatabaseBase $db, array $conditions ) {
+       protected function makeUpdateConditions( Database $db, array $conditions ) {
                if ( $this->mTouched ) {
                        // CAS check: only update if the row wasn't changed sicne it was loaded.
                        $conditions['user_touched'] = $db->timestamp( $this->mTouched );
@@ -1570,8 +1570,8 @@ class User implements IDBAccessObject {
 
        /**
         * Get blocking information
-        * @param bool $bFromSlave Whether to check the slave database first.
-        *   To improve performance, non-critical checks are done against slaves.
+        * @param bool $bFromSlave Whether to check the replica DB first.
+        *   To improve performance, non-critical checks are done against replica DBs.
         *   Check when actually saving should be done against master.
         */
        private function getBlockedStatus( $bFromSlave = true ) {
@@ -1922,7 +1922,7 @@ class User implements IDBAccessObject {
        /**
         * Check if user is blocked
         *
-        * @param bool $bFromSlave Whether to check the slave database instead of
+        * @param bool $bFromSlave Whether to check the replica DB instead of
         *   the master. Hacked from false due to horrible probs on site.
         * @return bool True if blocked, false otherwise
         */
@@ -1933,7 +1933,7 @@ class User implements IDBAccessObject {
        /**
         * Get the block affecting the user, or null if the user is not blocked
         *
-        * @param bool $bFromSlave Whether to check the slave database instead of the master
+        * @param bool $bFromSlave Whether to check the replica DB instead of the master
         * @return Block|null
         */
        public function getBlock( $bFromSlave = true ) {
@@ -1945,7 +1945,7 @@ class User implements IDBAccessObject {
         * Check if user is blocked from editing a particular article
         *
         * @param Title $title Title to check
-        * @param bool $bFromSlave Whether to check the slave database instead of the master
+        * @param bool $bFromSlave Whether to check the replica DB instead of the master
         * @return bool
         */
        public function isBlockedFrom( $title, $bFromSlave = false ) {
@@ -2190,7 +2190,7 @@ class User implements IDBAccessObject {
                        return [];
                }
                $utp = $this->getTalkPage();
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                // Get the "last viewed rev" timestamp from the oldest message notification
                $timestamp = $dbr->selectField( 'user_newtalk',
                        'MIN(user_last_timestamp)',
@@ -2233,7 +2233,7 @@ class User implements IDBAccessObject {
         * @return bool True if the user has new messages
         */
        protected function checkNewtalk( $field, $id ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
 
@@ -2358,7 +2358,8 @@ class User implements IDBAccessObject {
                        wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
                                function() use ( $cache, $key ) {
                                        $cache->delete( $key );
-                               }
+                               },
+                               __METHOD__
                        );
                }
        }
@@ -3243,7 +3244,7 @@ class User implements IDBAccessObject {
                if ( is_null( $this->mFormerGroups ) ) {
                        $db = ( $this->queryFlagsUsed & self::READ_LATEST )
                                ? wfGetDB( DB_MASTER )
-                               : wfGetDB( DB_SLAVE );
+                               : wfGetDB( DB_REPLICA );
                        $res = $db->select( 'user_former_groups',
                                [ 'ufg_group' ],
                                [ 'ufg_user' => $this->mId ],
@@ -3268,7 +3269,7 @@ class User implements IDBAccessObject {
 
                if ( $this->mEditCount === null ) {
                        /* Populate the count, if it has not been populated yet */
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        // check if the user_editcount field has been initialized
                        $count = $dbr->selectField(
                                'user', 'user_editcount',
@@ -3595,7 +3596,7 @@ class User implements IDBAccessObject {
 
                // Only update the timestamp if the page is being watched.
                // The query to find out if it is watched is cached both in memcached and per-invocation,
-               // and when it does have to be executed, it can be on a slave
+               // and when it does have to be executed, it can be on a replica DB
                // If this is the user's newtalk page, we always update the timestamp
                $force = '';
                if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
@@ -3613,31 +3614,69 @@ class User implements IDBAccessObject {
         * @note If the user doesn't have 'editmywatchlist', this will do nothing.
         */
        public function clearAllNotifications() {
-               if ( wfReadOnly() ) {
-                       return;
-               }
-
+               global $wgUseEnotif, $wgShowUpdatedMarker;
                // Do nothing if not allowed to edit the watchlist
-               if ( !$this->isAllowed( 'editmywatchlist' ) ) {
+               if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
                        return;
                }
 
-               global $wgUseEnotif, $wgShowUpdatedMarker;
                if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
                        $this->setNewtalk( false );
                        return;
                }
+
                $id = $this->getId();
-               if ( $id != 0 ) {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->update( 'watchlist',
-                               [ /* SET */ 'wl_notificationtimestamp' => null ],
-                               [ /* WHERE */ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
-                               __METHOD__
-                       );
-                       // We also need to clear here the "you have new message" notification for the own user_talk page;
-                       // it's cleared one page view later in WikiPage::doViewUpdates().
+               if ( !$id ) {
+                       return;
+               }
+
+               $dbw = wfGetDB( DB_MASTER );
+               $asOfTimes = array_unique( $dbw->selectFieldValues(
+                       'watchlist',
+                       'wl_notificationtimestamp',
+                       [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
+                       __METHOD__,
+                       [ 'ORDER BY' => 'wl_notificationtimestamp DESC', 'LIMIT' => 500 ]
+               ) );
+               if ( !$asOfTimes ) {
+                       return;
                }
+               // Immediately update the most recent touched rows, which hopefully covers what
+               // the user sees on the watchlist page before pressing "mark all pages visited"....
+               $dbw->update(
+                       'watchlist',
+                       [ 'wl_notificationtimestamp' => null ],
+                       [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimes ],
+                       __METHOD__
+               );
+               // ...and finish the older ones in a post-send update with lag checks...
+               DeferredUpdates::addUpdate( new AutoCommitUpdate(
+                       $dbw,
+                       __METHOD__,
+                       function () use ( $dbw, $id ) {
+                               global $wgUpdateRowsPerQuery;
+
+                               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+                               $asOfTimes = array_unique( $dbw->selectFieldValues(
+                                       'watchlist',
+                                       'wl_notificationtimestamp',
+                                       [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
+                                       __METHOD__
+                               ) );
+                               foreach ( array_chunk( $asOfTimes, $wgUpdateRowsPerQuery ) as $asOfTimeBatch ) {
+                                       $dbw->update(
+                                               'watchlist',
+                                               [ 'wl_notificationtimestamp' => null ],
+                                               [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimeBatch ],
+                                               __METHOD__
+                                       );
+                                       $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+                               }
+                       }
+               ) );
+               // We also need to clear here the "you have new message" notification for the own
+               // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
        }
 
        /**
@@ -3818,7 +3857,7 @@ class User implements IDBAccessObject {
 
                // Get a new user_touched that is higher than the old one.
                // This will be used for a CAS check as a last-resort safety
-               // check against race conditions and slave lag.
+               // check against race conditions and replica DB lag.
                $newTouched = $this->newTouchedTimestamp();
 
                $dbw = wfGetDB( DB_MASTER );
@@ -3841,7 +3880,7 @@ class User implements IDBAccessObject {
                        // Maybe the problem was a missed cache update; clear it to be safe
                        $this->clearSharedCache( 'refresh' );
                        // User was changed in the meantime or loaded with stale data
-                       $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'slave';
+                       $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
                        throw new MWException(
                                "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
                                " the version of the user to be saved is older than the current version."
@@ -3870,7 +3909,7 @@ class User implements IDBAccessObject {
 
                $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
                        ? wfGetDB( DB_MASTER )
-                       : wfGetDB( DB_SLAVE );
+                       : wfGetDB( DB_REPLICA );
 
                $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
                        ? [ 'LOCK IN SHARE MODE' ]
@@ -4510,7 +4549,7 @@ class User implements IDBAccessObject {
                if ( $this->getId() == 0 ) {
                        return false; // anons
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $time = $dbr->selectField( 'revision', 'rev_timestamp',
                        [ 'rev_user' => $this->getId() ],
                        __METHOD__,
@@ -4889,9 +4928,12 @@ class User implements IDBAccessObject {
         * Deferred version of incEditCountImmediate()
         */
        public function incEditCount() {
-               wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() {
-                       $this->incEditCountImmediate();
-               } );
+               wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
+                       function () {
+                               $this->incEditCountImmediate();
+                       },
+                       __METHOD__
+               );
        }
 
        /**
@@ -4915,14 +4957,14 @@ class User implements IDBAccessObject {
                // Lazy initialization check...
                if ( $dbw->affectedRows() == 0 ) {
                        // Now here's a goddamn hack...
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        if ( $dbr !== $dbw ) {
-                               // If we actually have a slave server, the count is
+                               // If we actually have a replica DB server, the count is
                                // at least one behind because the current transaction
                                // has not been committed and replicated.
                                $this->mEditCount = $this->initEditCount( 1 );
                        } else {
-                               // But if DB_SLAVE is selecting the master, then the
+                               // But if DB_REPLICA is selecting the master, then the
                                // count we just read includes the revision that was
                                // just added in the working transaction.
                                $this->mEditCount = $this->initEditCount();
@@ -4930,7 +4972,7 @@ class User implements IDBAccessObject {
                } else {
                        if ( $this->mEditCount === null ) {
                                $this->getEditCount();
-                               $dbr = wfGetDB( DB_SLAVE );
+                               $dbr = wfGetDB( DB_REPLICA );
                                $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
                        } else {
                                $this->mEditCount++;
@@ -4947,9 +4989,9 @@ class User implements IDBAccessObject {
         * @return int Number of edits
         */
        protected function initEditCount( $add = 0 ) {
-               // Pull from a slave to be less cruel to servers
+               // Pull from a replica DB to be less cruel to servers
                // Accuracy isn't the point anyway here
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $count = (int)$dbr->selectField(
                        'revision',
                        'COUNT(rev_user)',
@@ -5107,7 +5149,7 @@ class User implements IDBAccessObject {
                                // Load from database
                                $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
                                        ? wfGetDB( DB_MASTER )
-                                       : wfGetDB( DB_SLAVE );
+                                       : wfGetDB( DB_REPLICA );
 
                                $res = $dbr->select(
                                        'user_properties',
@@ -5173,7 +5215,7 @@ class User implements IDBAccessObject {
                        [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
 
                // Find prior rows that need to be removed or updated. These rows will
-               // all be deleted (the later so that INSERT IGNORE applies the new values).
+               // all be deleted (the latter so that INSERT IGNORE applies the new values).
                $keysDelete = [];
                foreach ( $res as $row ) {
                        if ( !isset( $saveOptions[$row->up_property] )
@@ -5317,7 +5359,7 @@ class User implements IDBAccessObject {
         * Get a new instance of this user that was loaded from the master via a locking read
         *
         * Use this instead of the main context User when updating that user. This avoids races
-        * where that user was loaded from a slave or even the master but without proper locks.
+        * where that user was loaded from a replica DB or even the master but without proper locks.
         *
         * @return User|null Returns null if the user was not found in the DB
         * @since 1.27