MediaWiki
master
|
00001 <?php 00024 require_once( __DIR__ . '/../../maintenance/Maintenance.php' ); 00025 00033 abstract class DatabaseUpdater { 00034 00040 protected $updates = array(); 00041 00046 protected $extensionUpdates = array(); 00047 00053 protected $db; 00054 00055 protected $shared = false; 00056 00061 protected $postDatabaseUpdateMaintenance = array( 00062 'DeleteDefaultMessages', 00063 'PopulateRevisionLength', 00064 'PopulateRevisionSha1', 00065 'PopulateImageSha1', 00066 'FixExtLinksProtocolRelative', 00067 'PopulateFilearchiveSha1', 00068 ); 00069 00077 protected function __construct( DatabaseBase &$db, $shared, Maintenance $maintenance = null ) { 00078 $this->db = $db; 00079 $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files 00080 $this->shared = $shared; 00081 if ( $maintenance ) { 00082 $this->maintenance = $maintenance; 00083 } else { 00084 $this->maintenance = new FakeMaintenance; 00085 } 00086 $this->maintenance->setDB( $db ); 00087 $this->initOldGlobals(); 00088 $this->loadExtensions(); 00089 wfRunHooks( 'LoadExtensionSchemaUpdates', array( $this ) ); 00090 } 00091 00096 private function initOldGlobals() { 00097 global $wgExtNewTables, $wgExtNewFields, $wgExtPGNewFields, 00098 $wgExtPGAlteredFields, $wgExtNewIndexes, $wgExtModifiedFields; 00099 00100 # For extensions only, should be populated via hooks 00101 # $wgDBtype should be checked to specifiy the proper file 00102 $wgExtNewTables = array(); // table, dir 00103 $wgExtNewFields = array(); // table, column, dir 00104 $wgExtPGNewFields = array(); // table, column, column attributes; for PostgreSQL 00105 $wgExtPGAlteredFields = array(); // table, column, new type, conversion method; for PostgreSQL 00106 $wgExtNewIndexes = array(); // table, index, dir 00107 $wgExtModifiedFields = array(); // table, index, dir 00108 } 00109 00113 private function loadExtensions() { 00114 if ( !defined( 'MEDIAWIKI_INSTALL' ) ) { 00115 return; // already loaded 00116 } 00117 $vars = Installer::getExistingLocalSettings(); 00118 if ( !$vars ) { 00119 return; // no LocalSettings found 00120 } 00121 if ( !isset( $vars['wgHooks'] ) || !isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) { 00122 return; 00123 } 00124 global $wgHooks, $wgAutoloadClasses; 00125 $wgHooks['LoadExtensionSchemaUpdates'] = $vars['wgHooks']['LoadExtensionSchemaUpdates']; 00126 $wgAutoloadClasses = $wgAutoloadClasses + $vars['wgAutoloadClasses']; 00127 } 00128 00136 public static function newForDB( &$db, $shared = false, $maintenance = null ) { 00137 $type = $db->getType(); 00138 if( in_array( $type, Installer::getDBTypes() ) ) { 00139 $class = ucfirst( $type ) . 'Updater'; 00140 return new $class( $db, $shared, $maintenance ); 00141 } else { 00142 throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' ); 00143 } 00144 } 00145 00151 public function getDB() { 00152 return $this->db; 00153 } 00154 00160 public function output( $str ) { 00161 if ( $this->maintenance->isQuiet() ) { 00162 return; 00163 } 00164 global $wgCommandLineMode; 00165 if( !$wgCommandLineMode ) { 00166 $str = htmlspecialchars( $str ); 00167 } 00168 echo $str; 00169 flush(); 00170 } 00171 00185 public function addExtensionUpdate( array $update ) { 00186 $this->extensionUpdates[] = $update; 00187 } 00188 00198 public function addExtensionTable( $tableName, $sqlPath ) { 00199 $this->extensionUpdates[] = array( 'addTable', $tableName, $sqlPath, true ); 00200 } 00201 00209 public function addExtensionIndex( $tableName, $indexName, $sqlPath ) { 00210 $this->extensionUpdates[] = array( 'addIndex', $tableName, $indexName, $sqlPath, true ); 00211 } 00212 00221 public function addExtensionField( $tableName, $columnName, $sqlPath ) { 00222 $this->extensionUpdates[] = array( 'addField', $tableName, $columnName, $sqlPath, true ); 00223 } 00224 00233 public function dropExtensionField( $tableName, $columnName, $sqlPath ) { 00234 $this->extensionUpdates[] = array( 'dropField', $tableName, $columnName, $sqlPath, true ); 00235 } 00236 00244 public function dropExtensionTable( $tableName, $sqlPath ) { 00245 $this->extensionUpdates[] = array( 'dropTable', $tableName, $sqlPath, true ); 00246 } 00247 00255 public function tableExists( $tableName ) { 00256 return ( $this->db->tableExists( $tableName, __METHOD__ ) ); 00257 } 00258 00268 public function addPostDatabaseUpdateMaintenance( $class ) { 00269 $this->postDatabaseUpdateMaintenance[] = $class; 00270 } 00271 00277 protected function getExtensionUpdates() { 00278 return $this->extensionUpdates; 00279 } 00280 00286 public function getPostDatabaseUpdateMaintenance() { 00287 return $this->postDatabaseUpdateMaintenance; 00288 } 00289 00295 public function doUpdates( $what = array( 'core', 'extensions', 'stats' ) ) { 00296 global $wgVersion; 00297 00298 $this->db->begin( __METHOD__ ); 00299 $what = array_flip( $what ); 00300 if ( isset( $what['core'] ) ) { 00301 $this->runUpdates( $this->getCoreUpdateList(), false ); 00302 } 00303 if ( isset( $what['extensions'] ) ) { 00304 $this->runUpdates( $this->getOldGlobalUpdates(), false ); 00305 $this->runUpdates( $this->getExtensionUpdates(), true ); 00306 } 00307 00308 $this->setAppliedUpdates( $wgVersion, $this->updates ); 00309 00310 if ( isset( $what['stats'] ) ) { 00311 $this->checkStats(); 00312 } 00313 $this->db->commit( __METHOD__ ); 00314 } 00315 00323 private function runUpdates( array $updates, $passSelf ) { 00324 foreach ( $updates as $params ) { 00325 $func = array_shift( $params ); 00326 if( !is_array( $func ) && method_exists( $this, $func ) ) { 00327 $func = array( $this, $func ); 00328 } elseif ( $passSelf ) { 00329 array_unshift( $params, $this ); 00330 } 00331 call_user_func_array( $func, $params ); 00332 flush(); 00333 } 00334 $this->updates = array_merge( $this->updates, $updates ); 00335 } 00336 00341 protected function setAppliedUpdates( $version, $updates = array() ) { 00342 $this->db->clearFlag( DBO_DDLMODE ); 00343 if( !$this->canUseNewUpdatelog() ) { 00344 return; 00345 } 00346 $key = "updatelist-$version-" . time(); 00347 $this->db->insert( 'updatelog', 00348 array( 'ul_key' => $key, 'ul_value' => serialize( $updates ) ), 00349 __METHOD__ ); 00350 $this->db->setFlag( DBO_DDLMODE ); 00351 } 00352 00361 public function updateRowExists( $key ) { 00362 $row = $this->db->selectRow( 00363 'updatelog', 00364 '1', 00365 array( 'ul_key' => $key ), 00366 __METHOD__ 00367 ); 00368 return (bool)$row; 00369 } 00370 00378 public function insertUpdateRow( $key, $val = null ) { 00379 $this->db->clearFlag( DBO_DDLMODE ); 00380 $values = array( 'ul_key' => $key ); 00381 if( $val && $this->canUseNewUpdatelog() ) { 00382 $values['ul_value'] = $val; 00383 } 00384 $this->db->insert( 'updatelog', $values, __METHOD__, 'IGNORE' ); 00385 $this->db->setFlag( DBO_DDLMODE ); 00386 } 00387 00396 protected function canUseNewUpdatelog() { 00397 return $this->db->tableExists( 'updatelog', __METHOD__ ) && 00398 $this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ ); 00399 } 00400 00409 protected function getOldGlobalUpdates() { 00410 global $wgExtNewFields, $wgExtNewTables, $wgExtModifiedFields, 00411 $wgExtNewIndexes, $wgSharedDB, $wgSharedTables; 00412 00413 $doUser = $this->shared ? 00414 $wgSharedDB && in_array( 'user', $wgSharedTables ) : 00415 !$wgSharedDB || !in_array( 'user', $wgSharedTables ); 00416 00417 $updates = array(); 00418 00419 foreach ( $wgExtNewTables as $tableRecord ) { 00420 $updates[] = array( 00421 'addTable', $tableRecord[0], $tableRecord[1], true 00422 ); 00423 } 00424 00425 foreach ( $wgExtNewFields as $fieldRecord ) { 00426 if ( $fieldRecord[0] != 'user' || $doUser ) { 00427 $updates[] = array( 00428 'addField', $fieldRecord[0], $fieldRecord[1], 00429 $fieldRecord[2], true 00430 ); 00431 } 00432 } 00433 00434 foreach ( $wgExtNewIndexes as $fieldRecord ) { 00435 $updates[] = array( 00436 'addIndex', $fieldRecord[0], $fieldRecord[1], 00437 $fieldRecord[2], true 00438 ); 00439 } 00440 00441 foreach ( $wgExtModifiedFields as $fieldRecord ) { 00442 $updates[] = array( 00443 'modifyField', $fieldRecord[0], $fieldRecord[1], 00444 $fieldRecord[2], true 00445 ); 00446 } 00447 00448 return $updates; 00449 } 00450 00459 protected abstract function getCoreUpdateList(); 00460 00467 protected function applyPatch( $path, $isFullPath = false, $msg = null ) { 00468 if ( $msg === null ) { 00469 $msg = "Applying $path patch"; 00470 } 00471 00472 if ( !$isFullPath ) { 00473 $path = $this->db->patchPath( $path ); 00474 } 00475 00476 $this->output( "$msg ..." ); 00477 $this->db->sourceFile( $path ); 00478 $this->output( "done.\n" ); 00479 } 00480 00487 protected function addTable( $name, $patch, $fullpath = false ) { 00488 if ( $this->db->tableExists( $name, __METHOD__ ) ) { 00489 $this->output( "...$name table already exists.\n" ); 00490 } else { 00491 $this->applyPatch( $patch, $fullpath, "Creating $name table" ); 00492 } 00493 } 00494 00502 protected function addField( $table, $field, $patch, $fullpath = false ) { 00503 if ( !$this->db->tableExists( $table, __METHOD__ ) ) { 00504 $this->output( "...$table table does not exist, skipping new field patch.\n" ); 00505 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) { 00506 $this->output( "...have $field field in $table table.\n" ); 00507 } else { 00508 $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" ); 00509 } 00510 } 00511 00519 protected function addIndex( $table, $index, $patch, $fullpath = false ) { 00520 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) { 00521 $this->output( "...index $index already set on $table table.\n" ); 00522 } else { 00523 $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" ); 00524 } 00525 } 00526 00535 protected function dropField( $table, $field, $patch, $fullpath = false ) { 00536 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) { 00537 $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" ); 00538 } else { 00539 $this->output( "...$table table does not contain $field field.\n" ); 00540 } 00541 } 00542 00551 protected function dropIndex( $table, $index, $patch, $fullpath = false ) { 00552 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) { 00553 $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" ); 00554 } else { 00555 $this->output( "...$index key doesn't exist.\n" ); 00556 } 00557 } 00558 00569 public function dropTable( $table, $patch = false, $fullpath = false ) { 00570 if ( $this->db->tableExists( $table, __METHOD__ ) ) { 00571 $msg = "Dropping table $table"; 00572 00573 if ( $patch === false ) { 00574 $this->output( "$msg ..." ); 00575 $this->db->dropTable( $table, __METHOD__ ); 00576 $this->output( "done.\n" ); 00577 } 00578 else { 00579 $this->applyPatch( $patch, $fullpath, $msg ); 00580 } 00581 00582 } else { 00583 $this->output( "...$table doesn't exist.\n" ); 00584 } 00585 } 00586 00595 public function modifyField( $table, $field, $patch, $fullpath = false ) { 00596 $updateKey = "$table-$field-$patch"; 00597 if ( !$this->db->tableExists( $table, __METHOD__ ) ) { 00598 $this->output( "...$table table does not exist, skipping modify field patch.\n" ); 00599 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) { 00600 $this->output( "...$field field does not exist in $table table, skipping modify field patch.\n" ); 00601 } elseif( $this->updateRowExists( $updateKey ) ) { 00602 $this->output( "...$field in table $table already modified by patch $patch.\n" ); 00603 } else { 00604 $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" ); 00605 $this->insertUpdateRow( $updateKey ); 00606 } 00607 } 00608 00612 public function purgeCache() { 00613 global $wgLocalisationCacheConf; 00614 # We can't guarantee that the user will be able to use TRUNCATE, 00615 # but we know that DELETE is available to us 00616 $this->output( "Purging caches..." ); 00617 $this->db->delete( 'objectcache', '*', __METHOD__ ); 00618 if ( $wgLocalisationCacheConf['manualRecache'] ) { 00619 $this->rebuildLocalisationCache(); 00620 } 00621 $this->output( "done.\n" ); 00622 } 00623 00627 protected function checkStats() { 00628 $this->output( "...site_stats is populated..." ); 00629 $row = $this->db->selectRow( 'site_stats', '*', array( 'ss_row_id' => 1 ), __METHOD__ ); 00630 if ( $row === false ) { 00631 $this->output( "data is missing! rebuilding...\n" ); 00632 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) { 00633 $this->output( "missing ss_total_pages, rebuilding...\n" ); 00634 } else { 00635 $this->output( "done.\n" ); 00636 return; 00637 } 00638 SiteStatsInit::doAllAndCommit( $this->db ); 00639 } 00640 00641 # Common updater functions 00642 00646 protected function doActiveUsersInit() { 00647 $activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', false, __METHOD__ ); 00648 if ( $activeUsers == -1 ) { 00649 $activeUsers = $this->db->selectField( 'recentchanges', 00650 'COUNT( DISTINCT rc_user_text )', 00651 array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ), __METHOD__ 00652 ); 00653 $this->db->update( 'site_stats', 00654 array( 'ss_active_users' => intval( $activeUsers ) ), 00655 array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 ) 00656 ); 00657 } 00658 $this->output( "...ss_active_users user count set...\n" ); 00659 } 00660 00664 protected function doLogUsertextPopulation() { 00665 if ( !$this->updateRowExists( 'populate log_usertext' ) ) { 00666 $this->output( 00667 "Populating log_user_text field, printing progress markers. For large\n" . 00668 "databases, you may want to hit Ctrl-C and do this manually with\n" . 00669 "maintenance/populateLogUsertext.php.\n" ); 00670 00671 $task = $this->maintenance->runChild( 'PopulateLogUsertext' ); 00672 $task->execute(); 00673 $this->output( "done.\n" ); 00674 } 00675 } 00676 00680 protected function doLogSearchPopulation() { 00681 if ( !$this->updateRowExists( 'populate log_search' ) ) { 00682 $this->output( 00683 "Populating log_search table, printing progress markers. For large\n" . 00684 "databases, you may want to hit Ctrl-C and do this manually with\n" . 00685 "maintenance/populateLogSearch.php.\n" ); 00686 00687 $task = $this->maintenance->runChild( 'PopulateLogSearch' ); 00688 $task->execute(); 00689 $this->output( "done.\n" ); 00690 } 00691 } 00692 00696 protected function doUpdateTranscacheField() { 00697 if ( $this->updateRowExists( 'convert transcache field' ) ) { 00698 $this->output( "...transcache tc_time already converted.\n" ); 00699 return; 00700 } 00701 00702 $this->applyPatch( 'patch-tc-timestamp.sql', false, "Converting tc_time from UNIX epoch to MediaWiki timestamp" ); 00703 } 00704 00708 protected function doCollationUpdate() { 00709 global $wgCategoryCollation; 00710 if ( $this->db->selectField( 00711 'categorylinks', 00712 'COUNT(*)', 00713 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ), 00714 __METHOD__ 00715 ) == 0 ) { 00716 $this->output( "...collations up-to-date.\n" ); 00717 return; 00718 } 00719 00720 $this->output( "Updating category collations..." ); 00721 $task = $this->maintenance->runChild( 'UpdateCollation' ); 00722 $task->execute(); 00723 $this->output( "...done.\n" ); 00724 } 00725 00729 protected function doMigrateUserOptions() { 00730 $cl = $this->maintenance->runChild( 'ConvertUserOptions', 'convertUserOptions.php' ); 00731 $cl->execute(); 00732 $this->output( "done.\n" ); 00733 } 00734 00738 protected function rebuildLocalisationCache() { 00742 $cl = $this->maintenance->runChild( 'RebuildLocalisationCache', 'rebuildLocalisationCache.php' ); 00743 $this->output( "Rebuilding localisation cache...\n" ); 00744 $cl->setForce(); 00745 $cl->execute(); 00746 $this->output( "done.\n" ); 00747 } 00748 }