* Made the show change size function work on page moves, page creations, and
[lhc/web/wiklou.git] / maintenance / updaters.inc
1 <?php
2 /**
3 * @package MediaWiki
4 * @subpackage Maintenance
5 */
6
7 /** */
8
9 require_once 'convertLinks.inc';
10 require_once 'InitialiseMessages.inc';
11 require_once 'userDupes.inc';
12
13 $wgRenamedTables = array(
14 # from to patch file
15 # array( 'group', 'groups', 'patch-rename-group.sql' ),
16 );
17
18 $wgNewTables = array(
19 # table patch file (in maintenance/archives)
20 array( 'hitcounter', 'patch-hitcounter.sql' ),
21 array( 'querycache', 'patch-querycache.sql' ),
22 array( 'objectcache', 'patch-objectcache.sql' ),
23 array( 'categorylinks', 'patch-categorylinks.sql' ),
24 array( 'logging', 'patch-logging.sql' ),
25 array( 'user_newtalk', 'patch-usernewtalk2.sql' ),
26 array( 'transcache', 'patch-transcache.sql' ),
27 array( 'trackbacks', 'patch-trackbacks.sql' ),
28 array( 'externallinks', 'patch-externallinks.sql' ),
29 array( 'job', 'patch-job.sql' ),
30 array( 'langlinks', 'patch-langlinks.sql' ),
31 array( 'querycache_info', 'patch-querycacheinfo.sql' ),
32 array( 'filearchive', 'patch-filearchive.sql' ),
33 array( 'redirect', 'patch-redirect.sql' ),
34 array( 'querycachetwo', 'patch-querycachetwo.sql' ),
35 );
36
37 $wgNewFields = array(
38 # table field patch file (in maintenance/archives)
39 array( 'ipblocks', 'ipb_id', 'patch-ipblocks.sql' ),
40 array( 'ipblocks', 'ipb_expiry', 'patch-ipb_expiry.sql' ),
41 array( 'ipblocks', 'ipb_enable_autoblock', 'patch-ipb_optional_autoblock.sql' ),
42 array( 'recentchanges', 'rc_type', 'patch-rc_type.sql' ),
43 array( 'recentchanges', 'rc_ip', 'patch-rc_ip.sql' ),
44 array( 'recentchanges', 'rc_id', 'patch-rc_id.sql' ),
45 array( 'recentchanges', 'rc_patrolled', 'patch-rc-patrol.sql' ),
46 array( 'recentchanges', 'rc_old_len', 'patch-rc_len.sql' ),
47 array( 'user', 'user_real_name', 'patch-user-realname.sql' ),
48 array( 'user', 'user_token', 'patch-user_token.sql' ),
49 array( 'user', 'user_email_token', 'patch-user_email_token.sql' ),
50 array( 'user', 'user_registration','patch-user_registration.sql' ),
51 array( 'logging', 'log_params', 'patch-log_params.sql' ),
52 array( 'archive', 'ar_rev_id', 'patch-archive-rev_id.sql' ),
53 array( 'archive', 'ar_text_id', 'patch-archive-text_id.sql' ),
54 array( 'page', 'page_len', 'patch-page_len.sql' ),
55 array( 'revision', 'rev_deleted', 'patch-rev_deleted.sql' ),
56 array( 'image', 'img_width', 'patch-img_width.sql' ),
57 array( 'image', 'img_metadata', 'patch-img_metadata.sql' ),
58 array( 'image', 'img_media_type', 'patch-img_media_type.sql' ),
59 array( 'site_stats', 'ss_total_pages', 'patch-ss_total_articles.sql' ),
60 array( 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ),
61 array( 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ),
62 array( 'site_stats', 'ss_images', 'patch-ss_images.sql' ),
63 array( 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ),
64 array( 'user', 'user_newpass_time','patch-user_newpass_time.sql' ),
65 );
66
67 function rename_table( $from, $to, $patch ) {
68 global $wgDatabase;
69 if ( $wgDatabase->tableExists( $from ) ) {
70 if ( $wgDatabase->tableExists( $to ) ) {
71 echo "...can't move table $from to $to, $to already exists.\n";
72 } else {
73 echo "Moving table $from to $to...";
74 dbsource( archive($patch), $wgDatabase );
75 echo "ok\n";
76 }
77 } else {
78 // Source table does not exist
79 // Renames are done before creations, so this is typical for a new installation
80 // Ignore silently
81 }
82 }
83
84 function add_table( $name, $patch ) {
85 global $wgDatabase;
86 if ( $wgDatabase->tableExists( $name ) ) {
87 echo "...$name table already exists.\n";
88 } else {
89 echo "Creating $name table...";
90 dbsource( archive($patch), $wgDatabase );
91 echo "ok\n";
92 }
93 }
94
95 function add_field( $table, $field, $patch ) {
96 global $wgDatabase;
97 if ( !$wgDatabase->tableExists( $table ) ) {
98 echo "...$table table does not exist, skipping new field patch\n";
99 } elseif ( $wgDatabase->fieldExists( $table, $field ) ) {
100 echo "...have $field field in $table table.\n";
101 } else {
102 echo "Adding $field field to table $table...";
103 dbsource( archive($patch) , $wgDatabase );
104 echo "ok\n";
105 }
106 }
107
108 function do_revision_updates() {
109 global $wgSoftwareRevision;
110 if ( $wgSoftwareRevision < 1001 ) {
111 update_passwords();
112 }
113 }
114
115 function update_passwords() {
116 wfDebugDieBacktrace( "This function needs to be updated or removed.\n" );
117
118 global $wgDatabase;
119 $fname = "Update script: update_passwords()";
120 print "\nIt appears that you need to update the user passwords in your\n" .
121 "database. If you have already done this (if you've run this update\n" .
122 "script once before, for example), doing so again will make all your\n" .
123 "user accounts inaccessible, so be sure you only do this once.\n" .
124 "Update user passwords? (yes/no)";
125
126 $resp = readconsole();
127 if ( ! ( "Y" == $resp{0} || "y" == $resp{0} ) ) { return; }
128
129 $sql = "SELECT user_id,user_password FROM user";
130 $source = $wgDatabase->query( $sql, $fname );
131
132 while ( $row = $wgDatabase->fetchObject( $source ) ) {
133 $id = $row->user_id;
134 $oldpass = $row->user_password;
135 $newpass = md5( "{$id}-{$oldpass}" );
136
137 $sql = "UPDATE user SET user_password='{$newpass}' " .
138 "WHERE user_id={$id}";
139 $wgDatabase->query( $sql, $fname );
140 }
141 }
142
143 function do_interwiki_update() {
144 # Check that interwiki table exists; if it doesn't source it
145 global $wgDatabase, $IP;
146 if( $wgDatabase->tableExists( "interwiki" ) ) {
147 echo "...already have interwiki table\n";
148 return true;
149 }
150 echo "Creating interwiki table: ";
151 dbsource( archive("patch-interwiki.sql") );
152 echo "ok\n";
153 echo "Adding default interwiki definitions: ";
154 dbsource( "$IP/maintenance/interwiki.sql" );
155 echo "ok\n";
156 }
157
158 function do_index_update() {
159 # Check that proper indexes are in place
160 global $wgDatabase;
161 $meta = $wgDatabase->fieldInfo( "recentchanges", "rc_timestamp" );
162 if( $meta->multiple_key == 0 ) {
163 echo "Updating indexes to 20031107: ";
164 dbsource( archive("patch-indexes.sql") );
165 echo "ok\n";
166 return true;
167 }
168 echo "...indexes seem up to 20031107 standards\n";
169 return false;
170 }
171
172 function do_image_index_update() {
173 global $wgDatabase;
174
175 $meta = $wgDatabase->fieldInfo( "image", "img_major_mime" );
176 if( $meta->multiple_key == 0 ) {
177 echo "Updating indexes to 20050912: ";
178 dbsource( archive("patch-mimesearch-indexes.sql") );
179 echo "ok\n";
180 return true;
181 }
182 echo "...indexes seem up to 20050912 standards\n";
183 return false;
184 }
185
186 function do_image_name_unique_update() {
187 global $wgDatabase;
188 if( $wgDatabase->indexExists( 'image', 'PRIMARY' ) ) {
189 echo "...image primary key already set.\n";
190 } else {
191 echo "Making img_name the primary key... ";
192 dbsource( archive("patch-image_name_primary.sql"), $wgDatabase );
193 echo "ok\n";
194 }
195 }
196
197 function do_logging_timestamp_index() {
198 global $wgDatabase;
199 if( $wgDatabase->indexExists( 'logging', 'times' ) ) {
200 echo "...timestamp key on logging already exists.\n";
201 } else {
202 echo "Adding timestamp key on logging table... ";
203 dbsource( archive("patch-logging-times-index.sql"), $wgDatabase );
204 echo "ok\n";
205 }
206 }
207
208
209 function do_watchlist_update() {
210 global $wgDatabase;
211 $fname = 'do_watchlist_update';
212 if( $wgDatabase->fieldExists( 'watchlist', 'wl_notificationtimestamp' ) ) {
213 echo "The watchlist table is already set up for email notification.\n";
214 } else {
215 echo "Adding wl_notificationtimestamp field for email notification management.";
216 /* ALTER TABLE watchlist ADD (wl_notificationtimestamp varchar(14) binary NOT NULL default '0'); */
217 dbsource( archive( 'patch-email-notification.sql' ), $wgDatabase );
218 echo "ok\n";
219 }
220 # Check if we need to add talk page rows to the watchlist
221 $talk = $wgDatabase->selectField( 'watchlist', 'count(*)', 'wl_namespace & 1', $fname );
222 $nontalk = $wgDatabase->selectField( 'watchlist', 'count(*)', 'NOT (wl_namespace & 1)', $fname );
223 if ( $talk != $nontalk ) {
224 echo "Adding missing watchlist talk page rows... ";
225 flush();
226
227 $wgDatabase->insertSelect( 'watchlist', 'watchlist',
228 array(
229 'wl_user' => 'wl_user',
230 'wl_namespace' => 'wl_namespace | 1',
231 'wl_title' => 'wl_title',
232 'wl_notificationtimestamp' => 'wl_notificationtimestamp'
233 ), array( 'NOT (wl_namespace & 1)' ), $fname, 'IGNORE' );
234 echo "ok\n";
235 } else {
236 echo "...watchlist talk page rows already present\n";
237 }
238 }
239
240 function do_copy_newtalk_to_watchlist() {
241 global $wgDatabase;
242 global $wgCommandLineMode; # this needs to be saved while getID() and getName() are called
243
244 $res = $wgDatabase->safeQuery( 'SELECT user_id, user_ip FROM !',
245 $wgDatabase->tableName( 'user_newtalk' ) );
246 $num_newtalks=$wgDatabase->numRows($res);
247 echo "Now converting ".$num_newtalks." user_newtalk entries to watchlist table entries ... \n";
248
249 $user = new User();
250 for ( $i = 1; $i <= $num_newtalks; $i++ ) {
251 $wluser = $wgDatabase->fetchObject( $res );
252 if ($wluser->user_id == 0) { # anonymous users ... have IP numbers as "names"
253 if ($user->isIP($wluser->user_ip)) { # do only if it really looks like an IP number (double checked)
254 $wgDatabase->replace( 'watchlist',
255 array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp' )),
256 array('wl_user' => 0,
257 'wl_namespace' => NS_USER_TALK,
258 'wl_title' => $wluser->user_ip,
259 'wl_notificationtimestamp' => '19700101000000'
260 ), 'updaters.inc::do_watchlist_update2'
261 );
262 }
263 } else { # normal users ... have user_ids
264 $user->setID($wluser->user_id);
265 $wgDatabase->replace( 'watchlist',
266 array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp' )),
267 array('wl_user' => $user->getID(),
268 'wl_namespace' => NS_USER_TALK,
269 'wl_title' => $user->getName(),
270 'wl_notificationtimestamp' => '19700101000000'
271 ), 'updaters.inc::do_watchlist_update3'
272 );
273 }
274 }
275 echo "Done.\n";
276 }
277
278
279 function do_user_update() {
280 global $wgDatabase;
281 if( $wgDatabase->fieldExists( 'user', 'user_emailauthenticationtimestamp' ) ) {
282 echo "User table contains old email authentication field. Dropping... ";
283 dbsource( archive( 'patch-email-authentication.sql' ), $wgDatabase );
284 echo "ok\n";
285 } else {
286 echo "...user table does not contain old email authentication field.\n";
287 }
288 }
289
290 /**
291 * 1.4 betas were missing the 'binary' marker from logging.log_title,
292 * which causes a collation mismatch error on joins in MySQL 4.1.
293 */
294 function do_logging_encoding() {
295 global $wgDatabase, $wgDBtype;
296 if ($wgDBtype != 'mysql')
297 return;
298 $logging = $wgDatabase->tableName( 'logging' );
299 $res = $wgDatabase->query( "SELECT log_title FROM $logging LIMIT 0" );
300 $flags = explode( ' ', mysql_field_flags( $res, 0 ) );
301 $wgDatabase->freeResult( $res );
302
303 if( in_array( 'binary', $flags ) ) {
304 echo "Logging table has correct title encoding.\n";
305 } else {
306 echo "Fixing title encoding on logging table... ";
307 dbsource( archive( 'patch-logging-title.sql' ), $wgDatabase );
308 echo "ok\n";
309 }
310 }
311
312 function do_schema_restructuring() {
313 global $wgDatabase;
314 $fname="do_schema_restructuring";
315 if ( $wgDatabase->tableExists( 'page' ) ) {
316 echo "...page table already exists.\n";
317 } else {
318 echo "...converting from cur/old to page/revision/text DB structure.\n"; flush();
319 echo wfTimestamp();
320 echo "......checking for duplicate entries.\n"; flush();
321
322 extract( $wgDatabase->tableNames( 'cur', 'old', 'page', 'revision', 'text' ) );
323
324 $rows = $wgDatabase->query( "SELECT cur_title, cur_namespace, COUNT(cur_namespace) AS c
325 FROM $cur GROUP BY cur_title, cur_namespace HAVING c>1", $fname );
326
327 if ( $wgDatabase->numRows( $rows ) > 0 ) {
328 echo wfTimestamp();
329 echo "......<b>Found duplicate entries</b>\n";
330 echo ( sprintf( "<b> %-60s %3s %5s</b>\n", 'Title', 'NS', 'Count' ) );
331 while ( $row = $wgDatabase->fetchObject( $rows ) ) {
332 if ( ! isset( $duplicate[$row->cur_namespace] ) ) {
333 $duplicate[$row->cur_namespace] = array();
334 }
335 $duplicate[$row->cur_namespace][] = $row->cur_title;
336 echo ( sprintf( " %-60s %3s %5s\n", $row->cur_title, $row->cur_namespace, $row->c ) );
337 }
338 $sql = "SELECT cur_title, cur_namespace, cur_id, cur_timestamp FROM $cur WHERE ";
339 $firstCond = true;
340 foreach ( $duplicate as $ns => $titles ) {
341 if ( $firstCond ) {
342 $firstCond = false;
343 } else {
344 $sql .= ' OR ';
345 }
346 $sql .= "( cur_namespace = {$ns} AND cur_title in (";
347 $first = true;
348 foreach ( $titles as $t ) {
349 if ( $first ) {
350 $sql .= $wgDatabase->addQuotes( $t );
351 $first = false;
352 } else {
353 $sql .= ', ' . $wgDatabase->addQuotes( $t );
354 }
355 }
356 $sql .= ") ) \n";
357 }
358 # By sorting descending, the most recent entry will be the first in the list.
359 # All following entries will be deleted by the next while-loop.
360 $sql .= 'ORDER BY cur_namespace, cur_title, cur_timestamp DESC';
361
362 $rows = $wgDatabase->query( $sql, $fname );
363
364 $prev_title = $prev_namespace = false;
365 $deleteId = array();
366
367 while ( $row = $wgDatabase->fetchObject( $rows ) ) {
368 if ( $prev_title == $row->cur_title && $prev_namespace == $row->cur_namespace ) {
369 $deleteId[] = $row->cur_id;
370 }
371 $prev_title = $row->cur_title;
372 $prev_namespace = $row->cur_namespace;
373 }
374 $sql = "DELETE FROM $cur WHERE cur_id IN ( " . join( ',', $deleteId ) . ')';
375 $rows = $wgDatabase->query( $sql, $fname );
376 echo wfTimestamp();
377 echo "......<b>Deleted</b> ".$wgDatabase->affectedRows()." records.\n";
378 }
379
380
381 echo wfTimestamp();
382 echo "......Creating tables.\n";
383 $wgDatabase->query("CREATE TABLE $page (
384 page_id int(8) unsigned NOT NULL auto_increment,
385 page_namespace int NOT NULL,
386 page_title varchar(255) binary NOT NULL,
387 page_restrictions tinyblob NOT NULL default '',
388 page_counter bigint(20) unsigned NOT NULL default '0',
389 page_is_redirect tinyint(1) unsigned NOT NULL default '0',
390 page_is_new tinyint(1) unsigned NOT NULL default '0',
391 page_random real unsigned NOT NULL,
392 page_touched char(14) binary NOT NULL default '',
393 page_latest int(8) unsigned NOT NULL,
394 page_len int(8) unsigned NOT NULL,
395
396 PRIMARY KEY page_id (page_id),
397 UNIQUE INDEX name_title (page_namespace,page_title),
398 INDEX (page_random),
399 INDEX (page_len)
400 ) TYPE=InnoDB", $fname );
401 $wgDatabase->query("CREATE TABLE $revision (
402 rev_id int(8) unsigned NOT NULL auto_increment,
403 rev_page int(8) unsigned NOT NULL,
404 rev_comment tinyblob NOT NULL default '',
405 rev_user int(5) unsigned NOT NULL default '0',
406 rev_user_text varchar(255) binary NOT NULL default '',
407 rev_timestamp char(14) binary NOT NULL default '',
408 rev_minor_edit tinyint(1) unsigned NOT NULL default '0',
409 rev_deleted tinyint(1) unsigned NOT NULL default '0',
410
411 PRIMARY KEY rev_page_id (rev_page, rev_id),
412 UNIQUE INDEX rev_id (rev_id),
413 INDEX rev_timestamp (rev_timestamp),
414 INDEX page_timestamp (rev_page,rev_timestamp),
415 INDEX user_timestamp (rev_user,rev_timestamp),
416 INDEX usertext_timestamp (rev_user_text,rev_timestamp)
417 ) TYPE=InnoDB", $fname );
418
419 echo wfTimestamp();
420 echo "......Locking tables.\n";
421 $wgDatabase->query( "LOCK TABLES $page WRITE, $revision WRITE, $old WRITE, $cur WRITE", $fname );
422
423 $maxold = intval( $wgDatabase->selectField( 'old', 'max(old_id)', '', $fname ) );
424 echo wfTimestamp();
425 echo "......maxold is {$maxold}\n";
426
427 echo wfTimestamp();
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 $wgDatabase->query( "INSERT INTO $old (old_namespace, old_title, old_text, old_comment, old_user, old_user_text,
443 old_timestamp, old_minor_edit, old_flags)
444 SELECT cur_namespace, cur_title, $cur_text, cur_comment, cur_user, cur_user_text, cur_timestamp, cur_minor_edit, $cur_flags
445 FROM $cur", $fname );
446
447 echo wfTimestamp();
448 echo "......Setting up revision table.\n";
449 $wgDatabase->query( "INSERT INTO $revision (rev_id, rev_page, rev_comment, rev_user, rev_user_text, rev_timestamp,
450 rev_minor_edit)
451 SELECT old_id, cur_id, old_comment, old_user, old_user_text,
452 old_timestamp, old_minor_edit
453 FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title", $fname );
454
455 echo wfTimestamp();
456 echo "......Setting up page table.\n";
457 $wgDatabase->query( "INSERT INTO $page (page_id, page_namespace, page_title, page_restrictions, page_counter,
458 page_is_redirect, page_is_new, page_random, page_touched, page_latest, page_len)
459 SELECT cur_id, cur_namespace, cur_title, cur_restrictions, cur_counter, cur_is_redirect, cur_is_new,
460 cur_random, cur_touched, rev_id, LENGTH(cur_text)
461 FROM $cur,$revision
462 WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}", $fname );
463
464 echo wfTimestamp();
465 echo "......Unlocking tables.\n";
466 $wgDatabase->query( "UNLOCK TABLES", $fname );
467
468 echo wfTimestamp();
469 echo "......Renaming old.\n";
470 $wgDatabase->query( "ALTER TABLE $old RENAME TO $text", $fname );
471
472 echo wfTimestamp();
473 echo "...done.\n";
474 }
475 }
476
477 function do_inverse_timestamp() {
478 global $wgDatabase;
479 $fname="do_schema_restructuring";
480 if( $wgDatabase->fieldExists( 'revision', 'inverse_timestamp' ) ) {
481 echo "Removing revision.inverse_timestamp and fixing indexes... ";
482 dbsource( archive( 'patch-inverse_timestamp.sql' ), $wgDatabase );
483 echo "ok\n";
484 } else {
485 echo "revision timestamp indexes already up to 2005-03-13\n";
486 }
487 }
488
489 function do_text_id() {
490 global $wgDatabase;
491 if( $wgDatabase->fieldExists( 'revision', 'rev_text_id' ) ) {
492 echo "...rev_text_id already in place.\n";
493 } else {
494 echo "Adding rev_text_id field... ";
495 dbsource( archive( 'patch-rev_text_id.sql' ), $wgDatabase );
496 echo "ok\n";
497 }
498 }
499
500 function do_namespace_size() {
501 $tables = array(
502 'page' => 'page',
503 'archive' => 'ar',
504 'recentchanges' => 'rc',
505 'watchlist' => 'wl',
506 'querycache' => 'qc',
507 'logging' => 'log',
508 );
509 foreach( $tables as $table => $prefix ) {
510 do_namespace_size_on( $table, $prefix );
511 flush();
512 }
513 }
514
515 function do_namespace_size_on( $table, $prefix ) {
516 global $wgDatabase, $wgDBtype;
517 if ($wgDBtype != 'mysql')
518 return;
519 $field = $prefix . '_namespace';
520
521 $tablename = $wgDatabase->tableName( $table );
522 $result = $wgDatabase->query( "SHOW COLUMNS FROM $tablename LIKE '$field'" );
523 $info = $wgDatabase->fetchObject( $result );
524 $wgDatabase->freeResult( $result );
525
526 if( substr( $info->Type, 0, 3 ) == 'int' ) {
527 echo "...$field is already a full int ($info->Type).\n";
528 } else {
529 echo "Promoting $field from $info->Type to int... ";
530
531 $sql = "ALTER TABLE $tablename MODIFY $field int NOT NULL";
532 $wgDatabase->query( $sql );
533
534 echo "ok\n";
535 }
536 }
537
538 function do_pagelinks_update() {
539 global $wgDatabase;
540 if( $wgDatabase->tableExists( 'pagelinks' ) ) {
541 echo "...already have pagelinks table.\n";
542 } else {
543 echo "Converting links and brokenlinks tables to pagelinks... ";
544 dbsource( archive( 'patch-pagelinks.sql' ), $wgDatabase );
545 echo "ok\n";
546 flush();
547
548 global $wgCanonicalNamespaceNames;
549 foreach( $wgCanonicalNamespaceNames as $ns => $name ) {
550 if( $ns != 0 ) {
551 do_pagelinks_namespace( $ns );
552 }
553 }
554 }
555 }
556
557 function do_pagelinks_namespace( $namespace ) {
558 global $wgDatabase, $wgContLang;
559
560 $ns = intval( $namespace );
561 echo "Cleaning up broken links for namespace $ns... ";
562
563 $pagelinks = $wgDatabase->tableName( 'pagelinks' );
564 $name = $wgContLang->getNsText( $ns );
565 $prefix = $wgDatabase->strencode( $name );
566 $likeprefix = str_replace( '_', '\\_', $prefix);
567
568 $sql = "UPDATE $pagelinks
569 SET pl_namespace=$ns,
570 pl_title=TRIM(LEADING '$prefix:' FROM pl_title)
571 WHERE pl_namespace=0
572 AND pl_title LIKE '$likeprefix:%'";
573
574 $wgDatabase->query( $sql, 'do_pagelinks_namespace' );
575 echo "ok\n";
576 }
577
578 function do_drop_img_type() {
579 global $wgDatabase;
580
581 if( $wgDatabase->fieldExists( 'image', 'img_type' ) ) {
582 echo "Dropping unused img_type field in image table... ";
583 dbsource( archive( 'patch-drop_img_type.sql' ), $wgDatabase );
584 echo "ok\n";
585 } else {
586 echo "No img_type field in image table; Good.\n";
587 }
588 }
589
590 function do_old_links_update() {
591 global $wgDatabase;
592 if( $wgDatabase->tableExists( 'pagelinks' ) ) {
593 echo "Already have pagelinks; skipping old links table updates.\n";
594 } else {
595 convertLinks(); flush();
596 }
597 }
598
599 function do_user_unique_update() {
600 global $wgDatabase;
601 $duper = new UserDupes( $wgDatabase );
602 if( $duper->hasUniqueIndex() ) {
603 echo "Already have unique user_name index.\n";
604 } else {
605 if( !$duper->clearDupes() ) {
606 echo "WARNING: This next step will probably fail due to unfixed duplicates...\n";
607 }
608 echo "Adding unique index on user_name... ";
609 dbsource( archive( 'patch-user_nameindex.sql' ), $wgDatabase );
610 echo "ok\n";
611 }
612 }
613
614 function do_user_groups_update() {
615 $fname = 'do_user_groups_update';
616 global $wgDatabase;
617
618 if( $wgDatabase->tableExists( 'user_groups' ) ) {
619 echo "...user_groups table already exists.\n";
620 return do_user_groups_reformat();
621 }
622
623 echo "Adding user_groups table... ";
624 dbsource( archive( 'patch-user_groups.sql' ), $wgDatabase );
625 echo "ok\n";
626
627 if( !$wgDatabase->tableExists( 'user_rights' ) ) {
628 if( $wgDatabase->fieldExists( 'user', 'user_rights' ) ) {
629 echo "Upgrading from a 1.3 or older database? Breaking out user_rights for conversion...";
630 dbsource( archive( 'patch-user_rights.sql' ), $wgDatabase );
631 echo "ok\n";
632 } else {
633 echo "*** WARNING: couldn't locate user_rights table or field for upgrade.\n";
634 echo "*** You may need to manually configure some sysops by manipulating\n";
635 echo "*** the user_groups table.\n";
636 return;
637 }
638 }
639
640 echo "Converting user_rights table to user_groups... ";
641 $result = $wgDatabase->select( 'user_rights',
642 array( 'ur_user', 'ur_rights' ),
643 array( "ur_rights != ''" ),
644 $fname );
645
646 while( $row = $wgDatabase->fetchObject( $result ) ) {
647 $groups = array_unique(
648 array_map( 'trim',
649 explode( ',', $row->ur_rights ) ) );
650
651 foreach( $groups as $group ) {
652 $wgDatabase->insert( 'user_groups',
653 array(
654 'ug_user' => $row->ur_user,
655 'ug_group' => $group ),
656 $fname );
657 }
658 }
659 $wgDatabase->freeResult( $result );
660 echo "ok\n";
661 }
662
663 function do_user_groups_reformat() {
664 # Check for bogus formats from previous 1.5 alpha code.
665 global $wgDatabase;
666 $info = $wgDatabase->fieldInfo( 'user_groups', 'ug_group' );
667
668 if( $info->type == 'int' ) {
669 $oldug = $wgDatabase->tableName( 'user_groups' );
670 $newug = $wgDatabase->tableName( 'user_groups_bogus' );
671 echo "user_groups is in bogus intermediate format. Renaming to $newug... ";
672 $wgDatabase->query( "ALTER TABLE $oldug RENAME TO $newug" );
673 echo "ok\n";
674
675 echo "Re-adding fresh user_groups table... ";
676 dbsource( archive( 'patch-user_groups.sql' ), $wgDatabase );
677 echo "ok\n";
678
679 echo "***\n";
680 echo "*** WARNING: You will need to manually fix up user permissions in the user_groups\n";
681 echo "*** table. Old 1.5 alpha versions did some pretty funky stuff...\n";
682 echo "***\n";
683 } else {
684 echo "...user_groups is in current format.\n";
685 }
686
687 }
688
689 function do_watchlist_null() {
690 # Make sure wl_notificationtimestamp can be NULL,
691 # and update old broken items.
692 global $wgDatabase;
693 $info = $wgDatabase->fieldInfo( 'watchlist', 'wl_notificationtimestamp' );
694
695 if( $info->not_null ) {
696 echo "Making wl_notificationtimestamp nullable... ";
697 dbsource( archive( 'patch-watchlist-null.sql' ), $wgDatabase );
698 echo "ok\n";
699 } else {
700 echo "...wl_notificationtimestamp is already nullable.\n";
701 }
702
703 }
704
705 /**
706 * @bug 3946
707 */
708 function do_page_random_update() {
709 global $wgDatabase;
710
711 echo "Setting page_random to a random value on rows where it equals 0...";
712
713 $page = $wgDatabase->tableName( 'page' );
714 $wgDatabase->query( "UPDATE $page SET page_random = RAND() WHERE page_random = 0", 'do_page_random_update' );
715 $rows = $wgDatabase->affectedRows();
716
717 echo "changed $rows rows\n";
718 }
719
720 function do_templatelinks_update() {
721 global $wgDatabase, $wgLoadBalancer;
722 $fname = 'do_templatelinks_update';
723
724 if ( $wgDatabase->tableExists( 'templatelinks' ) ) {
725 echo "...templatelinks table already exists\n";
726 return;
727 }
728 echo "Creating templatelinks table...\n";
729 dbsource( archive('patch-templatelinks.sql'), $wgDatabase );
730 echo "Populating...\n";
731 if ( isset( $wgLoadBalancer ) && $wgLoadBalancer->getServerCount() > 1 ) {
732 // Slow, replication-friendly update
733 $res = $wgDatabase->select( 'pagelinks', array( 'pl_from', 'pl_namespace', 'pl_title' ),
734 array( 'pl_namespace' => NS_TEMPLATE ), $fname );
735 $count = 0;
736 while ( $row = $wgDatabase->fetchObject( $res ) ) {
737 $count = ($count + 1) % 100;
738 if ( $count == 0 ) {
739 if ( function_exists( 'wfWaitForSlaves' ) ) {
740 wfWaitForSlaves( 10 );
741 } else {
742 sleep( 1 );
743 }
744 }
745 $wgDatabase->insert( 'templatelinks',
746 array(
747 'tl_from' => $row->pl_from,
748 'tl_namespace' => $row->pl_namespace,
749 'tl_title' => $row->pl_title,
750 ), $fname
751 );
752
753 }
754 $wgDatabase->freeResult( $res );
755 } else {
756 // Fast update
757 $wgDatabase->insertSelect( 'templatelinks', 'pagelinks',
758 array(
759 'tl_from' => 'pl_from',
760 'tl_namespace' => 'pl_namespace',
761 'tl_title' => 'pl_title'
762 ), array(
763 'pl_namespace' => 10
764 ), $fname
765 );
766 }
767 echo "Done. Please run maintenance/refreshLinks.php for a more thorough templatelinks update.\n";
768 }
769
770 # July 2006
771 # Add ( rc_namespace, rc_user_text ) index [R. Church]
772 function do_rc_indices_update() {
773 global $wgDatabase;
774 echo( "Checking for additional recent changes indices...\n" );
775 # See if we can find the index we want
776 $info = $wgDatabase->indexInfo( 'recentchanges', 'rc_ns_usertext', __METHOD__ );
777 if( !$info ) {
778 # None, so create
779 echo( "...index on ( rc_namespace, rc_user_text ) not found; creating\n" );
780 dbsource( archive( 'patch-recentchanges-utindex.sql' ) );
781 } else {
782 # Index seems to exist
783 echo( "...index on ( rc_namespace, rc_user_text ) seems to be ok\n" );
784 }
785
786 #Add (rc_user_text, rc_timestamp) index [A. Garrett], November 2006
787 # See if we can find the index we want
788 $info = $wgDatabase->indexInfo( 'recentchanges', 'rc_user_text', __METHOD__ );
789 if( !$info ) {
790 # None, so create
791 echo( "...index on ( rc_user_text, rc_timestamp ) not found; creating\n" );
792 dbsource( archive( 'patch-rc_user_text-index.sql' ) );
793 } else {
794 # Index seems to exist
795 echo( "...index on ( rc_user_text, rc_timestamp ) seems to be ok\n" );
796 }
797 }
798
799 function index_has_field($table, $index, $field) {
800 global $wgDatabase;
801 echo( "Checking if $table index $index includes field $field...\n" );
802 $info = $wgDatabase->indexInfo( $table, $index, __METHOD__ );
803 if( $info ) {
804 foreach($info as $row) {
805 if($row->Column_name == $field) {
806 echo( "...index $index on table $table seems to be ok\n" );
807 return true;
808 }
809 }
810 }
811 echo( "...index $index on table $table has no field $field; adding\n" );
812 return false;
813 }
814
815 function do_backlinking_indices_update() {
816 echo( "Checking for backlinking indices...\n" );
817 if (!index_has_field('pagelinks', 'pl_namespace', 'pl_from') ||
818 !index_has_field('templatelinks', 'tl_namespace', 'tl_from') ||
819 !index_has_field('imagelinks', 'il_to', 'il_from'))
820 {
821 dbsource( archive( 'patch-backlinkindexes.sql' ) );
822 }
823 }
824
825 function do_all_updates( $doShared = false ) {
826 global $wgNewTables, $wgNewFields, $wgRenamedTables, $wgSharedDB, $wgDatabase, $wgDBtype;
827
828 $doUser = !$wgSharedDB || $doShared;
829
830 if ($wgDBtype === 'postgres') {
831 do_postgres_updates();
832 return;
833 }
834
835 # Rename tables
836 foreach ( $wgRenamedTables as $tableRecord ) {
837 rename_table( $tableRecord[0], $tableRecord[1], $tableRecord[2] );
838 }
839
840 # Add missing tables
841 foreach ( $wgNewTables as $tableRecord ) {
842 add_table( $tableRecord[0], $tableRecord[1] );
843 flush();
844 }
845
846 # Add missing fields
847 foreach ( $wgNewFields as $fieldRecord ) {
848 if ( $fieldRecord[0] != 'user' || $doUser ) {
849 add_field( $fieldRecord[0], $fieldRecord[1], $fieldRecord[2] );
850 }
851 flush();
852 }
853
854 # Do schema updates which require special handling
855 do_interwiki_update(); flush();
856 do_index_update(); flush();
857 do_old_links_update(); flush();
858 do_image_name_unique_update(); flush();
859 do_watchlist_update(); flush();
860 if ( $doUser ) {
861 do_user_update(); flush();
862 }
863 ###### do_copy_newtalk_to_watchlist(); flush();
864 do_logging_encoding(); flush();
865
866 do_schema_restructuring(); flush();
867 do_inverse_timestamp(); flush();
868 do_text_id(); flush();
869 do_namespace_size(); flush();
870
871 do_pagelinks_update(); flush();
872 do_templatelinks_update(); flush(); // after pagelinks
873
874 do_drop_img_type(); flush();
875
876 if ( $doUser ) {
877 do_user_unique_update(); flush();
878 }
879 do_user_groups_update(); flush();
880
881 do_watchlist_null(); flush();
882
883 //do_image_index_update(); flush();
884
885 do_logging_timestamp_index(); flush();
886
887 do_page_random_update(); flush();
888
889 do_rc_indices_update(); flush();
890
891 do_backlinking_indices_update(); flush();
892
893 initialiseMessages(); flush();
894 }
895
896 function archive($name) {
897 global $wgDBtype, $IP;
898 switch ($wgDBtype) {
899 case "oracle":
900 return "$IP/maintenance/oracle/archives/$name";
901 default:
902 return "$IP/maintenance/archives/$name";
903 }
904 }
905
906 function do_postgres_updates() {
907 global $wgDatabase, $wgVersion, $wgDBmwschema;
908
909 # Just in case their LocalSetings.php does not have this:
910 if ( !isset( $wgDBmwschema ))
911 $wgDBmwschema = 'mediawiki';
912
913 ## Default to the oldest supported version
914 $version = 1.7;
915
916 if ($wgDatabase->tableExists("mediawiki_version")) {
917 $version = "1.8";
918 $sql = "SELECT mw_version FROM mediawiki_version ORDER BY cdate DESC LIMIT 1";
919 $tempversion = pg_fetch_result($wgDatabase->doQuery($sql),0,0);
920 $thisver = array();
921 if (preg_match('/(\d+\.\d+)/', $tempversion, $thisver)) {
922 $version = $thisver[1];
923 }
924 }
925
926 print " Detected version: $version ";
927 $upgrade = '';
928
929 if ($version <= 1.7) {
930 $upgrade = <<<PGEND
931
932 -- Type tweaking:
933 ALTER TABLE oldimage ALTER oi_size TYPE INTEGER;
934 ALTER TABLE oldimage ALTER oi_width TYPE INTEGER;
935 ALTER TABLE oldimage ALTER oi_height TYPE INTEGER;
936
937 ALTER TABLE image ALTER img_size TYPE INTEGER;
938 ALTER TABLE image ALTER img_width TYPE INTEGER;
939 ALTER TABLE image ALTER img_height TYPE INTEGER;
940
941 -- Constraint tweaking:
942 ALTER TABLE recentchanges ALTER rc_cur_id DROP NOT NULL;
943
944 -- New columns:
945 ALTER TABLE ipblocks ADD ipb_anon_only CHAR NOT NULL DEFAULT '0';
946 ALTER TABLE ipblocks ADD ipb_create_account CHAR NOT NULL DEFAULT '1';
947
948 -- Index order rearrangements:
949 DROP INDEX pagelink_unique;
950 CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title);
951
952 -- Rename tables
953 ALTER TABLE "user" RENAME TO mwuser;
954 ALTER TABLE "text" RENAME to pagecontent;
955
956 -- New tables:
957 CREATE TABLE profiling (
958 pf_count INTEGER NOT NULL DEFAULT 0,
959 pf_time NUMERIC(18,10) NOT NULL DEFAULT 0,
960 pf_name TEXT NOT NULL,
961 pf_server TEXT NULL
962 );
963 CREATE UNIQUE INDEX pf_name_server ON profiling (pf_name, pf_server);
964
965 CREATE TABLE mediawiki_version (
966 type TEXT NOT NULL,
967 mw_version TEXT NOT NULL,
968 notes TEXT NULL,
969
970 pg_version TEXT NULL,
971 pg_dbname TEXT NULL,
972 pg_user TEXT NULL,
973 pg_port TEXT NULL,
974 mw_schema TEXT NULL,
975 ts2_schema TEXT NULL,
976 ctype TEXT NULL,
977
978 sql_version TEXT NULL,
979 sql_date TEXT NULL,
980 cdate TIMESTAMPTZ NOT NULL DEFAULT now()
981 );
982
983 -- Special modifications
984 ALTER TABLE archive RENAME to archive2;
985 CREATE VIEW archive AS
986 SELECT
987 ar_namespace, ar_title, ar_text, ar_comment, ar_user, ar_user_text,
988 ar_minor_edit, ar_flags, ar_rev_id, ar_text_id,
989 TO_CHAR(ar_timestamp, 'YYYYMMDDHH24MISS') AS ar_timestamp
990 FROM archive2;
991
992 CREATE RULE archive_insert AS ON INSERT TO archive
993 DO INSTEAD INSERT INTO archive2 VALUES (
994 NEW.ar_namespace, NEW.ar_title, NEW.ar_text, NEW.ar_comment, NEW.ar_user, NEW.ar_user_text,
995 TO_DATE(NEW.ar_timestamp, 'YYYYMMDDHH24MISS'),
996 NEW.ar_minor_edit, NEW.ar_flags, NEW.ar_rev_id, NEW.ar_text_id
997 );
998
999 CREATE FUNCTION page_deleted() RETURNS TRIGGER LANGUAGE plpgsql AS
1000 \$mw\$
1001 BEGIN
1002 DELETE FROM recentchanges WHERE rc_namespace = OLD.page_namespace AND rc_title = OLD.page_title;
1003 RETURN NULL;
1004 END;
1005 \$mw\$;
1006
1007 CREATE TRIGGER page_deleted AFTER DELETE ON page
1008 FOR EACH ROW EXECUTE PROCEDURE page_deleted();
1009
1010 PGEND;
1011
1012 } ## end version 1.7
1013
1014 else if ($version <= 1.8) {
1015 $upgrade = <<<PGEND
1016
1017 -- Tighten up restrictions on the revision table so we don't lose data:
1018 ALTER TABLE revision DROP CONSTRAINT revision_rev_user_fkey;
1019 ALTER TABLE revision ADD CONSTRAINT revision_rev_user_fkey
1020 FOREIGN KEY (rev_user) REFERENCES mwuser(user_id) ON DELETE RESTRICT;
1021
1022 -- New column for better password tracking:
1023 ALTER TABLE mwuser ADD user_newpass_time TIMESTAMPTZ;
1024
1025 -- New column for autoblocking problem users
1026 ALTER TABLE ipblocks ADD ipb_enable_autoblock CHAR NOT NULL DEFAULT '1';
1027
1028 -- Despite it's name, ipb_address does not necessarily contain IP addresses :)
1029 ALTER TABLE ipblocks ALTER ipb_address TYPE TEXT USING ipb_address::TEXT;
1030
1031 -- New tables:
1032 CREATE TABLE redirect (
1033 rd_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
1034 rd_namespace SMALLINT NOT NULL,
1035 rd_title TEXT NOT NULL
1036 );
1037 CREATE INDEX redirect_ns_title ON redirect (rd_namespace,rd_title,rd_from);
1038
1039 CREATE TABLE querycachetwo (
1040 qcc_type TEXT NOT NULL,
1041 qcc_value SMALLINT NOT NULL DEFAULT 0,
1042 qcc_namespace INTEGER NOT NULL DEFAULT 0,
1043 qcc_title TEXT NOT NULL DEFAULT '',
1044 qcc_namespacetwo INTEGER NOT NULL DEFAULT 0,
1045 qcc_titletwo TEXT NOT NULL DEFAULT ''
1046 );
1047 CREATE INDEX querycachetwo_type_value ON querycachetwo (qcc_type, qcc_value);
1048 CREATE INDEX querycachetwo_title ON querycachetwo (qcc_type,qcc_namespace,qcc_title);
1049 CREATE INDEX querycachetwo_titletwo ON querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
1050
1051 -- New columns for fancy recentchanges display
1052 ALTER TABLE recentchanges ADD rc_old_len INT;
1053 ALTER TABLE recentchanges ADD rc_new_len INT;
1054
1055 -- Note this upgrade
1056 INSERT INTO mediawiki_version (type,mw_version,notes)
1057 VALUES ('Upgrade','MWVERSION','Upgrade from older version THISVERSION');
1058
1059 PGEND;
1060
1061 }
1062
1063 if ( !strlen($upgrade)) {
1064 print "No updates needed for version $version\n";
1065 return;
1066 }
1067
1068 $upgrade = str_replace( 'MWVERSION', $wgVersion, $upgrade );
1069 $upgrade = str_replace( 'THISVERSION', $version, $upgrade );
1070 $res = $wgDatabase->query("BEGIN;\n\n $upgrade\n\nCOMMIT;\n");
1071
1072 return;
1073 }
1074
1075 ?>