r72865 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r72864‎ | r72865 | r72866 >
Date:15:32, 12 September 2010
Author:ialex
Status:ok (Comments)
Tags:
Comment:
Moved FiveUpgrade.inc to upgrade1_5.php and made FiveUpgrade extends Maintenance since nothing else is using this class

Todo: maybe just remove this script entirely since it's useless for most people and parts of it are just broken, e.g. calls to wfImageDir() and wfImageArchiveDir() that were removed in r22580.
Modified paths:
  • /trunk/phase3/maintenance/FiveUpgrade.inc (deleted) (history)
  • /trunk/phase3/maintenance/upgrade1_5.php (replaced) (history)

Diff [purge]

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

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r22580Merged filerepo-work branch:...tstarling21:02, 30 May 2007

Comments

#Comment by 😂 (talk | contribs)   15:43, 12 September 2010

+1 on removing it. It's really really broken (I'm not even sure how well it worked back then, tbh)

Status & tagging log