* @ingroup Maintenance
*/
class MigrateActors extends LoggedUpdateMaintenance {
+
+ protected $tables = null;
+
public function __construct() {
parent::__construct();
$this->addDescription( 'Migrates actors from pre-1.31 columns to the \'actor\' table' );
+ $this->addOption( 'tables', 'List of tables to process, comma-separated', false, true );
$this->setBatchSize( 100 );
}
return __CLASS__;
}
+ protected function doTable( $table ) {
+ return $this->tables === null || in_array( $table, $this->tables, true );
+ }
+
protected function doDBUpdates() {
global $wgActorTableSchemaMigrationStage;
return false;
}
- $this->output( "Creating actor entries for all registered users\n" );
- $end = 0;
- $dbw = $this->getDB( DB_MASTER );
- $max = $dbw->selectField( 'user', 'MAX(user_id)', '', __METHOD__ );
- $count = 0;
- while ( $end < $max ) {
- $start = $end + 1;
- $end = min( $start + $this->mBatchSize, $max );
- $this->output( "... $start - $end\n" );
- $dbw->insertSelect(
- 'actor',
- 'user',
- [ 'actor_user' => 'user_id', 'actor_name' => 'user_name' ],
- [ "user_id >= $start", "user_id <= $end" ],
+ $tables = $this->getOption( 'tables' );
+ if ( $tables !== null ) {
+ $this->tables = explode( ',', $tables );
+ }
+
+ if ( $this->doTable( 'user' ) ) {
+ $this->output( "Creating actor entries for all registered users\n" );
+ $end = 0;
+ $dbw = $this->getDB( DB_MASTER );
+ $max = $dbw->selectField( 'user', 'MAX(user_id)', '', __METHOD__ );
+ $count = 0;
+ while ( $end < $max ) {
+ $start = $end + 1;
+ $end = min( $start + $this->mBatchSize, $max );
+ $this->output( "... $start - $end\n" );
+ $dbw->insertSelect(
+ 'actor',
+ 'user',
+ [ 'actor_user' => 'user_id', 'actor_name' => 'user_name' ],
+ [ "user_id >= $start", "user_id <= $end" ],
+ __METHOD__,
+ [ 'IGNORE' ],
+ [ 'ORDER BY' => [ 'user_id' ] ]
+ );
+ $count += $dbw->affectedRows();
+ wfWaitForSlaves();
+ }
+ $this->output( "Completed actor creation, added $count new actor(s)\n" );
+ } else {
+ $this->output( "Checking that actors exist for all registered users\n" );
+ $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
+ $anyMissing = $dbr->selectField(
+ [ 'user', 'actor' ],
+ '1',
+ [ 'actor_id' => null ],
__METHOD__,
- [ 'IGNORE' ],
- [ 'ORDER BY' => [ 'user_id' ] ]
+ [ 'LIMIT 1' ],
+ [ 'actor' => [ 'LEFT JOIN', 'actor_user = user_id' ] ]
);
- $count += $dbw->affectedRows();
- wfWaitForSlaves();
+ if ( $anyMissing ) {
+ $this->error( 'Some users lack actors; run without --tables or include `user` in --tables.' );
+ return false;
+ }
+ $this->output( "Ok, continuing.\n" );
}
- $this->output( "Completed actor creation, added $count new actor(s)\n" );
$errors = 0;
$errors += $this->migrateToTemp(
/**
* Add actors for anons in a set of rows
+ *
+ * @suppress SecurityCheck-SQLInjection The array_keys/array_map is too much for static analysis
* @param IDatabase $dbw
* @param string $nameField
* @param object[] &$rows
* @return int Number of errors
*/
protected function migrate( $table, $primaryKey, $userField, $nameField, $actorField ) {
+ if ( !$this->doTable( $table ) ) {
+ $this->output( "Skipping $table, not included in --tables\n" );
+ return 0;
+ }
+
$complainedAboutUsers = [];
$primaryKey = (array)$primaryKey;
protected function migrateToTemp(
$table, $primaryKey, $extra, $userField, $nameField, $newPrimaryKey, $actorField
) {
+ if ( !$this->doTable( $table ) ) {
+ $this->output( "Skipping $table, not included in --tables\n" );
+ return 0;
+ }
+
$complainedAboutUsers = [];
$newTable = $table . '_actor_temp';
* @return int Number of errors
*/
protected function migrateLogSearch() {
+ if ( !$this->doTable( 'log_search' ) ) {
+ $this->output( "Skipping log_search, not included in --tables\n" );
+ return 0;
+ }
+
$complainedAboutUsers = [];
- $primaryKey = [ 'ls_field', 'ls_value' ];
- $pkFilter = array_flip( $primaryKey );
+ $primaryKey = [ 'ls_value', 'ls_log_id' ];
$this->output( "Beginning migration of log_search\n" );
wfWaitForSlaves();
$dbw = $this->getDB( DB_MASTER );
- $countUpdated = 0;
+ $countInserted = 0;
$countActors = 0;
$countErrors = 0;
+ $anyBad = $dbw->selectField(
+ 'log_search',
+ 1,
+ [ 'ls_field' => 'target_author_actor', 'ls_value' => '' ],
+ __METHOD__,
+ [ 'LIMIT' => 1 ]
+ );
+ if ( $anyBad ) {
+ $this->output( "... Deleting bogus rows due to T21552\n" );
+ $dbw->delete(
+ 'log_search',
+ [ 'ls_field' => 'target_author_actor', 'ls_value' => '' ],
+ __METHOD__
+ );
+ $ct = $dbw->affectedRows();
+ $this->output( "... Deleted $ct bogus row(s) from T21552\n" );
+ wfWaitForSlaves();
+ }
+
$next = '1=1';
while ( true ) {
// Fetch the rows needing update
$res = $dbw->select(
+ [ 'log_search', 'actor' ],
+ [ 'ls_value', 'ls_log_id', 'actor_id' ],
[
- 'ls' => $dbw->buildSelectSubquery(
- 'log_search',
- 'ls_value',
- [
- 'ls_field' => 'target_author_id',
- $next
- ],
- __METHOD__,
- [
- 'DISTINCT',
- 'ORDER BY' => [ 'ls_value' ],
- 'LIMIT' => $this->mBatchSize,
- ]
- ),
- 'actor'
+ 'ls_field' => 'target_author_id',
+ $next
],
+ __METHOD__,
[
- 'ls_field' => $dbw->addQuotes( 'target_author_id' ),
- 'ls_value',
- 'actor_id'
+ 'ORDER BY' => $primaryKey,
+ 'LIMIT' => $this->mBatchSize,
],
- [],
- __METHOD__,
- [],
[ 'actor' => [ 'LEFT JOIN', 'ls_value = ' . $dbw->buildStringCast( 'actor_user' ) ] ]
);
if ( !$res->numRows() ) {
break;
}
- // Update the rows
- $del = [];
+ // Insert a 'target_author_actor' for each 'target_author_id'
+ $ins = [];
foreach ( $res as $row ) {
$lastRow = $row;
if ( !$row->actor_id ) {
list( , $display ) = $this->makeNextCond( $dbw, $primaryKey, $row );
- $this->error( "No actor for row with $display\n" );
+ $this->error( "No actor for target_author_id row with $display\n" );
$countErrors++;
continue;
}
- $dbw->update(
- 'log_search',
- [
- 'ls_field' => 'target_author_actor',
- 'ls_value' => $row->actor_id,
- ],
- [
- 'ls_field' => $row->ls_field,
- 'ls_value' => $row->ls_value,
- ],
- __METHOD__,
- [ 'IGNORE' ]
- );
- $countUpdated += $dbw->affectedRows();
- $del[] = $row->ls_value;
- }
- if ( $del ) {
- $dbw->delete(
- 'log_search', [ 'ls_field' => 'target_author_id', 'ls_value' => $del ], __METHOD__
- );
- $countUpdated += $dbw->affectedRows();
+ $ins[] = [
+ 'ls_field' => 'target_author_actor',
+ 'ls_value' => $row->actor_id,
+ 'ls_log_id' => $row->ls_log_id,
+ ];
}
+ $dbw->insert( 'log_search', $ins, __METHOD__, [ 'IGNORE' ] );
+ $countInserted += $dbw->affectedRows();
list( $next, $display ) = $this->makeNextCond( $dbw, $primaryKey, $lastRow );
- $this->output( "... $display\n" );
+ $this->output( "... target_author_id, $display\n" );
wfWaitForSlaves();
}
while ( true ) {
// Fetch the rows needing update
$res = $dbw->select(
+ [ 'log_search', 'actor' ],
+ [ 'ls_value', 'ls_log_id', 'actor_id' ],
[
- 'ls' => $dbw->buildSelectSubquery(
- 'log_search',
- 'ls_value',
- [
- 'ls_field' => 'target_author_ip',
- $next
- ],
- __METHOD__,
- [
- 'DISTINCT',
- 'ORDER BY' => [ 'ls_value' ],
- 'LIMIT' => $this->mBatchSize,
- ]
- ),
- 'actor'
+ 'ls_field' => 'target_author_ip',
+ $next
],
+ __METHOD__,
[
- 'ls_field' => $dbw->addQuotes( 'target_author_ip' ),
- 'ls_value',
- 'actor_id'
+ 'ORDER BY' => $primaryKey,
+ 'LIMIT' => $this->mBatchSize,
],
- [],
- __METHOD__,
- [],
[ 'actor' => [ 'LEFT JOIN', 'ls_value = actor_name' ] ]
);
if ( !$res->numRows() ) {
$dbw, 'ls_value', $rows, $complainedAboutUsers, $countErrors
);
- // Update the rows
- $del = [];
+ // Insert a 'target_author_actor' for each 'target_author_ip'
+ $ins = [];
foreach ( $rows as $row ) {
if ( !$row->actor_id ) {
list( , $display ) = $this->makeNextCond( $dbw, $primaryKey, $row );
- $this->error( "Could not make actor for row with $display\n" );
+ $this->error( "Could not make actor for target_author_ip row with $display\n" );
$countErrors++;
continue;
}
- $dbw->update(
- 'log_search',
- [
- 'ls_field' => 'target_author_actor',
- 'ls_value' => $row->actor_id,
- ],
- [
- 'ls_field' => $row->ls_field,
- 'ls_value' => $row->ls_value,
- ],
- __METHOD__,
- [ 'IGNORE' ]
- );
- $countUpdated += $dbw->affectedRows();
- $del[] = $row->ls_value;
- }
- if ( $del ) {
- $dbw->delete(
- 'log_search', [ 'ls_field' => 'target_author_ip', 'ls_value' => $del ], __METHOD__
- );
- $countUpdated += $dbw->affectedRows();
+ $ins[] = [
+ 'ls_field' => 'target_author_actor',
+ 'ls_value' => $row->actor_id,
+ 'ls_log_id' => $row->ls_log_id,
+ ];
}
+ $dbw->insert( 'log_search', $ins, __METHOD__, [ 'IGNORE' ] );
+ $countInserted += $dbw->affectedRows();
list( $next, $display ) = $this->makeNextCond( $dbw, $primaryKey, $lastRow );
- $this->output( "... $display\n" );
+ $this->output( "... target_author_ip, $display\n" );
wfWaitForSlaves();
}
$this->output(
- "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
+ "Completed migration, inserted $countInserted row(s) with $countActors new actor(s), "
. "$countErrors error(s)\n"
);
return $countErrors;