Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
88.57% |
124 / 140 |
|
66.67% |
2 / 3 |
CRAP | |
0.00% |
0 / 1 |
PopulateCheckUserTable | |
92.54% |
124 / 134 |
|
66.67% |
2 / 3 |
19.15 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getUpdateKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
doDBUpdates | |
92.19% |
118 / 128 |
|
0.00% |
0 / 1 |
17.14 |
1 | <?php |
2 | |
3 | namespace MediaWiki\CheckUser\Maintenance; |
4 | |
5 | use DatabaseLogEntry; |
6 | use LoggedUpdateMaintenance; |
7 | use MediaWiki\MediaWikiServices; |
8 | use RecentChange; |
9 | use Wikimedia\IPUtils; |
10 | |
11 | $IP = getenv( 'MW_INSTALL_PATH' ); |
12 | if ( $IP === false ) { |
13 | $IP = __DIR__ . '/../../..'; |
14 | } |
15 | require_once "$IP/maintenance/Maintenance.php"; |
16 | |
17 | /** |
18 | * Populate the cu_changes table needed for CheckUser queries with |
19 | * data from recent changes. |
20 | * This is automatically run during first installation within update.php |
21 | * but --force parameter should be set if you want to manually run thereafter. |
22 | */ |
23 | class PopulateCheckUserTable extends LoggedUpdateMaintenance { |
24 | public function __construct() { |
25 | parent::__construct(); |
26 | $this->addDescription( 'Populate `cu_changes` table with entries from recentchanges' ); |
27 | $this->addOption( 'cutoff', 'Cut-off time for rc_timestamp' ); |
28 | $this->setBatchSize( 100 ); |
29 | |
30 | $this->requireExtension( 'CheckUser' ); |
31 | } |
32 | |
33 | /** |
34 | * @inheritDoc |
35 | */ |
36 | protected function getUpdateKey() { |
37 | return __CLASS__; |
38 | } |
39 | |
40 | /** |
41 | * @inheritDoc |
42 | */ |
43 | protected function doDBUpdates() { |
44 | $db = $this->getPrimaryDB(); |
45 | |
46 | // Check if the table is empty |
47 | $rcRows = $db->newSelectQueryBuilder() |
48 | ->table( 'recentchanges' ) |
49 | ->caller( __METHOD__ ) |
50 | ->fetchRowCount(); |
51 | if ( !$rcRows ) { |
52 | $this->output( "recentchanges is empty; nothing to add.\n" ); |
53 | return true; |
54 | } |
55 | |
56 | $cutoff = $this->getOption( 'cutoff' ); |
57 | if ( $cutoff ) { |
58 | // Something leftover... clear old entries to minimize dupes |
59 | $cutoff = $db->timestamp( $cutoff ); |
60 | $db->newDeleteQueryBuilder() |
61 | ->deleteFrom( 'cu_changes' ) |
62 | ->where( $db->expr( 'cuc_timestamp', '<', $cutoff ) ) |
63 | ->caller( __METHOD__ ) |
64 | ->execute(); |
65 | $cutoffCond = $db->expr( 'rc_timestamp', '<', $cutoff ); |
66 | } else { |
67 | $cutoffCond = null; |
68 | } |
69 | |
70 | $start = (int)$db->newSelectQueryBuilder() |
71 | ->field( 'MIN(rc_id)' ) |
72 | ->table( 'recentchanges' ) |
73 | ->caller( __METHOD__ ) |
74 | ->fetchField(); |
75 | $end = (int)$db->newSelectQueryBuilder() |
76 | ->field( 'MAX(rc_id)' ) |
77 | ->table( 'recentchanges' ) |
78 | ->caller( __METHOD__ ) |
79 | ->fetchField(); |
80 | // Do remaining chunk |
81 | $end += $this->mBatchSize - 1; |
82 | $blockStart = $start; |
83 | $blockEnd = $start + $this->mBatchSize - 1; |
84 | |
85 | $this->output( |
86 | "Starting population of cu_changes with recentchanges rc_id from $start to $end.\n" |
87 | ); |
88 | |
89 | $services = MediaWikiServices::getInstance(); |
90 | $commentStore = $services->getCommentStore(); |
91 | $rcQuery = RecentChange::getQueryInfo(); |
92 | |
93 | while ( $blockStart <= $end ) { |
94 | $this->output( "...migrating rc_id from $blockStart to $blockEnd\n" ); |
95 | $queryBuilder = $db->newSelectQueryBuilder() |
96 | ->fields( $rcQuery['fields'] ) |
97 | ->tables( $rcQuery['tables'] ) |
98 | ->joinConds( $rcQuery['joins'] ) |
99 | ->conds( [ |
100 | $db->expr( 'rc_id', '>=', $blockStart ), |
101 | $db->expr( 'rc_id', '<=', $blockEnd ), |
102 | ] ) |
103 | ->caller( __METHOD__ ); |
104 | if ( $cutoffCond ) { |
105 | $queryBuilder->andWhere( $cutoffCond ); |
106 | } |
107 | $res = $queryBuilder->fetchResultSet(); |
108 | $cuChangesBatch = []; |
109 | $cuPrivateEventBatch = []; |
110 | $cuLogEventBatch = []; |
111 | foreach ( $res as $row ) { |
112 | $eventTablesMigrationStage = $services->getMainConfig() |
113 | ->get( 'CheckUserEventTablesMigrationStage' ); |
114 | $comment = $commentStore->getComment( 'rc_comment', $row ); |
115 | if ( |
116 | $row->rc_type == RC_LOG && |
117 | ( $eventTablesMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) |
118 | ) { |
119 | $logEntry = null; |
120 | if ( $row->rc_logid != 0 ) { |
121 | $logEntry = DatabaseLogEntry::newFromId( $row->rc_logid, $db ); |
122 | } |
123 | if ( $logEntry === null ) { |
124 | $cuPrivateEventBatch[] = [ |
125 | 'cupe_timestamp' => $row->rc_timestamp, |
126 | 'cupe_actor' => $row->rc_actor, |
127 | 'cupe_namespace' => $row->rc_namespace, |
128 | 'cupe_title' => $row->rc_title, |
129 | 'cupe_comment_id' => $comment->id, |
130 | 'cupe_page' => $row->rc_cur_id, |
131 | 'cupe_log_action' => $row->rc_log_action, |
132 | 'cupe_log_type' => $row->rc_log_type, |
133 | 'cupe_params' => $row->rc_params, |
134 | 'cupe_ip' => $row->rc_ip, |
135 | 'cupe_ip_hex' => IPUtils::toHex( $row->rc_ip ), |
136 | ]; |
137 | } else { |
138 | $cuLogEventBatch[] = [ |
139 | 'cule_timestamp' => $row->rc_timestamp, |
140 | 'cule_actor' => $row->rc_actor, |
141 | 'cule_log_id' => $row->rc_logid, |
142 | 'cule_ip' => $row->rc_ip, |
143 | 'cule_ip_hex' => IPUtils::toHex( $row->rc_ip ), |
144 | ]; |
145 | } |
146 | } |
147 | if ( |
148 | $row->rc_type != RC_LOG || |
149 | ( $eventTablesMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) |
150 | ) { |
151 | $cuChangesRow = [ |
152 | 'cuc_timestamp' => $row->rc_timestamp, |
153 | 'cuc_namespace' => $row->rc_namespace, |
154 | 'cuc_title' => $row->rc_title, |
155 | 'cuc_actor' => $row->rc_actor, |
156 | 'cuc_comment_id' => $comment->id, |
157 | 'cuc_minor' => $row->rc_minor, |
158 | 'cuc_page_id' => $row->rc_cur_id, |
159 | 'cuc_this_oldid' => $row->rc_this_oldid, |
160 | 'cuc_last_oldid' => $row->rc_last_oldid, |
161 | 'cuc_type' => $row->rc_type, |
162 | 'cuc_ip' => $row->rc_ip, |
163 | 'cuc_ip_hex' => IPUtils::toHex( $row->rc_ip ), |
164 | 'cuc_only_for_read_old' => 0, |
165 | ]; |
166 | if ( |
167 | $row->rc_type == RC_LOG && |
168 | ( $eventTablesMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) |
169 | ) { |
170 | $cuChangesRow['cuc_only_for_read_old'] = 1; |
171 | } |
172 | $cuChangesBatch[] = $cuChangesRow; |
173 | } |
174 | } |
175 | if ( count( $cuChangesBatch ) ) { |
176 | $db->newInsertQueryBuilder() |
177 | ->insertInto( 'cu_changes' ) |
178 | ->rows( $cuChangesBatch ) |
179 | ->caller( __METHOD__ ) |
180 | ->execute(); |
181 | } |
182 | if ( count( $cuPrivateEventBatch ) ) { |
183 | $db->newInsertQueryBuilder() |
184 | ->insertInto( 'cu_private_event' ) |
185 | ->rows( $cuPrivateEventBatch ) |
186 | ->caller( __METHOD__ ) |
187 | ->execute(); |
188 | } |
189 | if ( count( $cuLogEventBatch ) ) { |
190 | $db->newInsertQueryBuilder() |
191 | ->insertInto( 'cu_log_event' ) |
192 | ->rows( $cuLogEventBatch ) |
193 | ->caller( __METHOD__ ) |
194 | ->execute(); |
195 | } |
196 | $blockStart += $this->mBatchSize - 1; |
197 | $blockEnd += $this->mBatchSize - 1; |
198 | $this->waitForReplication(); |
199 | } |
200 | |
201 | $this->output( "...cu_changes table has been populated.\n" ); |
202 | return true; |
203 | } |
204 | } |
205 | |
206 | $maintClass = PopulateCheckUserTable::class; |
207 | require_once RUN_MAINTENANCE_IF_MAIN; |