27require_once __DIR__ .
'/../Maintenance.php';
40 parent::__construct();
41 $this->
addDescription(
'Migrates actors from pre-1.31 columns to the \'actor\' table' );
42 $this->
addOption(
'tables',
'List of tables to process, comma-separated',
false,
true );
51 return $this->tables ===
null || in_array( $table, $this->tables,
true );
57 $this->tables = explode(
',',
$tables );
60 if ( $this->
doTable(
'user' ) ) {
61 $this->
output(
"Creating actor entries for all registered users\n" );
64 $max = $dbw->selectField(
'user',
'MAX(user_id)',
'', __METHOD__ );
66 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
67 while ( $end < $max ) {
69 $end = min( $start + $this->mBatchSize, $max );
70 $this->
output(
"... $start - $end\n" );
74 [
'actor_user' =>
'user_id',
'actor_name' =>
'user_name' ],
75 [
"user_id >= $start",
"user_id <= $end" ],
78 [
'ORDER BY' => [
'user_id' ] ]
80 $count += $dbw->affectedRows();
81 $lbFactory->waitForReplication();
83 $this->
output(
"Completed actor creation, added $count new actor(s)\n" );
85 $this->
output(
"Checking that actors exist for all registered users\n" );
87 $anyMissing = (bool)
$dbr->selectField(
90 [
'actor_id' =>
null ],
93 [
'actor' => [
'LEFT JOIN',
'actor_user = user_id' ] ]
96 $this->
error(
'Some users lack actors; run without --tables or include `user` in --tables.' );
99 $this->
output(
"Ok, continuing.\n" );
104 'revision',
'rev_id', [
'revactor_timestamp' =>
'rev_timestamp',
'revactor_page' =>
'rev_page' ],
105 'rev_user',
'rev_user_text',
'revactor_rev',
'revactor_actor'
107 $errors += $this->
migrate(
'archive',
'ar_id',
'ar_user',
'ar_user_text',
'ar_actor' );
108 $errors += $this->
migrate(
'ipblocks',
'ipb_id',
'ipb_by',
'ipb_by_text',
'ipb_by_actor' );
109 $errors += $this->
migrate(
'image',
'img_name',
'img_user',
'img_user_text',
'img_actor' );
111 'oldimage', [
'oi_name',
'oi_timestamp' ],
'oi_user',
'oi_user_text',
'oi_actor'
113 $errors += $this->
migrate(
'filearchive',
'fa_id',
'fa_user',
'fa_user_text',
'fa_actor' );
114 $errors += $this->
migrate(
'recentchanges',
'rc_id',
'rc_user',
'rc_user_text',
'rc_actor' );
115 $errors += $this->
migrate(
'logging',
'log_id',
'log_user',
'log_user_text',
'log_actor' );
119 return $errors === 0;
132 for ( $i = count( $primaryKey ) - 1; $i >= 0; $i-- ) {
133 $field = $primaryKey[$i];
134 $display[] = $field .
'=' . $row->$field;
135 $value = $dbw->addQuotes( $row->$field );
136 if ( $next ===
'' ) {
137 $next =
"$field > $value";
139 $next =
"$field > $value OR $field = $value AND ($next)";
142 $display = implode(
' ', array_reverse( $display ) );
143 return [ $next, $display ];
154 $idSubquery = $dbw->buildSelectSubquery(
157 [
"$userField = actor_user" ],
160 $nameSubquery = $dbw->buildSelectSubquery(
163 [
"$nameField = actor_name" ],
166 return "CASE WHEN $userField = 0 OR $userField IS NULL THEN $nameSubquery ELSE $idSubquery END";
180 IDatabase $dbw, $nameField, array &$rows, array &$complainedAboutUsers, &$countErrors
184 $userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils();
187 foreach ( $rows as $index => $row ) {
188 $keep[$index] =
true;
189 if ( $row->actor_id ===
null ) {
193 $name = $row->$nameField;
194 if ( $userNameUtils->isUsable( $name ) ) {
195 if ( !isset( $complainedAboutUsers[$name] ) ) {
196 $complainedAboutUsers[$name] =
true;
198 "User name \"$name\" is usable, cannot create an anonymous actor for it."
199 .
" Run maintenance/cleanupUsersWithNoId.php to fix this situation.\n"
202 unset( $keep[$index] );
205 $needActors[$name] = 0;
209 $rows = array_intersect_key( $rows, $keep );
214 array_map(
static function ( $v ) {
218 }, array_keys( $needActors ) ),
225 [
'actor_id',
'actor_name' ],
226 [
'actor_name' => array_map(
'strval', array_keys( $needActors ) ) ],
229 foreach (
$res as $row ) {
230 $needActors[$row->actor_name] = $row->actor_id;
232 foreach ( $rows as $row ) {
233 if ( $row->actor_id ===
null ) {
234 $row->actor_id = $needActors[$row->$nameField];
255 protected function migrate( $table, $primaryKey, $userField, $nameField, $actorField ) {
256 if ( !$this->
doTable( $table ) ) {
257 $this->
output(
"Skipping $table, not included in --tables\n" );
262 if ( !$dbw->fieldExists( $table, $userField, __METHOD__ ) ) {
263 $this->
output(
"No need to migrate $table.$userField, field does not exist\n" );
267 $complainedAboutUsers = [];
269 $primaryKey = (array)$primaryKey;
270 $pkFilter = array_fill_keys( $primaryKey,
true );
272 "Beginning migration of $table.$userField and $table.$nameField to $table.$actorField\n"
274 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
275 $lbFactory->waitForReplication();
286 array_merge( $primaryKey, [ $userField, $nameField,
'actor_id' => $actorIdSubquery ] ),
293 'ORDER BY' => $primaryKey,
294 'LIMIT' => $this->mBatchSize,
297 if ( !
$res->numRows() ) {
302 $rows = iterator_to_array(
$res );
303 $lastRow = end( $rows );
305 $dbw, $nameField, $rows, $complainedAboutUsers, $countErrors
309 foreach ( $rows as $row ) {
310 if ( !$row->actor_id ) {
311 list( , $display ) = $this->
makeNextCond( $dbw, $primaryKey, $row );
313 "Could not make actor for row with $display "
314 .
"$userField={$row->$userField} $nameField={$row->$nameField}\n"
322 $actorField => $row->actor_id,
324 array_intersect_key( (array)$row, $pkFilter ) + [
329 $countUpdated += $dbw->affectedRows();
332 list( $next, $display ) = $this->
makeNextCond( $dbw, $primaryKey, $lastRow );
333 $this->
output(
"... $display\n" );
334 $lbFactory->waitForReplication();
338 "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
339 .
"$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" );
370 if ( !$dbw->fieldExists( $table, $userField, __METHOD__ ) ) {
371 $this->
output(
"No need to migrate $table.$userField, field does not exist\n" );
375 $complainedAboutUsers = [];
377 $newTable = $table .
'_actor_temp';
379 "Beginning migration of $table.$userField and $table.$nameField to $newTable.$actorField\n"
381 MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->waitForReplication();
391 [ $table, $newTable ],
392 [ $primaryKey, $userField, $nameField,
'actor_id' => $actorIdSubquery ] + $extra,
393 [ $newPrimaryKey =>
null ] + $next,
396 'ORDER BY' => $primaryKey,
397 'LIMIT' => $this->mBatchSize,
400 $newTable => [
'LEFT JOIN',
"{$primaryKey}={$newPrimaryKey}" ],
403 if ( !
$res->numRows() ) {
408 $rows = iterator_to_array(
$res );
409 $lastRow = end( $rows );
411 $dbw, $nameField, $rows, $complainedAboutUsers, $countErrors
417 foreach ( $rows as $row ) {
418 if ( !$row->actor_id ) {
419 list( , $display ) = $this->
makeNextCond( $dbw, [ $primaryKey ], $row );
421 "Could not make actor for row with $display "
422 .
"$userField={$row->$userField} $nameField={$row->$nameField}\n"
428 $newPrimaryKey => $row->$primaryKey,
429 $actorField => $row->actor_id,
431 foreach ( $extra as $to => $from ) {
433 $ins[$to] = $row->$to;
438 $dbw->insert( $newTable, $inserts, __METHOD__ );
439 $countUpdated += $dbw->affectedRows();
444 list( $n, $display ) = $this->
makeNextCond( $dbw, [ $primaryKey ], $lastRow );
446 $this->
output(
"... $display\n" );
450 "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
451 .
"$countErrors error(s)\n"
461 if ( !$this->
doTable(
'log_search' ) ) {
462 $this->
output(
"Skipping log_search, not included in --tables\n" );
466 $complainedAboutUsers = [];
468 $primaryKey = [
'ls_value',
'ls_log_id' ];
469 $this->
output(
"Beginning migration of log_search\n" );
470 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
471 $lbFactory->waitForReplication();
478 $anyBad = (bool)$dbw->selectField(
'log_search',
'1',
479 [
'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" );
491 $lbFactory->waitForReplication();
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" );
536 $lbFactory->waitForReplication();
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" );
587 $lbFactory->waitForReplication();
591 "Completed migration, inserted $countInserted row(s) with $countActors new actor(s), "
592 .
"$countErrors error(s)\n"
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 transaction on a DB.
commitTransaction(IDatabase $dbw, $fname)
Commit the transaction 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.
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.