26require_once __DIR__ .
'/../Maintenance.php';
39 parent::__construct();
40 $this->
addDescription(
'Migrates actors from pre-1.31 columns to the \'actor\' table' );
41 $this->
addOption(
'tables',
'List of tables to process, comma-separated',
false,
true );
50 return $this->tables ===
null || in_array( $table, $this->tables,
true );
56 $this->tables = explode(
',',
$tables );
59 if ( $this->
doTable(
'user' ) ) {
60 $this->
output(
"Creating actor entries for all registered users\n" );
63 $max = $dbw->selectField(
'user',
'MAX(user_id)',
'', __METHOD__ );
65 while ( $end < $max ) {
67 $end = min( $start + $this->mBatchSize, $max );
68 $this->
output(
"... $start - $end\n" );
72 [
'actor_user' =>
'user_id',
'actor_name' =>
'user_name' ],
73 [
"user_id >= $start",
"user_id <= $end" ],
76 [
'ORDER BY' => [
'user_id' ] ]
78 $count += $dbw->affectedRows();
81 $this->
output(
"Completed actor creation, added $count new actor(s)\n" );
83 $this->
output(
"Checking that actors exist for all registered users\n" );
85 $anyMissing =
$dbr->selectField(
88 [
'actor_id' =>
null ],
91 [
'actor' => [
'LEFT JOIN',
'actor_user = user_id' ] ]
94 $this->
error(
'Some users lack actors; run without --tables or include `user` in --tables.' );
97 $this->
output(
"Ok, continuing.\n" );
102 'revision',
'rev_id', [
'revactor_timestamp' =>
'rev_timestamp',
'revactor_page' =>
'rev_page' ],
103 'rev_user',
'rev_user_text',
'revactor_rev',
'revactor_actor'
105 $errors += $this->
migrate(
'archive',
'ar_id',
'ar_user',
'ar_user_text',
'ar_actor' );
106 $errors += $this->
migrate(
'ipblocks',
'ipb_id',
'ipb_by',
'ipb_by_text',
'ipb_by_actor' );
107 $errors += $this->
migrate(
'image',
'img_name',
'img_user',
'img_user_text',
'img_actor' );
109 'oldimage', [
'oi_name',
'oi_timestamp' ],
'oi_user',
'oi_user_text',
'oi_actor'
111 $errors += $this->
migrate(
'filearchive',
'fa_id',
'fa_user',
'fa_user_text',
'fa_actor' );
112 $errors += $this->
migrate(
'recentchanges',
'rc_id',
'rc_user',
'rc_user_text',
'rc_actor' );
113 $errors += $this->
migrate(
'logging',
'log_id',
'log_user',
'log_user_text',
'log_actor' );
117 return $errors === 0;
130 for ( $i = count( $primaryKey ) - 1; $i >= 0; $i-- ) {
131 $field = $primaryKey[$i];
132 $display[] = $field .
'=' . $row->$field;
133 $value = $dbw->addQuotes( $row->$field );
134 if ( $next ===
'' ) {
135 $next =
"$field > $value";
137 $next =
"$field > $value OR $field = $value AND ($next)";
140 $display = implode(
' ', array_reverse( $display ) );
141 return [ $next, $display ];
152 $idSubquery = $dbw->buildSelectSubquery(
155 [
"$userField = actor_user" ],
158 $nameSubquery = $dbw->buildSelectSubquery(
161 [
"$nameField = actor_name" ],
164 return "CASE WHEN $userField = 0 OR $userField IS NULL THEN $nameSubquery ELSE $idSubquery END";
179 IDatabase $dbw, $nameField, array &$rows, array &$complainedAboutUsers, &$countErrors
185 foreach ( $rows as $index => $row ) {
186 $keep[$index] =
true;
187 if ( $row->actor_id ===
null ) {
191 $name = $row->$nameField;
192 if ( User::isUsableName( $name ) ) {
193 if ( !isset( $complainedAboutUsers[$name] ) ) {
194 $complainedAboutUsers[$name] =
true;
196 "User name \"$name\" is usable, cannot create an anonymous actor for it."
197 .
" Run maintenance/cleanupUsersWithNoId.php to fix this situation.\n"
200 unset( $keep[$index] );
203 $needActors[$name] = 0;
207 $rows = array_intersect_key( $rows, $keep );
212 array_map(
function ( $v ) {
216 }, array_keys( $needActors ) ),
223 [
'actor_id',
'actor_name' ],
224 [
'actor_name' => array_keys( $needActors ) ],
227 foreach (
$res as $row ) {
228 $needActors[$row->actor_name] = $row->actor_id;
230 foreach ( $rows as $row ) {
231 if ( $row->actor_id ===
null ) {
232 $row->actor_id = $needActors[$row->$nameField];
253 protected function migrate( $table, $primaryKey, $userField, $nameField, $actorField ) {
254 if ( !$this->
doTable( $table ) ) {
255 $this->
output(
"Skipping $table, not included in --tables\n" );
260 if ( !$dbw->fieldExists( $table, $userField, __METHOD__ ) ) {
261 $this->
output(
"No need to migrate $table.$userField, field does not exist\n" );
265 $complainedAboutUsers = [];
267 $primaryKey = (array)$primaryKey;
268 $pkFilter = array_flip( $primaryKey );
270 "Beginning migration of $table.$userField and $table.$nameField to $table.$actorField\n"
283 array_merge( $primaryKey, [ $userField, $nameField,
'actor_id' => $actorIdSubquery ] ),
290 'ORDER BY' => $primaryKey,
291 'LIMIT' => $this->mBatchSize,
294 if ( !
$res->numRows() ) {
299 $rows = iterator_to_array(
$res );
300 $lastRow = end( $rows );
302 $dbw, $nameField, $rows, $complainedAboutUsers, $countErrors
306 foreach ( $rows as $row ) {
307 if ( !$row->actor_id ) {
308 list( , $display ) = $this->
makeNextCond( $dbw, $primaryKey, $row );
310 "Could not make actor for row with $display "
311 .
"$userField={$row->$userField} $nameField={$row->$nameField}\n"
319 $actorField => $row->actor_id,
321 array_intersect_key( (array)$row, $pkFilter ) + [
326 $countUpdated += $dbw->affectedRows();
329 list( $next, $display ) = $this->
makeNextCond( $dbw, $primaryKey, $lastRow );
330 $this->
output(
"... $display\n" );
335 "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
336 .
"$countErrors error(s)\n"
359 $table, $primaryKey, $extra, $userField, $nameField, $newPrimaryKey, $actorField
361 if ( !$this->
doTable( $table ) ) {
362 $this->
output(
"Skipping $table, not included in --tables\n" );
367 if ( !$dbw->fieldExists( $table, $userField, __METHOD__ ) ) {
368 $this->
output(
"No need to migrate $table.$userField, field does not exist\n" );
372 $complainedAboutUsers = [];
374 $newTable = $table .
'_actor_temp';
376 "Beginning migration of $table.$userField and $table.$nameField to $newTable.$actorField\n"
388 [ $table, $newTable ],
389 [ $primaryKey, $userField, $nameField,
'actor_id' => $actorIdSubquery ] + $extra,
390 [ $newPrimaryKey =>
null ] + $next,
393 'ORDER BY' => $primaryKey,
394 'LIMIT' => $this->mBatchSize,
397 $newTable => [
'LEFT JOIN',
"{$primaryKey}={$newPrimaryKey}" ],
400 if ( !
$res->numRows() ) {
405 $rows = iterator_to_array(
$res );
406 $lastRow = end( $rows );
408 $dbw, $nameField, $rows, $complainedAboutUsers, $countErrors
415 foreach ( $rows as $row ) {
416 if ( !$row->actor_id ) {
417 list( , $display ) = $this->
makeNextCond( $dbw, [ $primaryKey ], $row );
419 "Could not make actor for row with $display "
420 .
"$userField={$row->$userField} $nameField={$row->$nameField}\n"
426 $newPrimaryKey => $row->$primaryKey,
427 $actorField => $row->actor_id,
429 foreach ( $extra as $to => $from ) {
430 $ins[$to] = $row->$to;
433 $updates[] = $row->$primaryKey;
436 $dbw->insert( $newTable, $inserts, __METHOD__ );
437 $countUpdated += $dbw->affectedRows();
442 list( $n, $display ) = $this->
makeNextCond( $dbw, [ $primaryKey ], $lastRow );
444 $this->
output(
"... $display\n" );
448 "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
449 .
"$countErrors error(s)\n"
459 if ( !$this->
doTable(
'log_search' ) ) {
460 $this->
output(
"Skipping log_search, not included in --tables\n" );
464 $complainedAboutUsers = [];
466 $primaryKey = [
'ls_value',
'ls_log_id' ];
467 $this->
output(
"Beginning migration of log_search\n" );
475 $anyBad = $dbw->selectField(
478 [
'ls_field' =>
'target_author_actor',
'ls_value' =>
'' ],
483 $this->
output(
"... Deleting bogus rows due to T215525\n" );
486 [
'ls_field' =>
'target_author_actor',
'ls_value' =>
'' ],
489 $ct = $dbw->affectedRows();
490 $this->
output(
"... Deleted $ct bogus row(s) from T215525\n" );
498 [
'log_search',
'actor' ],
499 [
'ls_value',
'ls_log_id',
'actor_id' ],
501 'ls_field' =>
'target_author_id',
506 'ORDER BY' => $primaryKey,
507 'LIMIT' => $this->mBatchSize,
509 [
'actor' => [
'LEFT JOIN',
'actor_user = ' . $dbw->buildIntegerCast(
'ls_value' ) ] ]
511 if ( !
$res->numRows() ) {
517 foreach (
$res as $row ) {
519 if ( !$row->actor_id ) {
520 list( , $display ) = $this->
makeNextCond( $dbw, $primaryKey, $row );
521 $this->
error(
"No actor for target_author_id row with $display\n" );
526 'ls_field' =>
'target_author_actor',
527 'ls_value' => $row->actor_id,
528 'ls_log_id' => $row->ls_log_id,
531 $dbw->insert(
'log_search', $ins, __METHOD__, [
'IGNORE' ] );
532 $countInserted += $dbw->affectedRows();
534 list( $next, $display ) = $this->
makeNextCond( $dbw, $primaryKey, $lastRow );
535 $this->
output(
"... target_author_id, $display\n" );
543 [
'log_search',
'actor' ],
544 [
'ls_value',
'ls_log_id',
'actor_id' ],
546 'ls_field' =>
'target_author_ip',
551 'ORDER BY' => $primaryKey,
552 'LIMIT' => $this->mBatchSize,
554 [
'actor' => [
'LEFT JOIN',
'ls_value = actor_name' ] ]
556 if ( !
$res->numRows() ) {
561 $rows = iterator_to_array(
$res );
562 $lastRow = end( $rows );
564 $dbw,
'ls_value', $rows, $complainedAboutUsers, $countErrors
569 foreach ( $rows as $row ) {
570 if ( !$row->actor_id ) {
571 list( , $display ) = $this->
makeNextCond( $dbw, $primaryKey, $row );
572 $this->
error(
"Could not make actor for target_author_ip row with $display\n" );
577 'ls_field' =>
'target_author_actor',
578 'ls_value' => $row->actor_id,
579 'ls_log_id' => $row->ls_log_id,
582 $dbw->insert(
'log_search', $ins, __METHOD__, [
'IGNORE' ] );
583 $countInserted += $dbw->affectedRows();
585 list( $next, $display ) = $this->
makeNextCond( $dbw, $primaryKey, $lastRow );
586 $this->
output(
"... target_author_ip, $display\n" );
591 "Completed migration, inserted $countInserted row(s) with $countActors new actor(s), "
592 .
"$countErrors error(s)\n"
wfWaitForSlaves( $ifWritesSince=null, $wiki=false, $cluster=false, $timeout=null)
Waits for the replica DBs to catch up to the master position.
Class for scripts that perform database maintenance and want to log the update in updatelog so we can...
error( $err, $die=0)
Throw an error to the user.
beginTransaction(IDatabase $dbw, $fname)
Begin a transcation on a DB.
commitTransaction(IDatabase $dbw, $fname)
Commit the transcation on a DB handle and wait for replica DBs to catch up.
output( $out, $channel=null)
Throw some output to the user.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
setBatchSize( $s=0)
Set the batch size.
Maintenance script that migrates actors from pre-1.31 columns to the 'actor' table.
addActorsForRows(IDatabase $dbw, $nameField, array &$rows, array &$complainedAboutUsers, &$countErrors)
Add actors for anons in a set of rows.
doDBUpdates()
Do the actual work.
migrate( $table, $primaryKey, $userField, $nameField, $actorField)
Migrate actors in a table.
makeActorIdSubquery( $dbw, $userField, $nameField)
Make the subqueries for actor_id
migrateLogSearch()
Migrate actors in the log_search table.
__construct()
Default constructor.
makeNextCond( $dbw, $primaryKey, $row)
Calculate a "next" condition and a display string.
migrateToTemp( $table, $primaryKey, $extra, $userField, $nameField, $newPrimaryKey, $actorField)
Migrate actors in a table to a temporary table.
getUpdateKey()
Get the update key name to go in the update log table.