[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * DBMS-specific updater helper. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup Deployment 22 */ 23 24 require_once __DIR__ . '/../../maintenance/Maintenance.php'; 25 26 /** 27 * Class for handling database updates. Roughly based off of updaters.inc, with 28 * a few improvements :) 29 * 30 * @ingroup Deployment 31 * @since 1.17 32 */ 33 abstract class DatabaseUpdater { 34 35 /** 36 * Array of updates to perform on the database 37 * 38 * @var array 39 */ 40 protected $updates = array(); 41 42 /** 43 * Array of updates that were skipped 44 * 45 * @var array 46 */ 47 protected $updatesSkipped = array(); 48 49 /** 50 * List of extension-provided database updates 51 * @var array 52 */ 53 protected $extensionUpdates = array(); 54 55 /** 56 * Handle to the database subclass 57 * 58 * @var DatabaseBase 59 */ 60 protected $db; 61 62 protected $shared = false; 63 64 /** 65 * Scripts to run after database update 66 * Should be a subclass of LoggedUpdateMaintenance 67 */ 68 protected $postDatabaseUpdateMaintenance = array( 69 'DeleteDefaultMessages', 70 'PopulateRevisionLength', 71 'PopulateRevisionSha1', 72 'PopulateImageSha1', 73 'FixExtLinksProtocolRelative', 74 'PopulateFilearchiveSha1', 75 'PopulateBacklinkNamespace' 76 ); 77 78 /** 79 * File handle for SQL output. 80 * 81 * @var resource 82 */ 83 protected $fileHandle = null; 84 85 /** 86 * Flag specifying whether or not to skip schema (e.g. SQL-only) updates. 87 * 88 * @var bool 89 */ 90 protected $skipSchema = false; 91 92 /** 93 * Hold the value of $wgContentHandlerUseDB during the upgrade. 94 */ 95 protected $holdContentHandlerUseDB = true; 96 97 /** 98 * Constructor 99 * 100 * @param DatabaseBase $db To perform updates on 101 * @param bool $shared Whether to perform updates on shared tables 102 * @param Maintenance $maintenance Maintenance object which created us 103 */ 104 protected function __construct( DatabaseBase &$db, $shared, Maintenance $maintenance = null ) { 105 $this->db = $db; 106 $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files 107 $this->shared = $shared; 108 if ( $maintenance ) { 109 $this->maintenance = $maintenance; 110 $this->fileHandle = $maintenance->fileHandle; 111 } else { 112 $this->maintenance = new FakeMaintenance; 113 } 114 $this->maintenance->setDB( $db ); 115 $this->initOldGlobals(); 116 $this->loadExtensions(); 117 wfRunHooks( 'LoadExtensionSchemaUpdates', array( $this ) ); 118 } 119 120 /** 121 * Initialize all of the old globals. One day this should all become 122 * something much nicer 123 */ 124 private function initOldGlobals() { 125 global $wgExtNewTables, $wgExtNewFields, $wgExtPGNewFields, 126 $wgExtPGAlteredFields, $wgExtNewIndexes, $wgExtModifiedFields; 127 128 # For extensions only, should be populated via hooks 129 # $wgDBtype should be checked to specifiy the proper file 130 $wgExtNewTables = array(); // table, dir 131 $wgExtNewFields = array(); // table, column, dir 132 $wgExtPGNewFields = array(); // table, column, column attributes; for PostgreSQL 133 $wgExtPGAlteredFields = array(); // table, column, new type, conversion method; for PostgreSQL 134 $wgExtNewIndexes = array(); // table, index, dir 135 $wgExtModifiedFields = array(); // table, index, dir 136 } 137 138 /** 139 * Loads LocalSettings.php, if needed, and initialises everything needed for 140 * LoadExtensionSchemaUpdates hook. 141 */ 142 private function loadExtensions() { 143 if ( !defined( 'MEDIAWIKI_INSTALL' ) ) { 144 return; // already loaded 145 } 146 $vars = Installer::getExistingLocalSettings(); 147 if ( !$vars ) { 148 return; // no LocalSettings found 149 } 150 if ( !isset( $vars['wgHooks'] ) || !isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) { 151 return; 152 } 153 global $wgHooks, $wgAutoloadClasses; 154 $wgHooks['LoadExtensionSchemaUpdates'] = $vars['wgHooks']['LoadExtensionSchemaUpdates']; 155 $wgAutoloadClasses = $wgAutoloadClasses + $vars['wgAutoloadClasses']; 156 } 157 158 /** 159 * @param DatabaseBase $db 160 * @param bool $shared 161 * @param Maintenance $maintenance 162 * 163 * @throws MWException 164 * @return DatabaseUpdater 165 */ 166 public static function newForDB( &$db, $shared = false, $maintenance = null ) { 167 $type = $db->getType(); 168 if ( in_array( $type, Installer::getDBTypes() ) ) { 169 $class = ucfirst( $type ) . 'Updater'; 170 171 return new $class( $db, $shared, $maintenance ); 172 } else { 173 throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' ); 174 } 175 } 176 177 /** 178 * Get a database connection to run updates 179 * 180 * @return DatabaseBase 181 */ 182 public function getDB() { 183 return $this->db; 184 } 185 186 /** 187 * Output some text. If we're running from web, escape the text first. 188 * 189 * @param string $str Text to output 190 */ 191 public function output( $str ) { 192 if ( $this->maintenance->isQuiet() ) { 193 return; 194 } 195 global $wgCommandLineMode; 196 if ( !$wgCommandLineMode ) { 197 $str = htmlspecialchars( $str ); 198 } 199 echo $str; 200 flush(); 201 } 202 203 /** 204 * Add a new update coming from an extension. This should be called by 205 * extensions while executing the LoadExtensionSchemaUpdates hook. 206 * 207 * @since 1.17 208 * 209 * @param array $update The update to run. Format is the following: 210 * first item is the callback function, it also can be a 211 * simple string with the name of a function in this class, 212 * following elements are parameters to the function. 213 * Note that callback functions will receive this object as 214 * first parameter. 215 */ 216 public function addExtensionUpdate( array $update ) { 217 $this->extensionUpdates[] = $update; 218 } 219 220 /** 221 * Convenience wrapper for addExtensionUpdate() when adding a new table (which 222 * is the most common usage of updaters in an extension) 223 * 224 * @since 1.18 225 * 226 * @param string $tableName Name of table to create 227 * @param string $sqlPath Full path to the schema file 228 */ 229 public function addExtensionTable( $tableName, $sqlPath ) { 230 $this->extensionUpdates[] = array( 'addTable', $tableName, $sqlPath, true ); 231 } 232 233 /** 234 * @since 1.19 235 * 236 * @param string $tableName 237 * @param string $indexName 238 * @param string $sqlPath 239 */ 240 public function addExtensionIndex( $tableName, $indexName, $sqlPath ) { 241 $this->extensionUpdates[] = array( 'addIndex', $tableName, $indexName, $sqlPath, true ); 242 } 243 244 /** 245 * 246 * @since 1.19 247 * 248 * @param string $tableName 249 * @param string $columnName 250 * @param string $sqlPath 251 */ 252 public function addExtensionField( $tableName, $columnName, $sqlPath ) { 253 $this->extensionUpdates[] = array( 'addField', $tableName, $columnName, $sqlPath, true ); 254 } 255 256 /** 257 * 258 * @since 1.20 259 * 260 * @param string $tableName 261 * @param string $columnName 262 * @param string $sqlPath 263 */ 264 public function dropExtensionField( $tableName, $columnName, $sqlPath ) { 265 $this->extensionUpdates[] = array( 'dropField', $tableName, $columnName, $sqlPath, true ); 266 } 267 268 /** 269 * Drop an index from an extension table 270 * 271 * @since 1.21 272 * 273 * @param string $tableName The table name 274 * @param string $indexName The index name 275 * @param string $sqlPath The path to the SQL change path 276 */ 277 public function dropExtensionIndex( $tableName, $indexName, $sqlPath ) { 278 $this->extensionUpdates[] = array( 'dropIndex', $tableName, $indexName, $sqlPath, true ); 279 } 280 281 /** 282 * 283 * @since 1.20 284 * 285 * @param string $tableName 286 * @param string $sqlPath 287 */ 288 public function dropExtensionTable( $tableName, $sqlPath ) { 289 $this->extensionUpdates[] = array( 'dropTable', $tableName, $sqlPath, true ); 290 } 291 292 /** 293 * Rename an index on an extension table 294 * 295 * @since 1.21 296 * 297 * @param string $tableName The table name 298 * @param string $oldIndexName The old index name 299 * @param string $newIndexName The new index name 300 * @param string $sqlPath The path to the SQL change path 301 * @param bool $skipBothIndexExistWarning Whether to warn if both the old 302 * and the new indexes exist. [facultative; by default, false] 303 */ 304 public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName, 305 $sqlPath, $skipBothIndexExistWarning = false 306 ) { 307 $this->extensionUpdates[] = array( 308 'renameIndex', 309 $tableName, 310 $oldIndexName, 311 $newIndexName, 312 $skipBothIndexExistWarning, 313 $sqlPath, 314 true 315 ); 316 } 317 318 /** 319 * @since 1.21 320 * 321 * @param string $tableName The table name 322 * @param string $fieldName The field to be modified 323 * @param string $sqlPath The path to the SQL change path 324 */ 325 public function modifyExtensionField( $tableName, $fieldName, $sqlPath ) { 326 $this->extensionUpdates[] = array( 'modifyField', $tableName, $fieldName, $sqlPath, true ); 327 } 328 329 /** 330 * 331 * @since 1.20 332 * 333 * @param string $tableName 334 * @return bool 335 */ 336 public function tableExists( $tableName ) { 337 return ( $this->db->tableExists( $tableName, __METHOD__ ) ); 338 } 339 340 /** 341 * Add a maintenance script to be run after the database updates are complete. 342 * 343 * Script should subclass LoggedUpdateMaintenance 344 * 345 * @since 1.19 346 * 347 * @param string $class Name of a Maintenance subclass 348 */ 349 public function addPostDatabaseUpdateMaintenance( $class ) { 350 $this->postDatabaseUpdateMaintenance[] = $class; 351 } 352 353 /** 354 * Get the list of extension-defined updates 355 * 356 * @return array 357 */ 358 protected function getExtensionUpdates() { 359 return $this->extensionUpdates; 360 } 361 362 /** 363 * @since 1.17 364 * 365 * @return array 366 */ 367 public function getPostDatabaseUpdateMaintenance() { 368 return $this->postDatabaseUpdateMaintenance; 369 } 370 371 /** 372 * @since 1.21 373 * 374 * Writes the schema updates desired to a file for the DB Admin to run. 375 * @param array $schemaUpdate 376 */ 377 private function writeSchemaUpdateFile( $schemaUpdate = array() ) { 378 $updates = $this->updatesSkipped; 379 $this->updatesSkipped = array(); 380 381 foreach ( $updates as $funcList ) { 382 $func = $funcList[0]; 383 $arg = $funcList[1]; 384 $origParams = $funcList[2]; 385 call_user_func_array( $func, $arg ); 386 flush(); 387 $this->updatesSkipped[] = $origParams; 388 } 389 } 390 391 /** 392 * Do all the updates 393 * 394 * @param array $what What updates to perform 395 */ 396 public function doUpdates( $what = array( 'core', 'extensions', 'stats' ) ) { 397 global $wgVersion; 398 399 $this->db->begin( __METHOD__ ); 400 $what = array_flip( $what ); 401 $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null; 402 if ( isset( $what['core'] ) ) { 403 $this->runUpdates( $this->getCoreUpdateList(), false ); 404 } 405 if ( isset( $what['extensions'] ) ) { 406 $this->runUpdates( $this->getOldGlobalUpdates(), false ); 407 $this->runUpdates( $this->getExtensionUpdates(), true ); 408 } 409 410 if ( isset( $what['stats'] ) ) { 411 $this->checkStats(); 412 } 413 414 $this->setAppliedUpdates( $wgVersion, $this->updates ); 415 416 if ( $this->fileHandle ) { 417 $this->skipSchema = false; 418 $this->writeSchemaUpdateFile(); 419 $this->setAppliedUpdates( "$wgVersion-schema", $this->updatesSkipped ); 420 } 421 422 $this->db->commit( __METHOD__ ); 423 } 424 425 /** 426 * Helper function for doUpdates() 427 * 428 * @param array $updates Array of updates to run 429 * @param bool $passSelf Whether to pass this object we calling external functions 430 */ 431 private function runUpdates( array $updates, $passSelf ) { 432 $updatesDone = array(); 433 $updatesSkipped = array(); 434 foreach ( $updates as $params ) { 435 $origParams = $params; 436 $func = array_shift( $params ); 437 if ( !is_array( $func ) && method_exists( $this, $func ) ) { 438 $func = array( $this, $func ); 439 } elseif ( $passSelf ) { 440 array_unshift( $params, $this ); 441 } 442 $ret = call_user_func_array( $func, $params ); 443 flush(); 444 if ( $ret !== false ) { 445 $updatesDone[] = $origParams; 446 } else { 447 $updatesSkipped[] = array( $func, $params, $origParams ); 448 } 449 } 450 $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped ); 451 $this->updates = array_merge( $this->updates, $updatesDone ); 452 } 453 454 /** 455 * @param string $version 456 * @param array $updates 457 */ 458 protected function setAppliedUpdates( $version, $updates = array() ) { 459 $this->db->clearFlag( DBO_DDLMODE ); 460 if ( !$this->canUseNewUpdatelog() ) { 461 return; 462 } 463 $key = "updatelist-$version-" . time(); 464 $this->db->insert( 'updatelog', 465 array( 'ul_key' => $key, 'ul_value' => serialize( $updates ) ), 466 __METHOD__ ); 467 $this->db->setFlag( DBO_DDLMODE ); 468 } 469 470 /** 471 * Helper function: check if the given key is present in the updatelog table. 472 * Obviously, only use this for updates that occur after the updatelog table was 473 * created! 474 * @param string $key Name of the key to check for 475 * @return bool 476 */ 477 public function updateRowExists( $key ) { 478 $row = $this->db->selectRow( 479 'updatelog', 480 # Bug 65813 481 '1 AS X', 482 array( 'ul_key' => $key ), 483 __METHOD__ 484 ); 485 486 return (bool)$row; 487 } 488 489 /** 490 * Helper function: Add a key to the updatelog table 491 * Obviously, only use this for updates that occur after the updatelog table was 492 * created! 493 * @param string $key Name of key to insert 494 * @param string $val [optional] Value to insert along with the key 495 */ 496 public function insertUpdateRow( $key, $val = null ) { 497 $this->db->clearFlag( DBO_DDLMODE ); 498 $values = array( 'ul_key' => $key ); 499 if ( $val && $this->canUseNewUpdatelog() ) { 500 $values['ul_value'] = $val; 501 } 502 $this->db->insert( 'updatelog', $values, __METHOD__, 'IGNORE' ); 503 $this->db->setFlag( DBO_DDLMODE ); 504 } 505 506 /** 507 * Updatelog was changed in 1.17 to have a ul_value column so we can record 508 * more information about what kind of updates we've done (that's what this 509 * class does). Pre-1.17 wikis won't have this column, and really old wikis 510 * might not even have updatelog at all 511 * 512 * @return bool 513 */ 514 protected function canUseNewUpdatelog() { 515 return $this->db->tableExists( 'updatelog', __METHOD__ ) && 516 $this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ ); 517 } 518 519 /** 520 * Returns whether updates should be executed on the database table $name. 521 * Updates will be prevented if the table is a shared table and it is not 522 * specified to run updates on shared tables. 523 * 524 * @param string $name Table name 525 * @return bool 526 */ 527 protected function doTable( $name ) { 528 global $wgSharedDB, $wgSharedTables; 529 530 // Don't bother to check $wgSharedTables if there isn't a shared database 531 // or the user actually also wants to do updates on the shared database. 532 if ( $wgSharedDB === null || $this->shared ) { 533 return true; 534 } 535 536 if ( in_array( $name, $wgSharedTables ) ) { 537 $this->output( "...skipping update to shared table $name.\n" ); 538 return false; 539 } else { 540 return true; 541 } 542 } 543 544 /** 545 * Before 1.17, we used to handle updates via stuff like 546 * $wgExtNewTables/Fields/Indexes. This is nasty :) We refactored a lot 547 * of this in 1.17 but we want to remain back-compatible for a while. So 548 * load up these old global-based things into our update list. 549 * 550 * @return array 551 */ 552 protected function getOldGlobalUpdates() { 553 global $wgExtNewFields, $wgExtNewTables, $wgExtModifiedFields, 554 $wgExtNewIndexes; 555 556 $updates = array(); 557 558 foreach ( $wgExtNewTables as $tableRecord ) { 559 $updates[] = array( 560 'addTable', $tableRecord[0], $tableRecord[1], true 561 ); 562 } 563 564 foreach ( $wgExtNewFields as $fieldRecord ) { 565 $updates[] = array( 566 'addField', $fieldRecord[0], $fieldRecord[1], 567 $fieldRecord[2], true 568 ); 569 } 570 571 foreach ( $wgExtNewIndexes as $fieldRecord ) { 572 $updates[] = array( 573 'addIndex', $fieldRecord[0], $fieldRecord[1], 574 $fieldRecord[2], true 575 ); 576 } 577 578 foreach ( $wgExtModifiedFields as $fieldRecord ) { 579 $updates[] = array( 580 'modifyField', $fieldRecord[0], $fieldRecord[1], 581 $fieldRecord[2], true 582 ); 583 } 584 585 return $updates; 586 } 587 588 /** 589 * Get an array of updates to perform on the database. Should return a 590 * multi-dimensional array. The main key is the MediaWiki version (1.12, 591 * 1.13...) with the values being arrays of updates, identical to how 592 * updaters.inc did it (for now) 593 * 594 * @return array 595 */ 596 abstract protected function getCoreUpdateList(); 597 598 /** 599 * Append an SQL fragment to the open file handle. 600 * 601 * @param string $filename File name to open 602 */ 603 public function copyFile( $filename ) { 604 $this->db->sourceFile( $filename, false, false, false, 605 array( $this, 'appendLine' ) 606 ); 607 } 608 609 /** 610 * Append a line to the open filehandle. The line is assumed to 611 * be a complete SQL statement. 612 * 613 * This is used as a callback for for sourceLine(). 614 * 615 * @param string $line Text to append to the file 616 * @return bool False to skip actually executing the file 617 * @throws MWException 618 */ 619 public function appendLine( $line ) { 620 $line = rtrim( $line ) . ";\n"; 621 if ( fwrite( $this->fileHandle, $line ) === false ) { 622 throw new MWException( "trouble writing file" ); 623 } 624 625 return false; 626 } 627 628 /** 629 * Applies a SQL patch 630 * 631 * @param string $path Path to the patch file 632 * @param bool $isFullPath Whether to treat $path as a relative or not 633 * @param string $msg Description of the patch 634 * @return bool False if patch is skipped. 635 */ 636 protected function applyPatch( $path, $isFullPath = false, $msg = null ) { 637 if ( $msg === null ) { 638 $msg = "Applying $path patch"; 639 } 640 if ( $this->skipSchema ) { 641 $this->output( "...skipping schema change ($msg).\n" ); 642 643 return false; 644 } 645 646 $this->output( "$msg ..." ); 647 648 if ( !$isFullPath ) { 649 $path = $this->db->patchPath( $path ); 650 } 651 if ( $this->fileHandle !== null ) { 652 $this->copyFile( $path ); 653 } else { 654 $this->db->sourceFile( $path ); 655 } 656 $this->output( "done.\n" ); 657 658 return true; 659 } 660 661 /** 662 * Add a new table to the database 663 * 664 * @param string $name Name of the new table 665 * @param string $patch Path to the patch file 666 * @param bool $fullpath Whether to treat $patch path as a relative or not 667 * @return bool False if this was skipped because schema changes are skipped 668 */ 669 protected function addTable( $name, $patch, $fullpath = false ) { 670 if ( !$this->doTable( $name ) ) { 671 return true; 672 } 673 674 if ( $this->db->tableExists( $name, __METHOD__ ) ) { 675 $this->output( "...$name table already exists.\n" ); 676 } else { 677 return $this->applyPatch( $patch, $fullpath, "Creating $name table" ); 678 } 679 680 return true; 681 } 682 683 /** 684 * Add a new field to an existing table 685 * 686 * @param string $table Name of the table to modify 687 * @param string $field Name of the new field 688 * @param string $patch Path to the patch file 689 * @param bool $fullpath Whether to treat $patch path as a relative or not 690 * @return bool False if this was skipped because schema changes are skipped 691 */ 692 protected function addField( $table, $field, $patch, $fullpath = false ) { 693 if ( !$this->doTable( $table ) ) { 694 return true; 695 } 696 697 if ( !$this->db->tableExists( $table, __METHOD__ ) ) { 698 $this->output( "...$table table does not exist, skipping new field patch.\n" ); 699 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) { 700 $this->output( "...have $field field in $table table.\n" ); 701 } else { 702 return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" ); 703 } 704 705 return true; 706 } 707 708 /** 709 * Add a new index to an existing table 710 * 711 * @param string $table Name of the table to modify 712 * @param string $index Name of the new index 713 * @param string $patch Path to the patch file 714 * @param bool $fullpath Whether to treat $patch path as a relative or not 715 * @return bool False if this was skipped because schema changes are skipped 716 */ 717 protected function addIndex( $table, $index, $patch, $fullpath = false ) { 718 if ( !$this->doTable( $table ) ) { 719 return true; 720 } 721 722 if ( !$this->db->tableExists( $table, __METHOD__ ) ) { 723 $this->output( "...skipping: '$table' table doesn't exist yet.\n" ); 724 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) { 725 $this->output( "...index $index already set on $table table.\n" ); 726 } else { 727 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" ); 728 } 729 730 return true; 731 } 732 733 /** 734 * Drop a field from an existing table 735 * 736 * @param string $table Name of the table to modify 737 * @param string $field Name of the old field 738 * @param string $patch Path to the patch file 739 * @param bool $fullpath Whether to treat $patch path as a relative or not 740 * @return bool False if this was skipped because schema changes are skipped 741 */ 742 protected function dropField( $table, $field, $patch, $fullpath = false ) { 743 if ( !$this->doTable( $table ) ) { 744 return true; 745 } 746 747 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) { 748 return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" ); 749 } else { 750 $this->output( "...$table table does not contain $field field.\n" ); 751 } 752 753 return true; 754 } 755 756 /** 757 * Drop an index from an existing table 758 * 759 * @param string $table Name of the table to modify 760 * @param string $index Name of the index 761 * @param string $patch Path to the patch file 762 * @param bool $fullpath Whether to treat $patch path as a relative or not 763 * @return bool False if this was skipped because schema changes are skipped 764 */ 765 protected function dropIndex( $table, $index, $patch, $fullpath = false ) { 766 if ( !$this->doTable( $table ) ) { 767 return true; 768 } 769 770 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) { 771 return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" ); 772 } else { 773 $this->output( "...$index key doesn't exist.\n" ); 774 } 775 776 return true; 777 } 778 779 /** 780 * Rename an index from an existing table 781 * 782 * @param string $table Name of the table to modify 783 * @param string $oldIndex Old name of the index 784 * @param string $newIndex New name of the index 785 * @param bool $skipBothIndexExistWarning Whether to warn if both the 786 * old and the new indexes exist. 787 * @param string $patch Path to the patch file 788 * @param bool $fullpath Whether to treat $patch path as a relative or not 789 * @return bool False if this was skipped because schema changes are skipped 790 */ 791 protected function renameIndex( $table, $oldIndex, $newIndex, 792 $skipBothIndexExistWarning, $patch, $fullpath = false 793 ) { 794 if ( !$this->doTable( $table ) ) { 795 return true; 796 } 797 798 // First requirement: the table must exist 799 if ( !$this->db->tableExists( $table, __METHOD__ ) ) { 800 $this->output( "...skipping: '$table' table doesn't exist yet.\n" ); 801 802 return true; 803 } 804 805 // Second requirement: the new index must be missing 806 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) { 807 $this->output( "...index $newIndex already set on $table table.\n" ); 808 if ( !$skipBothIndexExistWarning && 809 $this->db->indexExists( $table, $oldIndex, __METHOD__ ) 810 ) { 811 $this->output( "...WARNING: $oldIndex still exists, despite it has " . 812 "been renamed into $newIndex (which also exists).\n" . 813 " $oldIndex should be manually removed if not needed anymore.\n" ); 814 } 815 816 return true; 817 } 818 819 // Third requirement: the old index must exist 820 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) { 821 $this->output( "...skipping: index $oldIndex doesn't exist.\n" ); 822 823 return true; 824 } 825 826 // Requirements have been satisfied, patch can be applied 827 return $this->applyPatch( 828 $patch, 829 $fullpath, 830 "Renaming index $oldIndex into $newIndex to table $table" 831 ); 832 } 833 834 /** 835 * If the specified table exists, drop it, or execute the 836 * patch if one is provided. 837 * 838 * Public @since 1.20 839 * 840 * @param string $table Table to drop. 841 * @param string|bool $patch String of patch file that will drop the table. Default: false. 842 * @param bool $fullpath Whether $patch is a full path. Default: false. 843 * @return bool False if this was skipped because schema changes are skipped 844 */ 845 public function dropTable( $table, $patch = false, $fullpath = false ) { 846 if ( !$this->doTable( $table ) ) { 847 return true; 848 } 849 850 if ( $this->db->tableExists( $table, __METHOD__ ) ) { 851 $msg = "Dropping table $table"; 852 853 if ( $patch === false ) { 854 $this->output( "$msg ..." ); 855 $this->db->dropTable( $table, __METHOD__ ); 856 $this->output( "done.\n" ); 857 } else { 858 return $this->applyPatch( $patch, $fullpath, $msg ); 859 } 860 } else { 861 $this->output( "...$table doesn't exist.\n" ); 862 } 863 864 return true; 865 } 866 867 /** 868 * Modify an existing field 869 * 870 * @param string $table Name of the table to which the field belongs 871 * @param string $field Name of the field to modify 872 * @param string $patch Path to the patch file 873 * @param bool $fullpath Whether to treat $patch path as a relative or not 874 * @return bool False if this was skipped because schema changes are skipped 875 */ 876 public function modifyField( $table, $field, $patch, $fullpath = false ) { 877 if ( !$this->doTable( $table ) ) { 878 return true; 879 } 880 881 $updateKey = "$table-$field-$patch"; 882 if ( !$this->db->tableExists( $table, __METHOD__ ) ) { 883 $this->output( "...$table table does not exist, skipping modify field patch.\n" ); 884 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) { 885 $this->output( "...$field field does not exist in $table table, " . 886 "skipping modify field patch.\n" ); 887 } elseif ( $this->updateRowExists( $updateKey ) ) { 888 $this->output( "...$field in table $table already modified by patch $patch.\n" ); 889 } else { 890 $this->insertUpdateRow( $updateKey ); 891 892 return $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" ); 893 } 894 895 return true; 896 } 897 898 /** 899 * Purge the objectcache table 900 */ 901 public function purgeCache() { 902 global $wgLocalisationCacheConf; 903 # We can't guarantee that the user will be able to use TRUNCATE, 904 # but we know that DELETE is available to us 905 $this->output( "Purging caches..." ); 906 $this->db->delete( 'objectcache', '*', __METHOD__ ); 907 if ( $wgLocalisationCacheConf['manualRecache'] ) { 908 $this->rebuildLocalisationCache(); 909 } 910 MessageBlobStore::getInstance()->clear(); 911 $this->output( "done.\n" ); 912 } 913 914 /** 915 * Check the site_stats table is not properly populated. 916 */ 917 protected function checkStats() { 918 $this->output( "...site_stats is populated..." ); 919 $row = $this->db->selectRow( 'site_stats', '*', array( 'ss_row_id' => 1 ), __METHOD__ ); 920 if ( $row === false ) { 921 $this->output( "data is missing! rebuilding...\n" ); 922 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) { 923 $this->output( "missing ss_total_pages, rebuilding...\n" ); 924 } else { 925 $this->output( "done.\n" ); 926 927 return; 928 } 929 SiteStatsInit::doAllAndCommit( $this->db ); 930 } 931 932 # Common updater functions 933 934 /** 935 * Sets the number of active users in the site_stats table 936 */ 937 protected function doActiveUsersInit() { 938 $activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', false, __METHOD__ ); 939 if ( $activeUsers == -1 ) { 940 $activeUsers = $this->db->selectField( 'recentchanges', 941 'COUNT( DISTINCT rc_user_text )', 942 array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ), __METHOD__ 943 ); 944 $this->db->update( 'site_stats', 945 array( 'ss_active_users' => intval( $activeUsers ) ), 946 array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 ) 947 ); 948 } 949 $this->output( "...ss_active_users user count set...\n" ); 950 } 951 952 /** 953 * Populates the log_user_text field in the logging table 954 */ 955 protected function doLogUsertextPopulation() { 956 if ( !$this->updateRowExists( 'populate log_usertext' ) ) { 957 $this->output( 958 "Populating log_user_text field, printing progress markers. For large\n" . 959 "databases, you may want to hit Ctrl-C and do this manually with\n" . 960 "maintenance/populateLogUsertext.php.\n" 961 ); 962 963 $task = $this->maintenance->runChild( 'PopulateLogUsertext' ); 964 $task->execute(); 965 $this->output( "done.\n" ); 966 } 967 } 968 969 /** 970 * Migrate log params to new table and index for searching 971 */ 972 protected function doLogSearchPopulation() { 973 if ( !$this->updateRowExists( 'populate log_search' ) ) { 974 $this->output( 975 "Populating log_search table, printing progress markers. For large\n" . 976 "databases, you may want to hit Ctrl-C and do this manually with\n" . 977 "maintenance/populateLogSearch.php.\n" ); 978 979 $task = $this->maintenance->runChild( 'PopulateLogSearch' ); 980 $task->execute(); 981 $this->output( "done.\n" ); 982 } 983 } 984 985 /** 986 * Updates the timestamps in the transcache table 987 * @return bool 988 */ 989 protected function doUpdateTranscacheField() { 990 if ( $this->updateRowExists( 'convert transcache field' ) ) { 991 $this->output( "...transcache tc_time already converted.\n" ); 992 993 return true; 994 } 995 996 return $this->applyPatch( 'patch-tc-timestamp.sql', false, 997 "Converting tc_time from UNIX epoch to MediaWiki timestamp" ); 998 } 999 1000 /** 1001 * Update CategoryLinks collation 1002 */ 1003 protected function doCollationUpdate() { 1004 global $wgCategoryCollation; 1005 if ( $this->db->fieldExists( 'categorylinks', 'cl_collation', __METHOD__ ) ) { 1006 if ( $this->db->selectField( 1007 'categorylinks', 1008 'COUNT(*)', 1009 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ), 1010 __METHOD__ 1011 ) == 0 1012 ) { 1013 $this->output( "...collations up-to-date.\n" ); 1014 1015 return; 1016 } 1017 1018 $this->output( "Updating category collations..." ); 1019 $task = $this->maintenance->runChild( 'UpdateCollation' ); 1020 $task->execute(); 1021 $this->output( "...done.\n" ); 1022 } 1023 } 1024 1025 /** 1026 * Migrates user options from the user table blob to user_properties 1027 */ 1028 protected function doMigrateUserOptions() { 1029 if ( $this->db->tableExists( 'user_properties' ) ) { 1030 $cl = $this->maintenance->runChild( 'ConvertUserOptions', 'convertUserOptions.php' ); 1031 $cl->execute(); 1032 $this->output( "done.\n" ); 1033 } 1034 } 1035 1036 /** 1037 * Rebuilds the localisation cache 1038 */ 1039 protected function rebuildLocalisationCache() { 1040 /** 1041 * @var $cl RebuildLocalisationCache 1042 */ 1043 $cl = $this->maintenance->runChild( 'RebuildLocalisationCache', 'rebuildLocalisationCache.php' ); 1044 $this->output( "Rebuilding localisation cache...\n" ); 1045 $cl->setForce(); 1046 $cl->execute(); 1047 $this->output( "done.\n" ); 1048 } 1049 1050 /** 1051 * Turns off content handler fields during parts of the upgrade 1052 * where they aren't available. 1053 */ 1054 protected function disableContentHandlerUseDB() { 1055 global $wgContentHandlerUseDB; 1056 1057 if ( $wgContentHandlerUseDB ) { 1058 $this->output( "Turning off Content Handler DB fields for this part of upgrade.\n" ); 1059 $this->holdContentHandlerUseDB = $wgContentHandlerUseDB; 1060 $wgContentHandlerUseDB = false; 1061 } 1062 } 1063 1064 /** 1065 * Turns content handler fields back on. 1066 */ 1067 protected function enableContentHandlerUseDB() { 1068 global $wgContentHandlerUseDB; 1069 1070 if ( $this->holdContentHandlerUseDB ) { 1071 $this->output( "Content Handler DB fields should be usable now.\n" ); 1072 $wgContentHandlerUseDB = $this->holdContentHandlerUseDB; 1073 } 1074 } 1075 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |