14require_once __DIR__ .
'/Maintenance.php';
28 parent::__construct();
30This script refreshes the category membership counts stored in the category
31table. As time passes, these counts often drift from the actual number of
32category members. The script identifies rows where the value in the category
33table does not match the number of categorylinks rows
for that category, and
34updates the category table accordingly.
36To fully refresh the data in the category table, you need to run
this script
37for all three modes. Alternatively, just one mode can be run
if required.
42 '(REQUIRED) Which category count column to recompute: "pages", "subcats", "files" or "all".',
48 'Only recount categories with cat_id greater than the given value',
54 'Wait this many milliseconds after each batch. Default: 0',
61 'Skip running cleanupEmptyCategories if the "page" mode is selected',
70 $originalMode = $this->
getOption(
'mode' );
71 if ( !in_array( $originalMode, [
'pages',
'subcats',
'files',
'all' ] ) ) {
72 $this->
fatalError(
'Please specify a valid mode: one of "pages", "subcats", "files" or "all".' );
75 if ( $originalMode ===
'all' ) {
76 $modes = [
'pages',
'subcats',
'files' ];
78 $modes = [ $originalMode ];
81 foreach ( $modes as $mode ) {
82 $this->
output(
"Starting to recount {$mode} counts.\n" );
83 $this->minimumId = intval( $this->
getOption(
'begin', 0 ) );
88 while ( ( $result = $this->
doWork( $mode ) ) !==
false ) {
89 $affectedRows += $result;
90 usleep( $this->
getOption(
'throttle', 0 ) * 1000 );
93 $this->
output(
"Updated the {$mode} counts of $affectedRows categories.\n" );
97 $this->
output(
"Done!\n" );
98 if ( $originalMode !==
'all' ) {
99 $this->
output(
"Now run the script using the other --mode options if you haven't already.\n" );
102 if ( in_array(
'pages', $modes ) ) {
103 if ( $this->
hasOption(
'skip-cleanup' ) ) {
105 "Also run 'php cleanupEmptyCategories.php --mode remove' to remove empty,\n" .
106 "nonexistent categories from the category table.\n\n" );
108 $this->
output(
"Running cleanupEmptyCategories.php\n" );
109 $cleanup = $this->
createChild( CleanupEmptyCategories::class );
110 '@phan-var CleanupEmptyCategories $cleanup';
113 $cleanup->loadParamsAndArgs( $this->mSelf, [], [] );
115 $cleanup->setForce(
true );
121 protected function doWork(
string $mode ): int|false {
122 $this->
output(
"Finding up to {$this->getBatchSize()} drifted rows " .
123 "greater than cat_id {$this->minimumId}...\n" );
127 $queryBuilder = $dbr->newSelectQueryBuilder()
128 ->select(
'COUNT(*)' )
129 ->from(
'categorylinks' )
130 ->join(
'linktarget',
null,
'cl_target_id = lt_id' )
136 if ( $mode ===
'subcats' ) {
137 $queryBuilder->andWhere( [
'cl_type' =>
'subcat' ] );
138 } elseif ( $mode ===
'files' ) {
139 $queryBuilder->andWhere( [
'cl_type' =>
'file' ] );
142 $countingSubquery = $queryBuilder->caller( __METHOD__ )->getSQL();
148 $idsToUpdate = $dbr->newSelectQueryBuilder()
151 ->where( [ $dbr->expr(
'cat_id',
'>', (
int)$this->minimumId ),
"cat_{$mode} != ($countingSubquery)" ] )
153 ->caller( __METHOD__ )->fetchFieldValues();
154 if ( !$idsToUpdate ) {
157 $this->
output(
"Updating cat_{$mode} field on " .
158 count( $idsToUpdate ) .
" rows...\n" );
164 $this->minimumId = end( $idsToUpdate );
168 $res = $dbw->newSelectQueryBuilder()
169 ->select( [
'cat_id',
'count' =>
"($countingSubquery)" ] )
171 ->where( [
'cat_id' => $idsToUpdate ] )
172 ->caller( __METHOD__ )->fetchResultSet();
180 foreach ( $res as $row ) {
181 $dbw->newUpdateQueryBuilder()
182 ->update(
'category' )
183 ->set( [
"cat_{$mode}" => $row->count ] )
185 'cat_id' => $row->cat_id,
186 $dbw->expr(
"cat_{$mode}",
'!=', (
int)$row->count ),
188 ->caller( __METHOD__ )
190 $affectedRows += $dbw->affectedRows();
195 return $affectedRows;
201require_once RUN_MAINTENANCE_IF_MAIN;
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
createChild(string $maintClass, ?string $classFile=null)
Returns an instance of the given maintenance script, with all of the current arguments passed to it.
getBatchSize()
Returns batch size.
output( $out, $channel=null)
Throw some output to the user.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
waitForReplication()
Wait for replica DB servers to catch up.
hasOption( $name)
Checks to see if a particular option was set.
getOption( $name, $default=null)
Get an option, or return the default.
getPrimaryDB(string|false $virtualDomain=false)
addDescription( $text)
Set the description text.
Maintenance script that refreshes category membership counts in the category table.
__construct()
Default constructor.
execute()
Do the actual work.