Merge "objectcache: convert APC and hash BagOStuff to using mergeViaCas()"
[lhc/web/wiklou.git] / maintenance / includes / MigrateActors.php
index ceba9b5..d9c2072 100644 (file)
@@ -32,9 +32,13 @@ require_once __DIR__ . '/../Maintenance.php';
  * @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 );
        }
 
@@ -42,6 +46,10 @@ class MigrateActors extends LoggedUpdateMaintenance {
                return __CLASS__;
        }
 
+       protected function doTable( $table ) {
+               return $this->tables === null || in_array( $table, $this->tables, true );
+       }
+
        protected function doDBUpdates() {
                global $wgActorTableSchemaMigrationStage;
 
@@ -52,28 +60,51 @@ class MigrateActors extends LoggedUpdateMaintenance {
                        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(
@@ -144,6 +175,8 @@ class MigrateActors extends LoggedUpdateMaintenance {
 
        /**
         * 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
@@ -227,6 +260,11 @@ class MigrateActors extends LoggedUpdateMaintenance {
         * @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;
@@ -323,6 +361,11 @@ class MigrateActors extends LoggedUpdateMaintenance {
        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';
@@ -411,88 +454,83 @@ class MigrateActors extends LoggedUpdateMaintenance {
         * @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();
                }
 
@@ -500,31 +538,17 @@ class MigrateActors extends LoggedUpdateMaintenance {
                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() ) {
@@ -538,45 +562,31 @@ class MigrateActors extends LoggedUpdateMaintenance {
                                $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;