]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - maintenance/archives/upgradeLogging.php
MediaWiki 1.30.2-scripts
[autoinstalls/mediawiki.git] / maintenance / archives / upgradeLogging.php
1 <?php
2 /**
3  * Replication-safe online upgrade for log_id/log_deleted fields.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  * @ingroup MaintenanceArchive
22  */
23
24 require __DIR__ . '/../commandLine.inc';
25
26 use Wikimedia\Rdbms\IMaintainableDatabase;
27
28 /**
29  * Maintenance script that upgrade for log_id/log_deleted fields in a
30  * replication-safe way.
31  *
32  * @ingroup Maintenance
33  */
34 class UpdateLogging {
35
36         /**
37          * @var IMaintainableDatabase
38          */
39         public $dbw;
40         public $batchSize = 1000;
41         public $minTs = false;
42
43         function execute() {
44                 $this->dbw = $this->getDB( DB_MASTER );
45                 $logging = $this->dbw->tableName( 'logging' );
46                 $logging_1_10 = $this->dbw->tableName( 'logging_1_10' );
47                 $logging_pre_1_10 = $this->dbw->tableName( 'logging_pre_1_10' );
48
49                 if ( $this->dbw->tableExists( 'logging_pre_1_10' ) && !$this->dbw->tableExists( 'logging' ) ) {
50                         # Fix previous aborted run
51                         echo "Cleaning up from previous aborted run\n";
52                         $this->dbw->query( "RENAME TABLE $logging_pre_1_10 TO $logging", __METHOD__ );
53                 }
54
55                 if ( $this->dbw->tableExists( 'logging_pre_1_10' ) ) {
56                         echo "This script has already been run to completion\n";
57
58                         return;
59                 }
60
61                 # Create the target table
62                 if ( !$this->dbw->tableExists( 'logging_1_10' ) ) {
63                         global $wgDBTableOptions;
64
65                         $sql = <<<EOT
66 CREATE TABLE $logging_1_10 (
67   -- Log ID, for referring to this specific log entry, probably for deletion and such.
68   log_id int unsigned NOT NULL auto_increment,
69
70   -- Symbolic keys for the general log type and the action type
71   -- within the log. The output format will be controlled by the
72   -- action field, but only the type controls categorization.
73   log_type varbinary(10) NOT NULL default '',
74   log_action varbinary(10) NOT NULL default '',
75
76   -- Timestamp. Duh.
77   log_timestamp binary(14) NOT NULL default '19700101000000',
78
79   -- The user who performed this action; key to user_id
80   log_user int unsigned NOT NULL default 0,
81
82   -- Key to the page affected. Where a user is the target,
83   -- this will point to the user page.
84   log_namespace int NOT NULL default 0,
85   log_title varchar(255) binary NOT NULL default '',
86
87   -- Freeform text. Interpreted as edit history comments.
88   log_comment varchar(255) NOT NULL default '',
89
90   -- LF separated list of miscellaneous parameters
91   log_params blob NOT NULL,
92
93   -- rev_deleted for logs
94   log_deleted tinyint unsigned NOT NULL default '0',
95
96   PRIMARY KEY log_id (log_id),
97   KEY type_time (log_type, log_timestamp),
98   KEY user_time (log_user, log_timestamp),
99   KEY page_time (log_namespace, log_title, log_timestamp),
100   KEY times (log_timestamp)
101
102 ) $wgDBTableOptions
103 EOT;
104                         echo "Creating table logging_1_10\n";
105                         $this->dbw->query( $sql, __METHOD__ );
106                 }
107
108                 # Synchronise the tables
109                 echo "Doing initial sync...\n";
110                 $this->sync( 'logging', 'logging_1_10' );
111                 echo "Sync done\n\n";
112
113                 # Rename the old table away
114                 echo "Renaming the old table to $logging_pre_1_10\n";
115                 $this->dbw->query( "RENAME TABLE $logging TO $logging_pre_1_10", __METHOD__ );
116
117                 # Copy remaining old rows
118                 # Done before the new table is active so that $copyPos is accurate
119                 echo "Doing final sync...\n";
120                 $this->sync( 'logging_pre_1_10', 'logging_1_10' );
121
122                 # Move the new table in
123                 echo "Moving the new table in...\n";
124                 $this->dbw->query( "RENAME TABLE $logging_1_10 TO $logging", __METHOD__ );
125                 echo "Finished.\n";
126         }
127
128         /**
129          * Copy all rows from $srcTable to $dstTable
130          * @param string $srcTable
131          * @param string $dstTable
132          */
133         function sync( $srcTable, $dstTable ) {
134                 $batchSize = 1000;
135                 $minTs = $this->dbw->selectField( $srcTable, 'MIN(log_timestamp)', false, __METHOD__ );
136                 $minTsUnix = wfTimestamp( TS_UNIX, $minTs );
137                 $numRowsCopied = 0;
138
139                 while ( true ) {
140                         $maxTs = $this->dbw->selectField( $srcTable, 'MAX(log_timestamp)', false, __METHOD__ );
141                         $copyPos = $this->dbw->selectField( $dstTable, 'MAX(log_timestamp)', false, __METHOD__ );
142                         $maxTsUnix = wfTimestamp( TS_UNIX, $maxTs );
143                         $copyPosUnix = wfTimestamp( TS_UNIX, $copyPos );
144
145                         if ( $copyPos === null ) {
146                                 $percent = 0;
147                         } else {
148                                 $percent = ( $copyPosUnix - $minTsUnix ) / ( $maxTsUnix - $minTsUnix ) * 100;
149                         }
150                         printf( "%s  %.2f%%\n", $copyPos, $percent );
151
152                         # Handle all entries with timestamp equal to $copyPos
153                         if ( $copyPos !== null ) {
154                                 $numRowsCopied += $this->copyExactMatch( $srcTable, $dstTable, $copyPos );
155                         }
156
157                         # Now copy a batch of rows
158                         if ( $copyPos === null ) {
159                                 $conds = false;
160                         } else {
161                                 $conds = [ 'log_timestamp > ' . $this->dbw->addQuotes( $copyPos ) ];
162                         }
163                         $srcRes = $this->dbw->select( $srcTable, '*', $conds, __METHOD__,
164                                 [ 'LIMIT' => $batchSize, 'ORDER BY' => 'log_timestamp' ] );
165
166                         if ( !$srcRes->numRows() ) {
167                                 # All done
168                                 break;
169                         }
170
171                         $batch = [];
172                         foreach ( $srcRes as $srcRow ) {
173                                 $batch[] = (array)$srcRow;
174                         }
175                         $this->dbw->insert( $dstTable, $batch, __METHOD__ );
176                         $numRowsCopied += count( $batch );
177
178                         wfWaitForSlaves();
179                 }
180                 echo "Copied $numRowsCopied rows\n";
181         }
182
183         function copyExactMatch( $srcTable, $dstTable, $copyPos ) {
184                 $numRowsCopied = 0;
185                 $srcRes = $this->dbw->select( $srcTable, '*', [ 'log_timestamp' => $copyPos ], __METHOD__ );
186                 $dstRes = $this->dbw->select( $dstTable, '*', [ 'log_timestamp' => $copyPos ], __METHOD__ );
187
188                 if ( $srcRes->numRows() ) {
189                         $srcRow = $srcRes->fetchObject();
190                         $srcFields = array_keys( (array)$srcRow );
191                         $srcRes->seek( 0 );
192                         $dstRowsSeen = [];
193
194                         # Make a hashtable of rows that already exist in the destination
195                         foreach ( $dstRes as $dstRow ) {
196                                 $reducedDstRow = [];
197                                 foreach ( $srcFields as $field ) {
198                                         $reducedDstRow[$field] = $dstRow->$field;
199                                 }
200                                 $hash = md5( serialize( $reducedDstRow ) );
201                                 $dstRowsSeen[$hash] = true;
202                         }
203
204                         # Copy all the source rows that aren't already in the destination
205                         foreach ( $srcRes as $srcRow ) {
206                                 $hash = md5( serialize( (array)$srcRow ) );
207                                 if ( !isset( $dstRowsSeen[$hash] ) ) {
208                                         $this->dbw->insert( $dstTable, (array)$srcRow, __METHOD__ );
209                                         $numRowsCopied++;
210                                 }
211                         }
212                 }
213
214                 return $numRowsCopied;
215         }
216 }
217
218 $ul = new UpdateLogging;
219 $ul->execute();