(bug 37638) Remove calls to deprecated LogPage::logName method.
[lhc/web/wiklou.git] / maintenance / upgrade1_5.php
1 <?php
2 /**
3 * Alternate 1.4 -> 1.5 schema upgrade.
4 * This does only the main tables + UTF-8 and is designed to allow upgrades to
5 * interleave with other updates on the replication stream so that large wikis
6 * can be upgraded without disrupting other services.
7 *
8 * Note: this script DOES NOT apply every update, nor will it probably handle
9 * much older versions, etc.
10 * Run this, FOLLOWED BY update.php, for upgrading from 1.4.5 release to 1.5.
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License along
23 * with this program; if not, write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 * http://www.gnu.org/copyleft/gpl.html
26 *
27 * @file
28 * @ingroup Maintenance
29 */
30
31 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
32
33 define( 'MW_UPGRADE_COPY', false );
34 define( 'MW_UPGRADE_ENCODE', true );
35 define( 'MW_UPGRADE_NULL', null );
36 define( 'MW_UPGRADE_CALLBACK', null ); // for self-documentation only
37
38 /**
39 * @ingroup Maintenance
40 */
41 class FiveUpgrade extends Maintenance {
42
43 /**
44 * @var DatabaseBase
45 */
46 protected $db;
47
48 function __construct() {
49 parent::__construct();
50
51 $this->mDescription = 'Script for upgrades from 1.4 to 1.5 (NOT 1.15) in very special cases.';
52
53 $this->addOption( 'upgrade', 'Really run the script' );
54 $this->addOption( 'noimage', '' );
55 $this->addOption( 'step', 'Only do a specific step', false, true );
56 }
57
58 public function getDbType() {
59 return Maintenance::DB_ADMIN;
60 }
61
62 public function execute() {
63 $this->output( "ATTENTION: This script is for upgrades from 1.4 to 1.5 (NOT 1.15) in very special cases.\n" );
64 $this->output( "Use update.php for usual updates.\n" );
65
66 if ( !$this->hasOption( 'upgrade' ) ) {
67 $this->output( "Please run this script with --upgrade key to actually run the updater.\n" );
68 return;
69 }
70
71 $this->setMembers();
72
73 $tables = array(
74 'page',
75 'links',
76 'user',
77 'image',
78 'oldimage',
79 'watchlist',
80 'logging',
81 'archive',
82 'imagelinks',
83 'categorylinks',
84 'ipblocks',
85 'recentchanges',
86 'querycache'
87 );
88
89 foreach ( $tables as $table ) {
90 if ( $this->doing( $table ) ) {
91 $method = 'upgrade' . ucfirst( $table );
92 $this->$method();
93 }
94 }
95
96 if ( $this->doing( 'cleanup' ) ) {
97 $this->upgradeCleanup();
98 }
99 }
100
101 protected function setMembers() {
102 $this->conversionTables = $this->prepareWindows1252();
103
104 $this->loadBalancers = array();
105 $this->dbw = wfGetDB( DB_MASTER );
106 $this->dbr = $this->streamConnection();
107
108 $this->cleanupSwaps = array();
109 $this->emailAuth = false; # don't preauthenticate emails
110 $this->step = $this->getOption( 'step', null );
111 }
112
113 function doing( $step ) {
114 return is_null( $this->step ) || $step == $this->step;
115 }
116
117 /**
118 * Open a connection to the master server with the admin rights.
119 * @return DatabaseBase
120 * @access private
121 */
122 function newConnection() {
123 $lb = wfGetLBFactory()->newMainLB();
124 $db = $lb->getConnection( DB_MASTER );
125
126 $this->loadBalancers[] = $lb;
127 return $db;
128 }
129
130 /**
131 * Commit transactions and close the connections when we're done...
132 */
133 function close() {
134 foreach ( $this->loadBalancers as $lb ) {
135 $lb->commitMasterChanges();
136 $lb->closeAll();
137 }
138 }
139
140 /**
141 * Open a second connection to the master server, with buffering off.
142 * This will let us stream large datasets in and write in chunks on the
143 * other end.
144 * @return DatabaseBase
145 * @access private
146 */
147 function streamConnection() {
148 $timeout = 3600 * 24;
149 $db = $this->newConnection();
150 $db->bufferResults( false );
151 if ( $db->getType() == 'mysql' ) {
152 $db->query( "SET net_read_timeout=$timeout" );
153 $db->query( "SET net_write_timeout=$timeout" );
154 }
155 return $db;
156 }
157
158 /**
159 * Prepare a conversion array for converting Windows Code Page 1252 to
160 * UTF-8. This should provide proper conversion of text that was miscoded
161 * as Windows-1252 by naughty user-agents, and doesn't rely on an outside
162 * iconv library.
163 *
164 * @return array
165 * @access private
166 */
167 function prepareWindows1252() {
168 # Mappings from:
169 # http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT
170 static $cp1252 = array(
171 0x80 => 0x20AC, # EURO SIGN
172 0x81 => 0xFFFD, # REPLACEMENT CHARACTER (no mapping)
173 0x82 => 0x201A, # SINGLE LOW-9 QUOTATION MARK
174 0x83 => 0x0192, # LATIN SMALL LETTER F WITH HOOK
175 0x84 => 0x201E, # DOUBLE LOW-9 QUOTATION MARK
176 0x85 => 0x2026, # HORIZONTAL ELLIPSIS
177 0x86 => 0x2020, # DAGGER
178 0x87 => 0x2021, # DOUBLE DAGGER
179 0x88 => 0x02C6, # MODIFIER LETTER CIRCUMFLEX ACCENT
180 0x89 => 0x2030, # PER MILLE SIGN
181 0x8A => 0x0160, # LATIN CAPITAL LETTER S WITH CARON
182 0x8B => 0x2039, # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
183 0x8C => 0x0152, # LATIN CAPITAL LIGATURE OE
184 0x8D => 0xFFFD, # REPLACEMENT CHARACTER (no mapping)
185 0x8E => 0x017D, # LATIN CAPITAL LETTER Z WITH CARON
186 0x8F => 0xFFFD, # REPLACEMENT CHARACTER (no mapping)
187 0x90 => 0xFFFD, # REPLACEMENT CHARACTER (no mapping)
188 0x91 => 0x2018, # LEFT SINGLE QUOTATION MARK
189 0x92 => 0x2019, # RIGHT SINGLE QUOTATION MARK
190 0x93 => 0x201C, # LEFT DOUBLE QUOTATION MARK
191 0x94 => 0x201D, # RIGHT DOUBLE QUOTATION MARK
192 0x95 => 0x2022, # BULLET
193 0x96 => 0x2013, # EN DASH
194 0x97 => 0x2014, # EM DASH
195 0x98 => 0x02DC, # SMALL TILDE
196 0x99 => 0x2122, # TRADE MARK SIGN
197 0x9A => 0x0161, # LATIN SMALL LETTER S WITH CARON
198 0x9B => 0x203A, # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
199 0x9C => 0x0153, # LATIN SMALL LIGATURE OE
200 0x9D => 0xFFFD, # REPLACEMENT CHARACTER (no mapping)
201 0x9E => 0x017E, # LATIN SMALL LETTER Z WITH CARON
202 0x9F => 0x0178, # LATIN CAPITAL LETTER Y WITH DIAERESIS
203 );
204 $pairs = array();
205 for ( $i = 0; $i < 0x100; $i++ ) {
206 $unicode = isset( $cp1252[$i] ) ? $cp1252[$i] : $i;
207 $pairs[chr( $i )] = codepointToUtf8( $unicode );
208 }
209 return $pairs;
210 }
211
212 /**
213 * Convert from 8-bit Windows-1252 to UTF-8 if necessary.
214 * @param string $text
215 * @return string
216 * @access private
217 */
218 function conv( $text ) {
219 global $wgUseLatin1;
220 return is_null( $text )
221 ? null
222 : ( $wgUseLatin1
223 ? strtr( $text, $this->conversionTables )
224 : $text );
225 }
226
227 /**
228 * Dump timestamp and message to output
229 * @param $message String
230 * @access private
231 */
232 function log( $message ) {
233 $this->output( wfWikiID() . ' ' . wfTimestamp( TS_DB ) . ': ' . $message . "\n" );
234 }
235
236 /**
237 * Initialize the chunked-insert system.
238 * Rows will be inserted in chunks of the given number, rather
239 * than in a giant INSERT...SELECT query, to keep the serialized
240 * MySQL database replication from getting hung up. This way other
241 * things can be going on during conversion without waiting for
242 * slaves to catch up as badly.
243 *
244 * @param int $chunksize Number of rows to insert at once
245 * @param int $final Total expected number of rows / id of last row,
246 * used for progress reports.
247 * @param string $table to insert on
248 * @param string $fname function name to report in SQL
249 * @access private
250 */
251 function setChunkScale( $chunksize, $final, $table, $fname ) {
252 $this->chunkSize = $chunksize;
253 $this->chunkFinal = $final;
254 $this->chunkCount = 0;
255 $this->chunkStartTime = wfTime();
256 $this->chunkOptions = array( 'IGNORE' );
257 $this->chunkTable = $table;
258 $this->chunkFunction = $fname;
259 }
260
261 /**
262 * Chunked inserts: perform an insert if we've reached the chunk limit.
263 * Prints a progress report with estimated completion time.
264 * @param array &$chunk -- This will be emptied if an insert is done.
265 * @param int $key A key identifier to use in progress estimation in
266 * place of the number of rows inserted. Use this if
267 * you provided a max key number instead of a count
268 * as the final chunk number in setChunkScale()
269 * @access private
270 */
271 function addChunk( &$chunk, $key = null ) {
272 if ( count( $chunk ) >= $this->chunkSize ) {
273 $this->insertChunk( $chunk );
274
275 $this->chunkCount += count( $chunk );
276 $now = wfTime();
277 $delta = $now - $this->chunkStartTime;
278 $rate = $this->chunkCount / $delta;
279
280 if ( is_null( $key ) ) {
281 $completed = $this->chunkCount;
282 } else {
283 $completed = $key;
284 }
285 $portion = $completed / $this->chunkFinal;
286
287 $estimatedTotalTime = $delta / $portion;
288 $eta = $this->chunkStartTime + $estimatedTotalTime;
289
290 printf( "%s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec\n",
291 wfTimestamp( TS_DB, intval( $now ) ),
292 $portion * 100.0,
293 $this->chunkTable,
294 wfTimestamp( TS_DB, intval( $eta ) ),
295 $completed,
296 $this->chunkFinal,
297 $rate );
298 flush();
299
300 $chunk = array();
301 }
302 }
303
304 /**
305 * Chunked inserts: perform an insert unconditionally, at the end, and log.
306 * @param array &$chunk -- This will be emptied if an insert is done.
307 * @access private
308 */
309 function lastChunk( &$chunk ) {
310 $n = count( $chunk );
311 if ( $n > 0 ) {
312 $this->insertChunk( $chunk );
313 }
314 $this->log( "100.00% done on $this->chunkTable (last chunk $n rows)." );
315 }
316
317 /**
318 * Chunked inserts: perform an insert.
319 * @param array &$chunk -- This will be emptied if an insert is done.
320 * @access private
321 */
322 function insertChunk( &$chunk ) {
323 // Give slaves a chance to catch up
324 wfWaitForSlaves();
325 $this->dbw->insert( $this->chunkTable, $chunk, $this->chunkFunction, $this->chunkOptions );
326 }
327
328 /**
329 * Helper function for copyTable array_filter
330 * @param $x
331 * @return bool
332 */
333 static private function notUpgradeNull( $x ) {
334 return $x !== MW_UPGRADE_NULL;
335 }
336
337 /**
338 * Copy and transcode a table to table_temp.
339 * @param string $name Base name of the source table
340 * @param string $tabledef CREATE TABLE definition, w/ $1 for the name
341 * @param array $fields set of destination fields to these constants:
342 * MW_UPGRADE_COPY - straight copy
343 * MW_UPGRADE_ENCODE - for old Latin1 wikis, conv to UTF-8
344 * MW_UPGRADE_NULL - just put NULL
345 * @param $callback callback An optional callback to modify the data
346 * or perform other processing. Func should be
347 * ( object $row, array $copy ) and return $copy
348 * @access private
349 */
350 function copyTable( $name, $tabledef, $fields, $callback = null ) {
351 $name_temp = $name . '_temp';
352 $this->log( "Migrating $name table to $name_temp..." );
353
354 $table_temp = $this->dbw->tableName( $name_temp );
355
356 // Create temporary table; we're going to copy everything in there,
357 // then at the end rename the final tables into place.
358 $def = str_replace( '$1', $table_temp, $tabledef );
359 $this->dbw->query( $def, __METHOD__ );
360
361 $numRecords = $this->dbw->selectField( $name, 'COUNT(*)', '', __METHOD__ );
362 $this->setChunkScale( 100, $numRecords, $name_temp, __METHOD__ );
363
364 // Pull all records from the second, streaming database connection.
365 $sourceFields = array_keys( array_filter( $fields, 'FiveUpgrade::notUpgradeNull' ) );
366 $result = $this->dbr->select( $name,
367 $sourceFields,
368 '',
369 __METHOD__ );
370
371 $add = array();
372 foreach ( $result as $row ) {
373 $copy = array();
374 foreach ( $fields as $field => $source ) {
375 if ( $source === MW_UPGRADE_COPY ) {
376 $copy[$field] = $row->$field;
377 } elseif ( $source === MW_UPGRADE_ENCODE ) {
378 $copy[$field] = $this->conv( $row->$field );
379 } elseif ( $source === MW_UPGRADE_NULL ) {
380 $copy[$field] = null;
381 } else {
382 $this->log( "Unknown field copy type: $field => $source" );
383 }
384 }
385 if ( is_callable( $callback ) ) {
386 $copy = call_user_func( $callback, $row, $copy );
387 }
388 $add[] = $copy;
389 $this->addChunk( $add );
390 }
391 $this->lastChunk( $add );
392
393 $this->log( "Done converting $name." );
394 $this->cleanupSwaps[] = $name;
395 }
396
397 function upgradePage() {
398 $chunksize = 100;
399
400 if ( $this->dbw->tableExists( 'page' ) ) {
401 $this->error( 'Page table already exists.', true );
402 }
403
404 $this->log( "Checking cur table for unique title index and applying if necessary" );
405 $this->checkDupes();
406
407 $this->log( "...converting from cur/old to page/revision/text DB structure." );
408
409 list ( $cur, $old, $page, $revision, $text ) = $this->dbw->tableNamesN( 'cur', 'old', 'page', 'revision', 'text' );
410
411 $this->log( "Creating page and revision tables..." );
412 $this->dbw->query( "CREATE TABLE $page (
413 page_id int(8) unsigned NOT NULL auto_increment,
414 page_namespace int NOT NULL,
415 page_title varchar(255) binary NOT NULL,
416 page_restrictions tinyblob NOT NULL default '',
417 page_counter bigint(20) unsigned NOT NULL default '0',
418 page_is_redirect tinyint(1) unsigned NOT NULL default '0',
419 page_is_new tinyint(1) unsigned NOT NULL default '0',
420 page_random real unsigned NOT NULL,
421 page_touched char(14) binary NOT NULL default '',
422 page_latest int(8) unsigned NOT NULL,
423 page_len int(8) unsigned NOT NULL,
424
425 PRIMARY KEY page_id (page_id),
426 UNIQUE INDEX name_title (page_namespace,page_title),
427 INDEX (page_random),
428 INDEX (page_len)
429 ) TYPE=InnoDB", __METHOD__ );
430 $this->dbw->query( "CREATE TABLE $revision (
431 rev_id int(8) unsigned NOT NULL auto_increment,
432 rev_page int(8) unsigned NOT NULL,
433 rev_text_id int(8) unsigned NOT NULL,
434 rev_comment tinyblob NOT NULL default '',
435 rev_user int(5) unsigned NOT NULL default '0',
436 rev_user_text varchar(255) binary NOT NULL default '',
437 rev_timestamp char(14) binary NOT NULL default '',
438 rev_minor_edit tinyint(1) unsigned NOT NULL default '0',
439 rev_deleted tinyint(1) unsigned NOT NULL default '0',
440
441 PRIMARY KEY rev_page_id (rev_page, rev_id),
442 UNIQUE INDEX rev_id (rev_id),
443 INDEX rev_timestamp (rev_timestamp),
444 INDEX page_timestamp (rev_page,rev_timestamp),
445 INDEX user_timestamp (rev_user,rev_timestamp),
446 INDEX usertext_timestamp (rev_user_text,rev_timestamp)
447 ) TYPE=InnoDB", __METHOD__ );
448
449 $maxold = intval( $this->dbw->selectField( 'old', 'max(old_id)', '', __METHOD__ ) );
450 $this->log( "Last old record is {$maxold}" );
451
452 global $wgLegacySchemaConversion;
453 if ( $wgLegacySchemaConversion ) {
454 // Create HistoryBlobCurStub entries.
455 // Text will be pulled from the leftover 'cur' table at runtime.
456 echo "......Moving metadata from cur; using blob references to text in cur table.\n";
457 $cur_text = "concat('O:18:\"historyblobcurstub\":1:{s:6:\"mCurId\";i:',cur_id,';}')";
458 $cur_flags = "'object'";
459 } else {
460 // Copy all cur text in immediately: this may take longer but avoids
461 // having to keep an extra table around.
462 echo "......Moving text from cur.\n";
463 $cur_text = 'cur_text';
464 $cur_flags = "''";
465 }
466
467 $maxcur = $this->dbw->selectField( 'cur', 'max(cur_id)', '', __METHOD__ );
468 $this->log( "Last cur entry is $maxcur" );
469
470 /**
471 * Copy placeholder records for each page's current version into old
472 * Don't do any conversion here; text records are converted at runtime
473 * based on the flags (and may be originally binary!) while the meta
474 * fields will be converted in the old -> rev and cur -> page steps.
475 */
476 $this->setChunkScale( $chunksize, $maxcur, 'old', __METHOD__ );
477 $result = $this->dbr->query(
478 "SELECT cur_id, cur_namespace, cur_title, $cur_text AS text, cur_comment,
479 cur_user, cur_user_text, cur_timestamp, cur_minor_edit, $cur_flags AS flags
480 FROM $cur
481 ORDER BY cur_id", __METHOD__ );
482 $add = array();
483 foreach ( $result as $row ) {
484 $add[] = array(
485 'old_namespace' => $row->cur_namespace,
486 'old_title' => $row->cur_title,
487 'old_text' => $row->text,
488 'old_comment' => $row->cur_comment,
489 'old_user' => $row->cur_user,
490 'old_user_text' => $row->cur_user_text,
491 'old_timestamp' => $row->cur_timestamp,
492 'old_minor_edit' => $row->cur_minor_edit,
493 'old_flags' => $row->flags );
494 $this->addChunk( $add, $row->cur_id );
495 }
496 $this->lastChunk( $add );
497
498 /**
499 * Copy revision metadata from old into revision.
500 * We'll also do UTF-8 conversion of usernames and comments.
501 */
502 # $newmaxold = $this->dbw->selectField( 'old', 'max(old_id)', '', __METHOD__ );
503 # $this->setChunkScale( $chunksize, $newmaxold, 'revision', __METHOD__ );
504 # $countold = $this->dbw->selectField( 'old', 'count(old_id)', '', __METHOD__ );
505 $countold = $this->dbw->selectField( 'old', 'max(old_id)', '', __METHOD__ );
506 $this->setChunkScale( $chunksize, $countold, 'revision', __METHOD__ );
507
508 $this->log( "......Setting up revision table." );
509 $result = $this->dbr->query(
510 "SELECT old_id, cur_id, old_comment, old_user, old_user_text,
511 old_timestamp, old_minor_edit
512 FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title",
513 __METHOD__ );
514
515 $add = array();
516 foreach ( $result as $row ) {
517 $add[] = array(
518 'rev_id' => $row->old_id,
519 'rev_page' => $row->cur_id,
520 'rev_text_id' => $row->old_id,
521 'rev_comment' => $this->conv( $row->old_comment ),
522 'rev_user' => $row->old_user,
523 'rev_user_text' => $this->conv( $row->old_user_text ),
524 'rev_timestamp' => $row->old_timestamp,
525 'rev_minor_edit' => $row->old_minor_edit );
526 $this->addChunk( $add );
527 }
528 $this->lastChunk( $add );
529
530
531 /**
532 * Copy page metadata from cur into page.
533 * We'll also do UTF-8 conversion of titles.
534 */
535 $this->log( "......Setting up page table." );
536 $this->setChunkScale( $chunksize, $maxcur, 'page', __METHOD__ );
537 $result = $this->dbr->query( "
538 SELECT cur_id, cur_namespace, cur_title, cur_restrictions, cur_counter, cur_is_redirect, cur_is_new,
539 cur_random, cur_touched, rev_id, LENGTH(cur_text) AS len
540 FROM $cur,$revision
541 WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}
542 ORDER BY cur_id", __METHOD__ );
543 $add = array();
544 foreach ( $result as $row ) {
545 $add[] = array(
546 'page_id' => $row->cur_id,
547 'page_namespace' => $row->cur_namespace,
548 'page_title' => $this->conv( $row->cur_title ),
549 'page_restrictions' => $row->cur_restrictions,
550 'page_counter' => $row->cur_counter,
551 'page_is_redirect' => $row->cur_is_redirect,
552 'page_is_new' => $row->cur_is_new,
553 'page_random' => $row->cur_random,
554 'page_touched' => $this->dbw->timestamp(),
555 'page_latest' => $row->rev_id,
556 'page_len' => $row->len );
557 # $this->addChunk( $add, $row->cur_id );
558 $this->addChunk( $add );
559 }
560 $this->lastChunk( $add );
561
562 $this->log( "...done with cur/old -> page/revision." );
563 }
564
565 function upgradeLinks() {
566 $chunksize = 200;
567 list ( $links, $brokenlinks, $pagelinks, $cur ) = $this->dbw->tableNamesN( 'links', 'brokenlinks', 'pagelinks', 'cur' );
568
569 $this->log( 'Checking for interwiki table change in case of bogus items...' );
570 if ( $this->dbw->fieldExists( 'interwiki', 'iw_trans' ) ) {
571 $this->log( 'interwiki has iw_trans.' );
572 } else {
573 global $IP;
574 $this->log( 'adding iw_trans...' );
575 $this->dbw->sourceFile( $IP . '/maintenance/archives/patch-interwiki-trans.sql' );
576 $this->log( 'added iw_trans.' );
577 }
578
579 $this->log( 'Creating pagelinks table...' );
580 $this->dbw->query( "
581 CREATE TABLE $pagelinks (
582 -- Key to the page_id of the page containing the link.
583 pl_from int(8) unsigned NOT NULL default '0',
584
585 -- Key to page_namespace/page_title of the target page.
586 -- The target page may or may not exist, and due to renames
587 -- and deletions may refer to different page records as time
588 -- goes by.
589 pl_namespace int NOT NULL default '0',
590 pl_title varchar(255) binary NOT NULL default '',
591
592 UNIQUE KEY pl_from(pl_from,pl_namespace,pl_title),
593 KEY (pl_namespace,pl_title)
594
595 ) TYPE=InnoDB" );
596
597 $this->log( 'Importing live links -> pagelinks' );
598 $nlinks = $this->dbw->selectField( 'links', 'count(*)', '', __METHOD__ );
599 if ( $nlinks ) {
600 $this->setChunkScale( $chunksize, $nlinks, 'pagelinks', __METHOD__ );
601 $result = $this->dbr->query( "
602 SELECT l_from,cur_namespace,cur_title
603 FROM $links, $cur
604 WHERE l_to=cur_id", __METHOD__ );
605 $add = array();
606 foreach ( $result as $row ) {
607 $add[] = array(
608 'pl_from' => $row->l_from,
609 'pl_namespace' => $row->cur_namespace,
610 'pl_title' => $this->conv( $row->cur_title ) );
611 $this->addChunk( $add );
612 }
613 $this->lastChunk( $add );
614 } else {
615 $this->log( 'no links!' );
616 }
617
618 $this->log( 'Importing brokenlinks -> pagelinks' );
619 $nbrokenlinks = $this->dbw->selectField( 'brokenlinks', 'count(*)', '', __METHOD__ );
620 if ( $nbrokenlinks ) {
621 $this->setChunkScale( $chunksize, $nbrokenlinks, 'pagelinks', __METHOD__ );
622 $result = $this->dbr->query(
623 "SELECT bl_from, bl_to FROM $brokenlinks",
624 __METHOD__ );
625 $add = array();
626 foreach ( $result as $row ) {
627 $pagename = $this->conv( $row->bl_to );
628 $title = Title::newFromText( $pagename );
629 if ( is_null( $title ) ) {
630 $this->log( "** invalid brokenlink: $row->bl_from -> '$pagename' (converted from '$row->bl_to')" );
631 } else {
632 $add[] = array(
633 'pl_from' => $row->bl_from,
634 'pl_namespace' => $title->getNamespace(),
635 'pl_title' => $title->getDBkey() );
636 $this->addChunk( $add );
637 }
638 }
639 $this->lastChunk( $add );
640 } else {
641 $this->log( 'no brokenlinks!' );
642 }
643
644 $this->log( 'Done with links.' );
645 }
646
647 function userDupeCallback( $str ) {
648 echo $str;
649 }
650
651 function upgradeUser() {
652 // Apply unique index, if necessary:
653 $duper = new UserDupes( $this->dbw, array( $this, 'userDupeCallback' ) );
654 if ( $duper->hasUniqueIndex() ) {
655 $this->log( "Already have unique user_name index." );
656 } else {
657 $this->log( "Clearing user duplicates..." );
658 if ( !$duper->clearDupes() ) {
659 $this->log( "WARNING: Duplicate user accounts, may explode!" );
660 }
661 }
662
663 $tabledef = <<<END
664 CREATE TABLE $1 (
665 user_id int(5) unsigned NOT NULL auto_increment,
666 user_name varchar(255) binary NOT NULL default '',
667 user_real_name varchar(255) binary NOT NULL default '',
668 user_password tinyblob NOT NULL default '',
669 user_newpassword tinyblob NOT NULL default '',
670 user_email tinytext NOT NULL default '',
671 user_options blob NOT NULL default '',
672 user_touched char(14) binary NOT NULL default '',
673 user_token char(32) binary NOT NULL default '',
674 user_email_authenticated CHAR(14) BINARY,
675 user_email_token CHAR(32) BINARY,
676 user_email_token_expires CHAR(14) BINARY,
677
678 PRIMARY KEY user_id (user_id),
679 UNIQUE INDEX user_name (user_name),
680 INDEX (user_email_token)
681
682 ) TYPE=InnoDB
683 END;
684 $fields = array(
685 'user_id' => MW_UPGRADE_COPY,
686 'user_name' => MW_UPGRADE_ENCODE,
687 'user_real_name' => MW_UPGRADE_ENCODE,
688 'user_password' => MW_UPGRADE_COPY,
689 'user_newpassword' => MW_UPGRADE_COPY,
690 'user_email' => MW_UPGRADE_ENCODE,
691 'user_options' => MW_UPGRADE_ENCODE,
692 'user_touched' => MW_UPGRADE_CALLBACK,
693 'user_token' => MW_UPGRADE_COPY,
694 'user_email_authenticated' => MW_UPGRADE_CALLBACK,
695 'user_email_token' => MW_UPGRADE_NULL,
696 'user_email_token_expires' => MW_UPGRADE_NULL );
697 $this->copyTable( 'user', $tabledef, $fields,
698 array( &$this, 'userCallback' ) );
699 }
700
701 function userCallback( $row, $copy ) {
702 $now = $this->dbw->timestamp();
703 $copy['user_touched'] = $now;
704 $copy['user_email_authenticated'] = $this->emailAuth ? $now : null;
705 return $copy;
706 }
707
708 function upgradeImage() {
709 $tabledef = <<<END
710 CREATE TABLE $1 (
711 img_name varchar(255) binary NOT NULL default '',
712 img_size int(8) unsigned NOT NULL default '0',
713 img_width int(5) NOT NULL default '0',
714 img_height int(5) NOT NULL default '0',
715 img_metadata mediumblob NOT NULL,
716 img_bits int(3) NOT NULL default '0',
717 img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
718 img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
719 img_minor_mime varchar(32) NOT NULL default "unknown",
720 img_description tinyblob NOT NULL default '',
721 img_user int(5) unsigned NOT NULL default '0',
722 img_user_text varchar(255) binary NOT NULL default '',
723 img_timestamp char(14) binary NOT NULL default '',
724
725 PRIMARY KEY img_name (img_name),
726 INDEX img_size (img_size),
727 INDEX img_timestamp (img_timestamp)
728 ) TYPE=InnoDB
729 END;
730 $fields = array(
731 'img_name' => MW_UPGRADE_ENCODE,
732 'img_size' => MW_UPGRADE_COPY,
733 'img_width' => MW_UPGRADE_CALLBACK,
734 'img_height' => MW_UPGRADE_CALLBACK,
735 'img_metadata' => MW_UPGRADE_CALLBACK,
736 'img_bits' => MW_UPGRADE_CALLBACK,
737 'img_media_type' => MW_UPGRADE_CALLBACK,
738 'img_major_mime' => MW_UPGRADE_CALLBACK,
739 'img_minor_mime' => MW_UPGRADE_CALLBACK,
740 'img_description' => MW_UPGRADE_ENCODE,
741 'img_user' => MW_UPGRADE_COPY,
742 'img_user_text' => MW_UPGRADE_ENCODE,
743 'img_timestamp' => MW_UPGRADE_COPY );
744 $this->copyTable( 'image', $tabledef, $fields,
745 array( &$this, 'imageCallback' ) );
746 }
747
748 function imageCallback( $row, $copy ) {
749 if ( !$this->hasOption( 'noimage' ) ) {
750 // Fill in the new image info fields
751 $info = $this->imageInfo( $row->img_name );
752
753 $copy['img_width' ] = $info['width'];
754 $copy['img_height' ] = $info['height'];
755 $copy['img_metadata' ] = ""; // loaded on-demand
756 $copy['img_bits' ] = $info['bits'];
757 $copy['img_media_type'] = $info['media'];
758 $copy['img_major_mime'] = $info['major'];
759 $copy['img_minor_mime'] = $info['minor'];
760 }
761
762 // If doing UTF8 conversion the file must be renamed
763 $this->renameFile( $row->img_name, 'wfImageDir' );
764
765 return $copy;
766 }
767
768 function imageInfo( $filename ) {
769 $info = array(
770 'width' => 0,
771 'height' => 0,
772 'bits' => 0,
773 'media' => '',
774 'major' => '',
775 'minor' => '' );
776
777 $magic = MimeMagic::singleton();
778 $mime = $magic->guessMimeType( $filename, true );
779 list( $info['major'], $info['minor'] ) = explode( '/', $mime );
780
781 $info['media'] = $magic->getMediaType( $filename, $mime );
782
783 $image = UnregisteredLocalFile::newFromPath( $filename, $mime );
784
785 $info['width'] = $image->getWidth();
786 $info['height'] = $image->getHeight();
787
788 $gis = $image->getImageSize( $filename );
789 if ( isset( $gis['bits'] ) ) {
790 $info['bits'] = $gis['bits'];
791 }
792
793 return $info;
794 }
795
796
797 /**
798 * Truncate a table.
799 * @param string $table The table name to be truncated
800 */
801 function clearTable( $table ) {
802 print "Clearing $table...\n";
803 $tableName = $this->db->tableName( $table );
804 $this->db->query( "TRUNCATE $tableName" );
805 }
806
807 /**
808 * Rename a given image or archived image file to the converted filename,
809 * leaving a symlink for URL compatibility.
810 *
811 * @param $oldname string pre-conversion filename
812 * @param $subdirCallback string
813 * @param $basename string pre-conversion base filename for dir hashing, if an archive
814 * @return bool|string
815 * @access private
816 */
817 function renameFile( $oldname, $subdirCallback = 'wfImageDir', $basename = null ) {
818 $newname = $this->conv( $oldname );
819 if ( $newname == $oldname ) {
820 // No need to rename; another field triggered this row.
821 return false;
822 }
823
824 if ( is_null( $basename ) ) $basename = $oldname;
825 $ubasename = $this->conv( $basename );
826 $oldpath = call_user_func( $subdirCallback, $basename ) . '/' . $oldname;
827 $newpath = call_user_func( $subdirCallback, $ubasename ) . '/' . $newname;
828
829 $this->log( "$oldpath -> $newpath" );
830 if ( rename( $oldpath, $newpath ) ) {
831 $relpath = wfRelativePath( $newpath, dirname( $oldpath ) );
832 if ( !symlink( $relpath, $oldpath ) ) {
833 $this->log( "... symlink failed!" );
834 }
835 return $newname;
836 } else {
837 $this->log( "... rename failed!" );
838 return false;
839 }
840 }
841
842 function upgradeOldImage() {
843 $tabledef = <<<END
844 CREATE TABLE $1 (
845 -- Base filename: key to image.img_name
846 oi_name varchar(255) binary NOT NULL default '',
847
848 -- Filename of the archived file.
849 -- This is generally a timestamp and '!' prepended to the base name.
850 oi_archive_name varchar(255) binary NOT NULL default '',
851
852 -- Other fields as in image...
853 oi_size int(8) unsigned NOT NULL default 0,
854 oi_width int(5) NOT NULL default 0,
855 oi_height int(5) NOT NULL default 0,
856 oi_bits int(3) NOT NULL default 0,
857 oi_description tinyblob NOT NULL default '',
858 oi_user int(5) unsigned NOT NULL default '0',
859 oi_user_text varchar(255) binary NOT NULL default '',
860 oi_timestamp char(14) binary NOT NULL default '',
861
862 INDEX oi_name (oi_name(10))
863
864 ) TYPE=InnoDB;
865 END;
866 $fields = array(
867 'oi_name' => MW_UPGRADE_ENCODE,
868 'oi_archive_name' => MW_UPGRADE_ENCODE,
869 'oi_size' => MW_UPGRADE_COPY,
870 'oi_width' => MW_UPGRADE_CALLBACK,
871 'oi_height' => MW_UPGRADE_CALLBACK,
872 'oi_bits' => MW_UPGRADE_CALLBACK,
873 'oi_description' => MW_UPGRADE_ENCODE,
874 'oi_user' => MW_UPGRADE_COPY,
875 'oi_user_text' => MW_UPGRADE_ENCODE,
876 'oi_timestamp' => MW_UPGRADE_COPY );
877 $this->copyTable( 'oldimage', $tabledef, $fields,
878 array( &$this, 'oldimageCallback' ) );
879 }
880
881 function oldimageCallback( $row, $copy ) {
882 global $options;
883 if ( !isset( $options['noimage'] ) ) {
884 // Fill in the new image info fields
885 $info = $this->imageInfo( $row->oi_archive_name, 'wfImageArchiveDir', $row->oi_name );
886 $copy['oi_width' ] = $info['width' ];
887 $copy['oi_height'] = $info['height'];
888 $copy['oi_bits' ] = $info['bits' ];
889 }
890
891 // If doing UTF8 conversion the file must be renamed
892 $this->renameFile( $row->oi_archive_name, 'wfImageArchiveDir', $row->oi_name );
893
894 return $copy;
895 }
896
897
898 function upgradeWatchlist() {
899 $chunksize = 100;
900
901 list ( $watchlist, $watchlist_temp ) = $this->dbw->tableNamesN( 'watchlist', 'watchlist_temp' );
902
903 $this->log( 'Migrating watchlist table to watchlist_temp...' );
904 $this->dbw->query(
905 "CREATE TABLE $watchlist_temp (
906 -- Key to user_id
907 wl_user int(5) unsigned NOT NULL,
908
909 -- Key to page_namespace/page_title
910 -- Note that users may watch patches which do not exist yet,
911 -- or existed in the past but have been deleted.
912 wl_namespace int NOT NULL default '0',
913 wl_title varchar(255) binary NOT NULL default '',
914
915 -- Timestamp when user was last sent a notification e-mail;
916 -- cleared when the user visits the page.
917 -- FIXME: add proper null support etc
918 wl_notificationtimestamp varchar(14) binary NOT NULL default '0',
919
920 UNIQUE KEY (wl_user, wl_namespace, wl_title),
921 KEY namespace_title (wl_namespace,wl_title)
922
923 ) TYPE=InnoDB;", __METHOD__ );
924
925 // Fix encoding for Latin-1 upgrades, add some fields,
926 // and double article to article+talk pairs
927 $numwatched = $this->dbw->selectField( 'watchlist', 'count(*)', '', __METHOD__ );
928
929 $this->setChunkScale( $chunksize, $numwatched * 2, 'watchlist_temp', __METHOD__ );
930 $result = $this->dbr->select( 'watchlist',
931 array(
932 'wl_user',
933 'wl_namespace',
934 'wl_title' ),
935 '',
936 __METHOD__ );
937
938 $add = array();
939 foreach ( $result as $row ) {
940 $add[] = array(
941 'wl_user' => $row->wl_user,
942 'wl_namespace' => MWNamespace::getSubject( $row->wl_namespace ),
943 'wl_title' => $this->conv( $row->wl_title ),
944 'wl_notificationtimestamp' => '0' );
945 $this->addChunk( $add );
946
947 $add[] = array(
948 'wl_user' => $row->wl_user,
949 'wl_namespace' => MWNamespace::getTalk( $row->wl_namespace ),
950 'wl_title' => $this->conv( $row->wl_title ),
951 'wl_notificationtimestamp' => '0' );
952 $this->addChunk( $add );
953 }
954 $this->lastChunk( $add );
955
956 $this->log( 'Done converting watchlist.' );
957 $this->cleanupSwaps[] = 'watchlist';
958 }
959
960 function upgradeLogging() {
961 $tabledef = <<<ENDS
962 CREATE TABLE $1 (
963 -- Symbolic keys for the general log type and the action type
964 -- within the log. The output format will be controlled by the
965 -- action field, but only the type controls categorization.
966 log_type char(10) NOT NULL default '',
967 log_action char(10) NOT NULL default '',
968
969 -- Timestamp. Duh.
970 log_timestamp char(14) NOT NULL default '19700101000000',
971
972 -- The user who performed this action; key to user_id
973 log_user int unsigned NOT NULL default 0,
974
975 -- Key to the page affected. Where a user is the target,
976 -- this will point to the user page.
977 log_namespace int NOT NULL default 0,
978 log_title varchar(255) binary NOT NULL default '',
979
980 -- Freeform text. Interpreted as edit history comments.
981 log_comment varchar(255) NOT NULL default '',
982
983 -- LF separated list of miscellaneous parameters
984 log_params blob NOT NULL default '',
985
986 KEY type_time (log_type, log_timestamp),
987 KEY user_time (log_user, log_timestamp),
988 KEY page_time (log_namespace, log_title, log_timestamp)
989
990 ) TYPE=InnoDB
991 ENDS;
992 $fields = array(
993 'log_type' => MW_UPGRADE_COPY,
994 'log_action' => MW_UPGRADE_COPY,
995 'log_timestamp' => MW_UPGRADE_COPY,
996 'log_user' => MW_UPGRADE_COPY,
997 'log_namespace' => MW_UPGRADE_COPY,
998 'log_title' => MW_UPGRADE_ENCODE,
999 'log_comment' => MW_UPGRADE_ENCODE,
1000 'log_params' => MW_UPGRADE_ENCODE );
1001 $this->copyTable( 'logging', $tabledef, $fields );
1002 }
1003
1004 function upgradeArchive() {
1005 $tabledef = <<<ENDS
1006 CREATE TABLE $1 (
1007 ar_namespace int NOT NULL default '0',
1008 ar_title varchar(255) binary NOT NULL default '',
1009 ar_text mediumblob NOT NULL default '',
1010
1011 ar_comment tinyblob NOT NULL default '',
1012 ar_user int(5) unsigned NOT NULL default '0',
1013 ar_user_text varchar(255) binary NOT NULL,
1014 ar_timestamp char(14) binary NOT NULL default '',
1015 ar_minor_edit tinyint(1) NOT NULL default '0',
1016
1017 ar_flags tinyblob NOT NULL default '',
1018
1019 ar_rev_id int(8) unsigned,
1020 ar_text_id int(8) unsigned,
1021
1022 KEY name_title_timestamp (ar_namespace,ar_title,ar_timestamp)
1023
1024 ) TYPE=InnoDB
1025 ENDS;
1026 $fields = array(
1027 'ar_namespace' => MW_UPGRADE_COPY,
1028 'ar_title' => MW_UPGRADE_ENCODE,
1029 'ar_text' => MW_UPGRADE_COPY,
1030 'ar_comment' => MW_UPGRADE_ENCODE,
1031 'ar_user' => MW_UPGRADE_COPY,
1032 'ar_user_text' => MW_UPGRADE_ENCODE,
1033 'ar_timestamp' => MW_UPGRADE_COPY,
1034 'ar_minor_edit' => MW_UPGRADE_COPY,
1035 'ar_flags' => MW_UPGRADE_COPY,
1036 'ar_rev_id' => MW_UPGRADE_NULL,
1037 'ar_text_id' => MW_UPGRADE_NULL );
1038 $this->copyTable( 'archive', $tabledef, $fields );
1039 }
1040
1041 function upgradeImagelinks() {
1042 global $wgUseLatin1;
1043 if ( $wgUseLatin1 ) {
1044 $tabledef = <<<ENDS
1045 CREATE TABLE $1 (
1046 -- Key to page_id of the page containing the image / media link.
1047 il_from int(8) unsigned NOT NULL default '0',
1048
1049 -- Filename of target image.
1050 -- This is also the page_title of the file's description page;
1051 -- all such pages are in namespace 6 (NS_FILE).
1052 il_to varchar(255) binary NOT NULL default '',
1053
1054 UNIQUE KEY il_from(il_from,il_to),
1055 KEY (il_to)
1056
1057 ) TYPE=InnoDB
1058 ENDS;
1059 $fields = array(
1060 'il_from' => MW_UPGRADE_COPY,
1061 'il_to' => MW_UPGRADE_ENCODE );
1062 $this->copyTable( 'imagelinks', $tabledef, $fields );
1063 }
1064 }
1065
1066 function upgradeCategorylinks() {
1067 global $wgUseLatin1;
1068 if ( $wgUseLatin1 ) {
1069 $tabledef = <<<ENDS
1070 CREATE TABLE $1 (
1071 cl_from int(8) unsigned NOT NULL default '0',
1072 cl_to varchar(255) binary NOT NULL default '',
1073 cl_sortkey varchar(86) binary NOT NULL default '',
1074 cl_timestamp timestamp NOT NULL,
1075
1076 UNIQUE KEY cl_from(cl_from,cl_to),
1077 KEY cl_sortkey(cl_to,cl_sortkey),
1078 KEY cl_timestamp(cl_to,cl_timestamp)
1079 ) TYPE=InnoDB
1080 ENDS;
1081 $fields = array(
1082 'cl_from' => MW_UPGRADE_COPY,
1083 'cl_to' => MW_UPGRADE_ENCODE,
1084 'cl_sortkey' => MW_UPGRADE_ENCODE,
1085 'cl_timestamp' => MW_UPGRADE_COPY );
1086 $this->copyTable( 'categorylinks', $tabledef, $fields );
1087 }
1088 }
1089
1090 function upgradeIpblocks() {
1091 global $wgUseLatin1;
1092 if ( $wgUseLatin1 ) {
1093 $tabledef = <<<ENDS
1094 CREATE TABLE $1 (
1095 ipb_id int(8) NOT NULL auto_increment,
1096 ipb_address varchar(40) binary NOT NULL default '',
1097 ipb_user int(8) unsigned NOT NULL default '0',
1098 ipb_by int(8) unsigned NOT NULL default '0',
1099 ipb_reason tinyblob NOT NULL default '',
1100 ipb_timestamp char(14) binary NOT NULL default '',
1101 ipb_auto tinyint(1) NOT NULL default '0',
1102 ipb_expiry char(14) binary NOT NULL default '',
1103
1104 PRIMARY KEY ipb_id (ipb_id),
1105 INDEX ipb_address (ipb_address),
1106 INDEX ipb_user (ipb_user)
1107
1108 ) TYPE=InnoDB
1109 ENDS;
1110 $fields = array(
1111 'ipb_id' => MW_UPGRADE_COPY,
1112 'ipb_address' => MW_UPGRADE_COPY,
1113 'ipb_user' => MW_UPGRADE_COPY,
1114 'ipb_by' => MW_UPGRADE_COPY,
1115 'ipb_reason' => MW_UPGRADE_ENCODE,
1116 'ipb_timestamp' => MW_UPGRADE_COPY,
1117 'ipb_auto' => MW_UPGRADE_COPY,
1118 'ipb_expiry' => MW_UPGRADE_COPY );
1119 $this->copyTable( 'ipblocks', $tabledef, $fields );
1120 }
1121 }
1122
1123 function upgradeRecentchanges() {
1124 // There's a format change in the namespace field
1125 $tabledef = <<<ENDS
1126 CREATE TABLE $1 (
1127 rc_id int(8) NOT NULL auto_increment,
1128 rc_timestamp varchar(14) binary NOT NULL default '',
1129 rc_cur_time varchar(14) binary NOT NULL default '',
1130
1131 rc_user int(10) unsigned NOT NULL default '0',
1132 rc_user_text varchar(255) binary NOT NULL default '',
1133
1134 rc_namespace int NOT NULL default '0',
1135 rc_title varchar(255) binary NOT NULL default '',
1136
1137 rc_comment varchar(255) binary NOT NULL default '',
1138 rc_minor tinyint(3) unsigned NOT NULL default '0',
1139
1140 rc_bot tinyint(3) unsigned NOT NULL default '0',
1141 rc_new tinyint(3) unsigned NOT NULL default '0',
1142
1143 rc_cur_id int(10) unsigned NOT NULL default '0',
1144 rc_this_oldid int(10) unsigned NOT NULL default '0',
1145 rc_last_oldid int(10) unsigned NOT NULL default '0',
1146
1147 rc_type tinyint(3) unsigned NOT NULL default '0',
1148 rc_moved_to_ns tinyint(3) unsigned NOT NULL default '0',
1149 rc_moved_to_title varchar(255) binary NOT NULL default '',
1150
1151 rc_patrolled tinyint(3) unsigned NOT NULL default '0',
1152
1153 rc_ip char(15) NOT NULL default '',
1154
1155 PRIMARY KEY rc_id (rc_id),
1156 INDEX rc_timestamp (rc_timestamp),
1157 INDEX rc_namespace_title (rc_namespace, rc_title),
1158 INDEX rc_cur_id (rc_cur_id),
1159 INDEX new_name_timestamp(rc_new,rc_namespace,rc_timestamp),
1160 INDEX rc_ip (rc_ip)
1161
1162 ) TYPE=InnoDB
1163 ENDS;
1164 $fields = array(
1165 'rc_id' => MW_UPGRADE_COPY,
1166 'rc_timestamp' => MW_UPGRADE_COPY,
1167 'rc_cur_time' => MW_UPGRADE_COPY,
1168 'rc_user' => MW_UPGRADE_COPY,
1169 'rc_user_text' => MW_UPGRADE_ENCODE,
1170 'rc_namespace' => MW_UPGRADE_COPY,
1171 'rc_title' => MW_UPGRADE_ENCODE,
1172 'rc_comment' => MW_UPGRADE_ENCODE,
1173 'rc_minor' => MW_UPGRADE_COPY,
1174 'rc_bot' => MW_UPGRADE_COPY,
1175 'rc_new' => MW_UPGRADE_COPY,
1176 'rc_cur_id' => MW_UPGRADE_COPY,
1177 'rc_this_oldid' => MW_UPGRADE_COPY,
1178 'rc_last_oldid' => MW_UPGRADE_COPY,
1179 'rc_type' => MW_UPGRADE_COPY,
1180 'rc_moved_to_ns' => MW_UPGRADE_COPY,
1181 'rc_moved_to_title' => MW_UPGRADE_ENCODE,
1182 'rc_patrolled' => MW_UPGRADE_COPY,
1183 'rc_ip' => MW_UPGRADE_COPY );
1184 $this->copyTable( 'recentchanges', $tabledef, $fields );
1185 }
1186
1187 function upgradeQuerycache() {
1188 // There's a format change in the namespace field
1189 $tabledef = <<<ENDS
1190 CREATE TABLE $1 (
1191 -- A key name, generally the base name of of the special page.
1192 qc_type char(32) NOT NULL,
1193
1194 -- Some sort of stored value. Sizes, counts...
1195 qc_value int(5) unsigned NOT NULL default '0',
1196
1197 -- Target namespace+title
1198 qc_namespace int NOT NULL default '0',
1199 qc_title char(255) binary NOT NULL default '',
1200
1201 KEY (qc_type,qc_value)
1202
1203 ) TYPE=InnoDB
1204 ENDS;
1205 $fields = array(
1206 'qc_type' => MW_UPGRADE_COPY,
1207 'qc_value' => MW_UPGRADE_COPY,
1208 'qc_namespace' => MW_UPGRADE_COPY,
1209 'qc_title' => MW_UPGRADE_ENCODE );
1210 $this->copyTable( 'querycache', $tabledef, $fields );
1211 }
1212
1213 /**
1214 * Check for duplicate rows in "cur" table and move duplicates entries in
1215 * "old" table.
1216 *
1217 * This was in cleanupDupes.inc before.
1218 */
1219 function checkDupes() {
1220 $dbw = wfGetDB( DB_MASTER );
1221 if ( $dbw->indexExists( 'cur', 'name_title' ) &&
1222 $dbw->indexUnique( 'cur', 'name_title' ) ) {
1223 echo wfWikiID() . ": cur table has the current unique index; no duplicate entries.\n";
1224 return;
1225 } elseif ( $dbw->indexExists( 'cur', 'name_title_dup_prevention' ) ) {
1226 echo wfWikiID() . ": cur table has a temporary name_title_dup_prevention unique index; no duplicate entries.\n";
1227 return;
1228 }
1229
1230 echo wfWikiID() . ": cur table has the old non-unique index and may have duplicate entries.\n";
1231
1232 $dbw = wfGetDB( DB_MASTER );
1233 $cur = $dbw->tableName( 'cur' );
1234 $old = $dbw->tableName( 'old' );
1235 $dbw->query( "LOCK TABLES $cur WRITE, $old WRITE" );
1236 echo "Checking for duplicate cur table entries... (this may take a while on a large wiki)\n";
1237 $res = $dbw->query( <<<END
1238 SELECT cur_namespace,cur_title,count(*) as c,min(cur_id) as id
1239 FROM $cur
1240 GROUP BY cur_namespace,cur_title
1241 HAVING c > 1
1242 END
1243 );
1244 $n = $dbw->numRows( $res );
1245 echo "Found $n titles with duplicate entries.\n";
1246 if ( $n > 0 ) {
1247 echo "Correcting...\n";
1248 foreach ( $res as $row ) {
1249 $ns = intval( $row->cur_namespace );
1250 $title = $dbw->addQuotes( $row->cur_title );
1251
1252 # Get the first responding ID; that'll be the one we keep.
1253 $id = $dbw->selectField( 'cur', 'cur_id', array(
1254 'cur_namespace' => $row->cur_namespace,
1255 'cur_title' => $row->cur_title ) );
1256
1257 echo "$ns:$row->cur_title (canonical ID $id)\n";
1258 if ( $id != $row->id ) {
1259 echo " ** minimum ID $row->id; ";
1260 $timeMin = $dbw->selectField( 'cur', 'cur_timestamp', array(
1261 'cur_id' => $row->id ) );
1262 $timeFirst = $dbw->selectField( 'cur', 'cur_timestamp', array(
1263 'cur_id' => $id ) );
1264 if ( $timeMin == $timeFirst ) {
1265 echo "timestamps match at $timeFirst; ok\n";
1266 } else {
1267 echo "timestamps don't match! min: $timeMin, first: $timeFirst; ";
1268 if ( $timeMin > $timeFirst ) {
1269 $id = $row->id;
1270 echo "keeping minimum: $id\n";
1271 } else {
1272 echo "keeping first: $id\n";
1273 }
1274 }
1275 }
1276
1277 $dbw->query( <<<END
1278 INSERT
1279 INTO $old
1280 (old_namespace, old_title, old_text,
1281 old_comment, old_user, old_user_text,
1282 old_timestamp, old_minor_edit, old_flags,
1283 inverse_timestamp)
1284 SELECT cur_namespace, cur_title, cur_text,
1285 cur_comment, cur_user, cur_user_text,
1286 cur_timestamp, cur_minor_edit, '',
1287 inverse_timestamp
1288 FROM $cur
1289 WHERE cur_namespace=$ns
1290 AND cur_title=$title
1291 AND cur_id != $id
1292 END
1293 );
1294 $dbw->query( <<<END
1295 DELETE
1296 FROM $cur
1297 WHERE cur_namespace=$ns
1298 AND cur_title=$title
1299 AND cur_id != $id
1300 END
1301 );
1302 }
1303 }
1304 $dbw->query( 'UNLOCK TABLES' );
1305 echo "Done.\n";
1306 }
1307
1308 /**
1309 * Rename all our temporary tables into final place.
1310 * We've left things in place so a read-only wiki can continue running
1311 * on the old code during all this.
1312 */
1313 function upgradeCleanup() {
1314 $this->renameTable( 'old', 'text' );
1315
1316 foreach ( $this->cleanupSwaps as $table ) {
1317 $this->swap( $table );
1318 }
1319 }
1320
1321 function renameTable( $from, $to ) {
1322 $this->log( "Renaming $from to $to..." );
1323
1324 $fromtable = $this->dbw->tableName( $from );
1325 $totable = $this->dbw->tableName( $to );
1326 $this->dbw->query( "ALTER TABLE $fromtable RENAME TO $totable" );
1327 }
1328
1329 function swap( $base ) {
1330 $this->renameTable( $base, "{$base}_old" );
1331 $this->renameTable( "{$base}_temp", $base );
1332 }
1333
1334 }
1335
1336 $maintClass = 'FiveUpgrade';
1337 require_once( RUN_MAINTENANCE_IF_MAIN );