MediaWiki
REL1_19
|
00001 <?php 00009 require_once( dirname(__FILE__) . '/../../maintenance/Maintenance.php' ); 00010 00018 abstract class DatabaseUpdater { 00019 00025 protected $updates = array(); 00026 00031 protected $extensionUpdates = array(); 00032 00038 protected $db; 00039 00040 protected $shared = false; 00041 00042 protected $postDatabaseUpdateMaintenance = array( 00043 'DeleteDefaultMessages', 00044 'PopulateRevisionLength', 00045 'PopulateRevisionSha1', 00046 'PopulateImageSha1', 00047 'FixExtLinksProtocolRelative', 00048 ); 00049 00057 protected function __construct( DatabaseBase &$db, $shared, Maintenance $maintenance = null ) { 00058 $this->db = $db; 00059 $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files 00060 $this->shared = $shared; 00061 if ( $maintenance ) { 00062 $this->maintenance = $maintenance; 00063 } else { 00064 $this->maintenance = new FakeMaintenance; 00065 } 00066 $this->maintenance->setDB( $db ); 00067 $this->initOldGlobals(); 00068 $this->loadExtensions(); 00069 wfRunHooks( 'LoadExtensionSchemaUpdates', array( $this ) ); 00070 } 00071 00076 private function initOldGlobals() { 00077 global $wgExtNewTables, $wgExtNewFields, $wgExtPGNewFields, 00078 $wgExtPGAlteredFields, $wgExtNewIndexes, $wgExtModifiedFields; 00079 00080 # For extensions only, should be populated via hooks 00081 # $wgDBtype should be checked to specifiy the proper file 00082 $wgExtNewTables = array(); // table, dir 00083 $wgExtNewFields = array(); // table, column, dir 00084 $wgExtPGNewFields = array(); // table, column, column attributes; for PostgreSQL 00085 $wgExtPGAlteredFields = array(); // table, column, new type, conversion method; for PostgreSQL 00086 $wgExtNewIndexes = array(); // table, index, dir 00087 $wgExtModifiedFields = array(); // table, index, dir 00088 } 00089 00093 private function loadExtensions() { 00094 if ( !defined( 'MEDIAWIKI_INSTALL' ) ) { 00095 return; // already loaded 00096 } 00097 $vars = Installer::getExistingLocalSettings(); 00098 if ( !$vars ) { 00099 return; // no LocalSettings found 00100 } 00101 if ( !isset( $vars['wgHooks'] ) || !isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) { 00102 return; 00103 } 00104 global $wgHooks, $wgAutoloadClasses; 00105 $wgHooks['LoadExtensionSchemaUpdates'] = $vars['wgHooks']['LoadExtensionSchemaUpdates']; 00106 $wgAutoloadClasses = $wgAutoloadClasses + $vars['wgAutoloadClasses']; 00107 } 00108 00116 public static function newForDB( &$db, $shared = false, $maintenance = null ) { 00117 $type = $db->getType(); 00118 if( in_array( $type, Installer::getDBTypes() ) ) { 00119 $class = ucfirst( $type ) . 'Updater'; 00120 return new $class( $db, $shared, $maintenance ); 00121 } else { 00122 throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' ); 00123 } 00124 } 00125 00131 public function getDB() { 00132 return $this->db; 00133 } 00134 00140 public function output( $str ) { 00141 if ( $this->maintenance->isQuiet() ) { 00142 return; 00143 } 00144 global $wgCommandLineMode; 00145 if( !$wgCommandLineMode ) { 00146 $str = htmlspecialchars( $str ); 00147 } 00148 echo $str; 00149 flush(); 00150 } 00151 00165 public function addExtensionUpdate( Array $update ) { 00166 $this->extensionUpdates[] = $update; 00167 } 00168 00178 public function addExtensionTable( $tableName, $sqlPath ) { 00179 $this->extensionUpdates[] = array( 'addTable', $tableName, $sqlPath, true ); 00180 } 00181 00189 public function addExtensionIndex( $tableName, $indexName, $sqlPath ) { 00190 $this->extensionUpdates[] = array( 'addIndex', $tableName, $indexName, $sqlPath, true ); 00191 } 00192 00201 public function addExtensionField( $tableName, $columnName, $sqlPath ) { 00202 $this->extensionUpdates[] = array( 'addField', $tableName, $columnName, $sqlPath, true ); 00203 } 00204 00212 public function addPostDatabaseUpdateMaintenance( $class ) { 00213 $this->postDatabaseUpdateMaintenance[] = $class; 00214 } 00215 00221 protected function getExtensionUpdates() { 00222 return $this->extensionUpdates; 00223 } 00224 00230 public function getPostDatabaseUpdateMaintenance() { 00231 return $this->postDatabaseUpdateMaintenance; 00232 } 00233 00239 public function doUpdates( $what = array( 'core', 'extensions', 'purge', 'stats' ) ) { 00240 global $wgLocalisationCacheConf, $wgVersion; 00241 00242 $what = array_flip( $what ); 00243 if ( isset( $what['core'] ) ) { 00244 $this->runUpdates( $this->getCoreUpdateList(), false ); 00245 } 00246 if ( isset( $what['extensions'] ) ) { 00247 $this->runUpdates( $this->getOldGlobalUpdates(), false ); 00248 $this->runUpdates( $this->getExtensionUpdates(), true ); 00249 } 00250 00251 $this->setAppliedUpdates( $wgVersion, $this->updates ); 00252 00253 if ( isset( $what['stats'] ) ) { 00254 $this->checkStats(); 00255 } 00256 00257 if ( isset( $what['purge'] ) ) { 00258 $this->purgeCache(); 00259 00260 if ( $wgLocalisationCacheConf['manualRecache'] ) { 00261 $this->rebuildLocalisationCache(); 00262 } 00263 } 00264 } 00265 00273 private function runUpdates( array $updates, $passSelf ) { 00274 foreach ( $updates as $params ) { 00275 $func = array_shift( $params ); 00276 if( !is_array( $func ) && method_exists( $this, $func ) ) { 00277 $func = array( $this, $func ); 00278 } elseif ( $passSelf ) { 00279 array_unshift( $params, $this ); 00280 } 00281 call_user_func_array( $func, $params ); 00282 flush(); 00283 } 00284 $this->updates = array_merge( $this->updates, $updates ); 00285 } 00286 00291 protected function setAppliedUpdates( $version, $updates = array() ) { 00292 $this->db->clearFlag( DBO_DDLMODE ); 00293 if( !$this->canUseNewUpdatelog() ) { 00294 return; 00295 } 00296 $key = "updatelist-$version-" . time(); 00297 $this->db->insert( 'updatelog', 00298 array( 'ul_key' => $key, 'ul_value' => serialize( $updates ) ), 00299 __METHOD__ ); 00300 $this->db->setFlag( DBO_DDLMODE ); 00301 } 00302 00311 public function updateRowExists( $key ) { 00312 $row = $this->db->selectRow( 00313 'updatelog', 00314 '1', 00315 array( 'ul_key' => $key ), 00316 __METHOD__ 00317 ); 00318 return (bool)$row; 00319 } 00320 00328 public function insertUpdateRow( $key, $val = null ) { 00329 $this->db->clearFlag( DBO_DDLMODE ); 00330 $values = array( 'ul_key' => $key ); 00331 if( $val && $this->canUseNewUpdatelog() ) { 00332 $values['ul_value'] = $val; 00333 } 00334 $this->db->insert( 'updatelog', $values, __METHOD__, 'IGNORE' ); 00335 $this->db->setFlag( DBO_DDLMODE ); 00336 } 00337 00346 protected function canUseNewUpdatelog() { 00347 return $this->db->tableExists( 'updatelog', __METHOD__ ) && 00348 $this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ ); 00349 } 00350 00359 protected function getOldGlobalUpdates() { 00360 global $wgExtNewFields, $wgExtNewTables, $wgExtModifiedFields, 00361 $wgExtNewIndexes, $wgSharedDB, $wgSharedTables; 00362 00363 $doUser = $this->shared ? 00364 $wgSharedDB && in_array( 'user', $wgSharedTables ) : 00365 !$wgSharedDB || !in_array( 'user', $wgSharedTables ); 00366 00367 $updates = array(); 00368 00369 foreach ( $wgExtNewTables as $tableRecord ) { 00370 $updates[] = array( 00371 'addTable', $tableRecord[0], $tableRecord[1], true 00372 ); 00373 } 00374 00375 foreach ( $wgExtNewFields as $fieldRecord ) { 00376 if ( $fieldRecord[0] != 'user' || $doUser ) { 00377 $updates[] = array( 00378 'addField', $fieldRecord[0], $fieldRecord[1], 00379 $fieldRecord[2], true 00380 ); 00381 } 00382 } 00383 00384 foreach ( $wgExtNewIndexes as $fieldRecord ) { 00385 $updates[] = array( 00386 'addIndex', $fieldRecord[0], $fieldRecord[1], 00387 $fieldRecord[2], true 00388 ); 00389 } 00390 00391 foreach ( $wgExtModifiedFields as $fieldRecord ) { 00392 $updates[] = array( 00393 'modifyField', $fieldRecord[0], $fieldRecord[1], 00394 $fieldRecord[2], true 00395 ); 00396 } 00397 00398 return $updates; 00399 } 00400 00409 protected abstract function getCoreUpdateList(); 00410 00416 protected function applyPatch( $path, $isFullPath = false ) { 00417 if ( $isFullPath ) { 00418 $this->db->sourceFile( $path ); 00419 } else { 00420 $this->db->sourceFile( $this->db->patchPath( $path ) ); 00421 } 00422 } 00423 00430 protected function addTable( $name, $patch, $fullpath = false ) { 00431 if ( $this->db->tableExists( $name, __METHOD__ ) ) { 00432 $this->output( "...$name table already exists.\n" ); 00433 } else { 00434 $this->output( "Creating $name table..." ); 00435 $this->applyPatch( $patch, $fullpath ); 00436 $this->output( "done.\n" ); 00437 } 00438 } 00439 00447 protected function addField( $table, $field, $patch, $fullpath = false ) { 00448 if ( !$this->db->tableExists( $table, __METHOD__ ) ) { 00449 $this->output( "...$table table does not exist, skipping new field patch.\n" ); 00450 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) { 00451 $this->output( "...have $field field in $table table.\n" ); 00452 } else { 00453 $this->output( "Adding $field field to table $table..." ); 00454 $this->applyPatch( $patch, $fullpath ); 00455 $this->output( "done.\n" ); 00456 } 00457 } 00458 00466 protected function addIndex( $table, $index, $patch, $fullpath = false ) { 00467 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) { 00468 $this->output( "...index $index already set on $table table.\n" ); 00469 } else { 00470 $this->output( "Adding index $index to table $table... " ); 00471 $this->applyPatch( $patch, $fullpath ); 00472 $this->output( "done.\n" ); 00473 } 00474 } 00475 00484 protected function dropField( $table, $field, $patch, $fullpath = false ) { 00485 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) { 00486 $this->output( "Table $table contains $field field. Dropping... " ); 00487 $this->applyPatch( $patch, $fullpath ); 00488 $this->output( "done.\n" ); 00489 } else { 00490 $this->output( "...$table table does not contain $field field.\n" ); 00491 } 00492 } 00493 00502 protected function dropIndex( $table, $index, $patch, $fullpath = false ) { 00503 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) { 00504 $this->output( "Dropping $index index from table $table... " ); 00505 $this->applyPatch( $patch, $fullpath ); 00506 $this->output( "done.\n" ); 00507 } else { 00508 $this->output( "...$index key doesn't exist.\n" ); 00509 } 00510 } 00511 00517 protected function dropTable( $table, $patch, $fullpath = false ) { 00518 if ( $this->db->tableExists( $table, __METHOD__ ) ) { 00519 $this->output( "Dropping table $table... " ); 00520 $this->applyPatch( $patch, $fullpath ); 00521 $this->output( "done.\n" ); 00522 } else { 00523 $this->output( "...$table doesn't exist.\n" ); 00524 } 00525 } 00526 00535 public function modifyField( $table, $field, $patch, $fullpath = false ) { 00536 $updateKey = "$table-$field-$patch"; 00537 if ( !$this->db->tableExists( $table, __METHOD__ ) ) { 00538 $this->output( "...$table table does not exist, skipping modify field patch.\n" ); 00539 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) { 00540 $this->output( "...$field field does not exist in $table table, skipping modify field patch.\n" ); 00541 } elseif( $this->updateRowExists( $updateKey ) ) { 00542 $this->output( "...$field in table $table already modified by patch $patch.\n" ); 00543 } else { 00544 $this->output( "Modifying $field field of table $table..." ); 00545 $this->applyPatch( $patch, $fullpath ); 00546 $this->insertUpdateRow( $updateKey ); 00547 $this->output( "done.\n" ); 00548 } 00549 } 00550 00554 protected function purgeCache() { 00555 # We can't guarantee that the user will be able to use TRUNCATE, 00556 # but we know that DELETE is available to us 00557 $this->output( "Purging caches..." ); 00558 $this->db->delete( 'objectcache', '*', __METHOD__ ); 00559 $this->output( "done.\n" ); 00560 } 00561 00565 protected function checkStats() { 00566 $this->output( "...site_stats is populated..." ); 00567 $row = $this->db->selectRow( 'site_stats', '*', array( 'ss_row_id' => 1 ), __METHOD__ ); 00568 if ( $row === false ) { 00569 $this->output( "data is missing! rebuilding...\n" ); 00570 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) { 00571 $this->output( "missing ss_total_pages, rebuilding...\n" ); 00572 } else { 00573 $this->output( "done.\n" ); 00574 return; 00575 } 00576 SiteStatsInit::doAllAndCommit( $this->db ); 00577 } 00578 00579 # Common updater functions 00580 00584 protected function doActiveUsersInit() { 00585 $activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', false, __METHOD__ ); 00586 if ( $activeUsers == -1 ) { 00587 $activeUsers = $this->db->selectField( 'recentchanges', 00588 'COUNT( DISTINCT rc_user_text )', 00589 array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ), __METHOD__ 00590 ); 00591 $this->db->update( 'site_stats', 00592 array( 'ss_active_users' => intval( $activeUsers ) ), 00593 array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 ) 00594 ); 00595 } 00596 $this->output( "...ss_active_users user count set...\n" ); 00597 } 00598 00602 protected function doLogUsertextPopulation() { 00603 if ( !$this->updateRowExists( 'populate log_usertext' ) ) { 00604 $this->output( 00605 "Populating log_user_text field, printing progress markers. For large\n" . 00606 "databases, you may want to hit Ctrl-C and do this manually with\n" . 00607 "maintenance/populateLogUsertext.php.\n" ); 00608 00609 $task = $this->maintenance->runChild( 'PopulateLogUsertext' ); 00610 $task->execute(); 00611 $this->output( "done.\n" ); 00612 } 00613 } 00614 00618 protected function doLogSearchPopulation() { 00619 if ( !$this->updateRowExists( 'populate log_search' ) ) { 00620 $this->output( 00621 "Populating log_search table, printing progress markers. For large\n" . 00622 "databases, you may want to hit Ctrl-C and do this manually with\n" . 00623 "maintenance/populateLogSearch.php.\n" ); 00624 00625 $task = $this->maintenance->runChild( 'PopulateLogSearch' ); 00626 $task->execute(); 00627 $this->output( "done.\n" ); 00628 } 00629 } 00630 00634 protected function doUpdateTranscacheField() { 00635 if ( $this->updateRowExists( 'convert transcache field' ) ) { 00636 $this->output( "...transcache tc_time already converted.\n" ); 00637 return; 00638 } 00639 00640 $this->output( "Converting tc_time from UNIX epoch to MediaWiki timestamp... " ); 00641 $this->applyPatch( 'patch-tc-timestamp.sql' ); 00642 $this->output( "done.\n" ); 00643 } 00644 00648 protected function doCollationUpdate() { 00649 global $wgCategoryCollation; 00650 if ( $this->db->selectField( 00651 'categorylinks', 00652 'COUNT(*)', 00653 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ), 00654 __METHOD__ 00655 ) == 0 ) { 00656 $this->output( "...collations up-to-date.\n" ); 00657 return; 00658 } 00659 00660 $this->output( "Updating category collations..." ); 00661 $task = $this->maintenance->runChild( 'UpdateCollation' ); 00662 $task->execute(); 00663 $this->output( "...done.\n" ); 00664 } 00665 00669 protected function doMigrateUserOptions() { 00670 $cl = $this->maintenance->runChild( 'ConvertUserOptions', 'convertUserOptions.php' ); 00671 $cl->execute(); 00672 $this->output( "done.\n" ); 00673 } 00674 00678 protected function rebuildLocalisationCache() { 00682 $cl = $this->maintenance->runChild( 'RebuildLocalisationCache', 'rebuildLocalisationCache.php' ); 00683 $this->output( "Rebuilding localisation cache...\n" ); 00684 $cl->setForce(); 00685 $cl->execute(); 00686 $this->output( "done.\n" ); 00687 } 00688 }