Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoveOrphanedEvents
0.00% covered (danger)
0.00%
0 / 93
0.00% covered (danger)
0.00%
0 / 4
90
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getUpdateKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 doDBUpdates
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 doMajorBatch
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * Remove rows from echo_event that don't have corresponding rows in echo_notification or echo_email_batch.
4 *
5 * @ingroup Maintenance
6 */
7
8use MediaWiki\Extension\Notifications\DbFactory;
9
10require_once getenv( 'MW_INSTALL_PATH' ) !== false
11    ? getenv( 'MW_INSTALL_PATH' ) . '/maintenance/Maintenance.php'
12    : __DIR__ . '/../../../maintenance/Maintenance.php';
13
14/**
15 * Maintenance script that removes orphaned event rows
16 *
17 * @ingroup Maintenance
18 */
19class RemoveOrphanedEvents extends LoggedUpdateMaintenance {
20
21    public function __construct() {
22        parent::__construct();
23
24        $this->addDescription( "Remove rows from echo_event and echo_target_page that don't have corresponding " .
25            "rows in echo_notification or echo_email_batch" );
26
27        $this->setBatchSize( 500 );
28
29        $this->requireExtension( 'Echo' );
30    }
31
32    public function getUpdateKey() {
33        return __CLASS__;
34    }
35
36    public function doDBUpdates() {
37        $startId = 0;
38        $dbFactory = DbFactory::newFromDefault();
39        $dbr = $dbFactory->getEchoDb( DB_REPLICA );
40        $maxId = (int)$dbr->newSelectQueryBuilder()
41            ->select( 'MAX(event_id)' )
42            ->from( 'echo_event' )
43            ->fetchField();
44        $eventsProcessedTotal = 0;
45        $targetsProcessedTotal = 0;
46        while ( $startId < $maxId ) {
47            $startId += $this->getBatchSize() * 1000;
48            [ $eventsProcessed, $targetsProcessed ] = $this->doMajorBatch( $startId );
49            $eventsProcessedTotal += $eventsProcessed;
50            $targetsProcessedTotal += $targetsProcessed;
51        }
52        $this->output( "In total, deleted $eventsProcessedTotal orphaned events and " .
53            "$targetsProcessedTotal target_page rows.\n" );
54
55        return true;
56    }
57
58    private function doMajorBatch( $maxId ) {
59        $dbFactory = DbFactory::newFromDefault();
60        $dbw = $dbFactory->getEchoDb( DB_PRIMARY );
61        $dbr = $dbFactory->getEchoDb( DB_REPLICA );
62        $iterator = new BatchRowIterator(
63            $dbr,
64            [ 'echo_event', 'echo_notification', 'echo_email_batch' ],
65            'event_id',
66            $this->getBatchSize()
67        );
68        $iterator->addJoinConditions( [
69            'echo_notification' => [ 'LEFT JOIN', 'notification_event=event_id' ],
70            'echo_email_batch' => [ 'LEFT JOIN', 'eeb_event_id=event_id' ],
71        ] );
72        $iterator->addConditions( [
73            'notification_user' => null,
74            'eeb_user_id' => null,
75            'event_id < ' . $maxId
76        ] );
77        $iterator->setCaller( __METHOD__ );
78
79        $this->output( "Removing orphaned echo_event rows with max event_id of $maxId...\n" );
80
81        $eventsProcessed = 0;
82        $targetsProcessed = 0;
83        foreach ( $iterator as $batch ) {
84            $ids = [];
85            foreach ( $batch as $row ) {
86                $ids[] = $row->event_id;
87            }
88            $dbw->newDeleteQueryBuilder()
89                ->deleteFrom( 'echo_event' )
90                ->where( [ 'event_id' => $ids ] )
91                ->caller( __METHOD__ )
92                ->execute();
93            $eventsProcessed += $dbw->affectedRows();
94            $dbw->newDeleteQueryBuilder()
95                ->deleteFrom( 'echo_target_page' )
96                ->where( [ 'etp_event' => $ids ] )
97                ->caller( __METHOD__ )
98                ->execute();
99            $targetsProcessed += $dbw->affectedRows();
100            $this->output( "Deleted $eventsProcessed orphaned events and $targetsProcessed target_page rows.\n" );
101            $this->waitForReplication();
102        }
103
104        $this->output( "Removing any remaining orphaned echo_target_page rows with max etp_event of $maxId...\n" );
105        $iterator = new BatchRowIterator(
106            $dbr,
107            [ 'echo_target_page', 'echo_event' ],
108            'etp_event',
109            $this->getBatchSize()
110        );
111        $iterator->addJoinConditions( [ 'echo_event' => [ 'LEFT JOIN', 'event_id=etp_event' ] ] );
112        $iterator->addConditions(
113            [
114                'event_type' => null,
115                'etp_event < ' . $maxId
116            ]
117        );
118        $iterator->addOptions( [ 'DISTINCT' ] );
119        $iterator->setCaller( __METHOD__ );
120
121        $processed = 0;
122        foreach ( $iterator as $batch ) {
123            $ids = [];
124            foreach ( $batch as $row ) {
125                $ids[] = $row->etp_event;
126            }
127            $dbw->newDeleteQueryBuilder()
128                ->deleteFrom( 'echo_target_page' )
129                ->where( [ 'etp_event' => $ids ] )
130                ->caller( __METHOD__ )
131                ->execute();
132            $processed += $dbw->affectedRows();
133            $this->output( "Deleted $processed orphaned target_page rows.\n" );
134            $this->waitForReplication();
135        }
136
137        return [ $eventsProcessed, $targetsProcessed + $processed ];
138    }
139}
140
141$maintClass = RemoveOrphanedEvents::class;
142require_once RUN_MAINTENANCE_IF_MAIN;