Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
FixInconsistentRedirects
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
3 / 3
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getUpdateKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doDBUpdates
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
8
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7use MediaWiki\Maintenance\LoggedUpdateMaintenance;
8
9// @codeCoverageIgnoreStart
10require_once __DIR__ . '/Maintenance.php';
11// @codeCoverageIgnoreEnd
12
13/**
14 * Fix redirect pages with missing or incomplete row in the redirect table.
15 *
16 * @ingroup Maintenance
17 * @since 1.41
18 */
19class FixInconsistentRedirects extends LoggedUpdateMaintenance {
20
21    public function __construct() {
22        parent::__construct();
23        $this->addDescription( 'Fix redirect pages with missing or incomplete row in the redirect table' );
24        $this->setBatchSize( 100 );
25    }
26
27    /** @inheritDoc */
28    protected function getUpdateKey() {
29        return __CLASS__;
30    }
31
32    /** @inheritDoc */
33    protected function doDBUpdates() {
34        $dbr = $this->getReplicaDB();
35
36        $builder = $dbr->newSelectQueryBuilder()
37            ->caller( __METHOD__ )
38            ->from( 'page' )
39            ->where( [ 'page_is_redirect' => 1 ] );
40
41        $this->output( "Fixing inconsistent redirects ...\n" );
42
43        $estimateCount = $builder->estimateRowCount();
44        $this->output( "Estimated redirect page count: $estimateCount\n" );
45
46        $builder
47            ->limit( $this->getBatchSize() )
48            ->leftJoin( 'redirect', null, 'page_id=rd_from' )
49            ->select( [ 'rd_from', 'rd_interwiki', 'rd_fragment' ] );
50
51        // Using the page_redirect_namespace_len index to skip non-redirects
52        $index = [ 'page_is_redirect', 'page_namespace', 'page_len', 'page_id' ];
53        $builder->select( $index )->orderBy( $index );
54        $prevRow = [];
55
56        $total = 0;
57        $updated = 0;
58        do {
59            $res = ( clone $builder )
60                ->where( $prevRow ? [ $dbr->buildComparison( '>', $prevRow ) ] : [] )
61                ->caller( __METHOD__ )->fetchResultSet();
62
63            foreach ( $res as $row ) {
64                // Only attempt write queries if the row or rd_interwiki/rd_fragment fields are missing
65                // (we don't include this condition in the query to avoid slow queries and bad estimates)
66                if ( $row->rd_from === null || $row->rd_interwiki === null || $row->rd_fragment === null ) {
67                    RefreshLinks::fixRedirect( $this, $row->page_id );
68                    $updated++;
69                }
70            }
71            if ( isset( $row ) ) {
72                // Update the conditions to select the next batch
73                foreach ( $index as $field ) {
74                    $prevRow[ $field ] = $row->$field;
75                }
76            }
77
78            $this->waitForReplication();
79            $total += $res->numRows();
80            $this->output( "$updated/$total\n" );
81
82        } while ( $res->numRows() == $this->getBatchSize() );
83
84        $this->output( "Done, updated $updated of $total rows.\n" );
85        return true;
86    }
87}
88
89// @codeCoverageIgnoreStart
90$maintClass = FixInconsistentRedirects::class;
91require_once RUN_MAINTENANCE_IF_MAIN;
92// @codeCoverageIgnoreEnd