26 require_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 );
58 "...cannot update while \$wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_WRITE_NEW\n"
68 if ( $this->
doTable(
'user' ) ) {
69 $this->
output(
"Creating actor entries for all registered users\n" );
72 $max = $dbw->selectField(
'user',
'MAX(user_id)',
'', __METHOD__ );
74 while ( $end < $max ) {
76 $end = min( $start + $this->mBatchSize, $max );
77 $this->
output(
"... $start - $end\n" );
81 [
'actor_user' =>
'user_id',
'actor_name' =>
'user_name' ],
82 [
"user_id >= $start",
"user_id <= $end" ],
85 [
'ORDER BY' => [
'user_id' ] ]
87 $count += $dbw->affectedRows();
90 $this->
output(
"Completed actor creation, added $count new actor(s)\n" );
92 $this->
output(
"Checking that actors exist for all registered users\n" );
94 $anyMissing =
$dbr->selectField(
97 [
'actor_id' =>
null ],
100 [
'actor' => [
'LEFT JOIN',
'actor_user = user_id' ] ]
103 $this->
error(
'Some users lack actors; run without --tables or include `user` in --tables.' );
106 $this->
output(
"Ok, continuing.\n" );
111 'revision',
'rev_id', [
'revactor_timestamp' =>
'rev_timestamp',
'revactor_page' =>
'rev_page' ],
112 'rev_user',
'rev_user_text',
'revactor_rev',
'revactor_actor'
114 $errors += $this->
migrate(
'archive',
'ar_id',
'ar_user',
'ar_user_text',
'ar_actor' );
115 $errors += $this->
migrate(
'ipblocks',
'ipb_id',
'ipb_by',
'ipb_by_text',
'ipb_by_actor' );
116 $errors += $this->
migrate(
'image',
'img_name',
'img_user',
'img_user_text',
'img_actor' );
118 'oldimage', [
'oi_name',
'oi_timestamp' ],
'oi_user',
'oi_user_text',
'oi_actor'
120 $errors += $this->
migrate(
'filearchive',
'fa_id',
'fa_user',
'fa_user_text',
'fa_actor' );
121 $errors += $this->
migrate(
'recentchanges',
'rc_id',
'rc_user',
'rc_user_text',
'rc_actor' );
122 $errors += $this->
migrate(
'logging',
'log_id',
'log_user',
'log_user_text',
'log_actor' );
126 return $errors === 0;
139 for ( $i =
count( $primaryKey ) - 1; $i >= 0; $i-- ) {
140 $field = $primaryKey[$i];
141 $display[] = $field .
'=' . $row->$field;
142 $value = $dbw->addQuotes( $row->$field );
143 if ( $next ===
'' ) {
144 $next =
"$field > $value";
146 $next =
"$field > $value OR $field = $value AND ($next)";
149 $display = implode(
' ', array_reverse( $display ) );
150 return [ $next, $display ];
161 $idSubquery = $dbw->buildSelectSubquery(
164 [
"$userField = actor_user" ],
167 $nameSubquery = $dbw->buildSelectSubquery(
170 [
"$nameField = actor_name" ],
173 return "CASE WHEN $userField = 0 OR $userField IS NULL THEN $nameSubquery ELSE $idSubquery END";
194 foreach (
$rows as $index => $row ) {
195 $keep[$index] =
true;
196 if ( $row->actor_id ===
null ) {
200 $name = $row->$nameField;
202 if ( !isset( $complainedAboutUsers[
$name] ) ) {
203 $complainedAboutUsers[
$name] =
true;
205 "User name \"$name\" is usable, cannot create an anonymous actor for it."
206 .
" Run maintenance/cleanupUsersWithNoId.php to fix this situation.\n"
209 unset( $keep[$index] );
212 $needActors[
$name] = 0;
221 array_map(
function ( $v ) {
225 }, array_keys( $needActors ) ),
232 [
'actor_id',
'actor_name' ],
233 [
'actor_name' => array_keys( $needActors ) ],
236 foreach (
$res as $row ) {
237 $needActors[$row->actor_name] = $row->actor_id;
240 if ( $row->actor_id ===
null ) {
241 $row->actor_id = $needActors[$row->$nameField];
262 protected function migrate( $table, $primaryKey, $userField, $nameField, $actorField ) {
263 if ( !$this->
doTable( $table ) ) {
264 $this->
output(
"Skipping $table, not included in --tables\n" );
268 $complainedAboutUsers = [];
270 $primaryKey = (
array)$primaryKey;
271 $pkFilter = array_flip( $primaryKey );
273 "Beginning migration of $table.$userField and $table.$nameField to $table.$actorField\n"
287 array_merge( $primaryKey, [ $userField, $nameField,
'actor_id' => $actorIdSubquery ] ),
294 'ORDER BY' => $primaryKey,
295 'LIMIT' => $this->mBatchSize,
298 if ( !
$res->numRows() ) {
304 $lastRow = end(
$rows );
306 $dbw, $nameField,
$rows, $complainedAboutUsers, $countErrors
311 if ( !$row->actor_id ) {
314 "Could not make actor for row with $display "
315 .
"$userField={$row->$userField} $nameField={$row->$nameField}\n"
323 $actorField => $row->actor_id,
325 array_intersect_key( (
array)$row, $pkFilter ) + [
330 $countUpdated += $dbw->affectedRows();
333 list( $next, $display ) = $this->
makeNextCond( $dbw, $primaryKey, $lastRow );
334 $this->
output(
"... $display\n" );
339 "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
340 .
"$countErrors error(s)\n"
362 $table, $primaryKey, $extra, $userField, $nameField, $newPrimaryKey, $actorField
364 if ( !$this->
doTable( $table ) ) {
365 $this->
output(
"Skipping $table, not included in --tables\n" );
369 $complainedAboutUsers = [];
371 $newTable = $table .
'_actor_temp';
373 "Beginning migration of $table.$userField and $table.$nameField to $newTable.$actorField\n"
386 [ $table, $newTable ],
387 [ $primaryKey, $userField, $nameField,
'actor_id' => $actorIdSubquery ] + $extra,
388 [ $newPrimaryKey =>
null ] + $next,
391 'ORDER BY' => $primaryKey,
392 'LIMIT' => $this->mBatchSize,
395 $newTable => [
'LEFT JOIN',
"{$primaryKey}={$newPrimaryKey}" ],
398 if ( !
$res->numRows() ) {
404 $lastRow = end(
$rows );
406 $dbw, $nameField,
$rows, $complainedAboutUsers, $countErrors
414 if ( !$row->actor_id ) {
417 "Could not make actor for row with $display "
418 .
"$userField={$row->$userField} $nameField={$row->$nameField}\n"
424 $newPrimaryKey => $row->$primaryKey,
425 $actorField => $row->actor_id,
427 foreach ( $extra
as $to => $from ) {
428 $ins[$to] = $row->$to;
431 $updates[] = $row->$primaryKey;
434 $dbw->insert( $newTable, $inserts, __METHOD__ );
435 $countUpdated += $dbw->affectedRows();
440 list( $n, $display ) = $this->
makeNextCond( $dbw, [ $primaryKey ], $lastRow );
442 $this->
output(
"... $display\n" );
446 "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
447 .
"$countErrors error(s)\n"
457 if ( !$this->
doTable(
'log_search' ) ) {
458 $this->
output(
"Skipping log_search, not included in --tables\n" );
462 $complainedAboutUsers = [];
464 $primaryKey = [
'ls_value',
'ls_log_id' ];
465 $this->
output(
"Beginning migration of log_search\n" );
473 $anyBad = $dbw->selectField(
476 [
'ls_field' =>
'target_author_actor',
'ls_value' =>
'' ],
481 $this->
output(
"... Deleting bogus rows due to T215525\n" );
484 [
'ls_field' =>
'target_author_actor',
'ls_value' =>
'' ],
487 $ct = $dbw->affectedRows();
488 $this->
output(
"... Deleted $ct bogus row(s) from T215525\n" );
496 [
'log_search',
'actor' ],
497 [
'ls_value',
'ls_log_id',
'actor_id' ],
499 'ls_field' =>
'target_author_id',
504 'ORDER BY' => $primaryKey,
505 'LIMIT' => $this->mBatchSize,
507 [
'actor' => [
'LEFT JOIN',
'actor_user = ' . $dbw->buildIntegerCast(
'ls_value' ) ] ]
509 if ( !
$res->numRows() ) {
515 foreach (
$res as $row ) {
517 if ( !$row->actor_id ) {
519 $this->
error(
"No actor for target_author_id row with $display\n" );
524 'ls_field' =>
'target_author_actor',
525 'ls_value' => $row->actor_id,
526 'ls_log_id' => $row->ls_log_id,
529 $dbw->insert(
'log_search', $ins, __METHOD__, [
'IGNORE' ] );
530 $countInserted += $dbw->affectedRows();
532 list( $next, $display ) = $this->
makeNextCond( $dbw, $primaryKey, $lastRow );
533 $this->
output(
"... target_author_id, $display\n" );
541 [
'log_search',
'actor' ],
542 [
'ls_value',
'ls_log_id',
'actor_id' ],
544 'ls_field' =>
'target_author_ip',
549 'ORDER BY' => $primaryKey,
550 'LIMIT' => $this->mBatchSize,
552 [
'actor' => [
'LEFT JOIN',
'ls_value = actor_name' ] ]
554 if ( !
$res->numRows() ) {
560 $lastRow = end(
$rows );
562 $dbw,
'ls_value',
$rows, $complainedAboutUsers, $countErrors
568 if ( !$row->actor_id ) {
570 $this->
error(
"Could not make actor for target_author_ip row with $display\n" );
575 'ls_field' =>
'target_author_actor',
576 'ls_value' => $row->actor_id,
577 'ls_log_id' => $row->ls_log_id,
580 $dbw->insert(
'log_search', $ins, __METHOD__, [
'IGNORE' ] );
581 $countInserted += $dbw->affectedRows();
583 list( $next, $display ) = $this->
makeNextCond( $dbw, $primaryKey, $lastRow );
584 $this->
output(
"... target_author_ip, $display\n" );
589 "Completed migration, inserted $countInserted row(s) with $countActors new actor(s), "
590 .
"$countErrors error(s)\n"