31require_once __DIR__ .
'/../../maintenance/Maintenance.php';
85 DeleteDefaultMessages::class,
86 PopulateRevisionLength::class,
87 PopulateRevisionSha1::class,
88 PopulateImageSha1::class,
89 FixExtLinksProtocolRelative::class,
90 PopulateFilearchiveSha1::class,
91 PopulateBacklinkNamespace::class,
92 FixDefaultJsonContentPages::class,
93 CleanupEmptyCategories::class,
94 AddRFCandPMIDInterwiki::class,
95 PopulatePPSortKey::class,
96 PopulateIpChanges::class,
97 RefreshExternallinksIndex::class,
134 $this->maintenance->
setDB( $db );
142 (
new HookRunner( $hookContainer ) )->onLoadExtensionSchemaUpdates( $this );
152 if ( $this->autoExtensionHookContainer ) {
154 return $this->autoExtensionHookContainer;
156 if ( defined(
'MW_EXTENSIONS_LOADED' ) ) {
157 throw new Exception( __METHOD__ .
158 ' apparently called from installer but no hook container was injected' );
160 if ( !defined(
'MEDIAWIKI_INSTALL' ) ) {
162 return MediaWikiServices::getInstance()->getHookContainer();
166 $registry = ExtensionRegistry::getInstance();
167 $queue = $registry->getQueue();
169 $registry->clearQueue();
172 $data = $registry->readFromQueue(
$queue );
176 $legacySchemaHooks = $data[
'globals'][
'wgHooks'][
'LoadExtensionSchemaUpdates'] ?? [];
177 if ( $vars && isset( $vars[
'wgHooks'][
'LoadExtensionSchemaUpdates'] ) ) {
178 $legacySchemaHooks = array_merge( $legacySchemaHooks, $vars[
'wgHooks'][
'LoadExtensionSchemaUpdates'] );
183 if ( $vars && isset( $vars[
'wgAutoloadClasses'] ) ) {
189 [
'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
190 $data[
'attributes'][
'Hooks'] ?? [],
191 $data[
'attributes'][
'DeprecatedHooks'] ?? []
193 MediaWikiServices::getInstance()->getObjectFactory()
212 $class = ucfirst(
$type ) .
'Updater';
216 throw new MWException( __METHOD__ .
' called for unsupported $wgDBtype' );
228 $this->autoExtensionHookContainer = $hookContainer;
247 if ( $this->maintenance->isQuiet() ) {
252 $str = htmlspecialchars( $str );
271 $this->extensionUpdates[] = $update;
285 $this->extensionUpdates[] = [
'addTable', $tableName, $sqlPath,
true ];
299 $this->extensionUpdates[] = [
'addIndex', $tableName, $indexName, $sqlPath,
true ];
313 $this->extensionUpdates[] = [
'addField', $tableName, $columnName, $sqlPath,
true ];
327 $this->extensionUpdates[] = [
'dropField', $tableName, $columnName, $sqlPath,
true ];
341 $this->extensionUpdates[] = [
'dropIndex', $tableName, $indexName, $sqlPath,
true ];
354 $this->extensionUpdates[] = [
'dropTable', $tableName, $sqlPath,
true ];
371 $sqlPath, $skipBothIndexExistWarning =
false
373 $this->extensionUpdates[] = [
378 $skipBothIndexExistWarning,
395 $this->extensionUpdates[] = [
'modifyField', $tableName, $fieldName, $sqlPath,
true ];
408 $this->extensionUpdates[] = [
'modifyTable', $tableName, $sqlPath,
true ];
418 return ( $this->db->tableExists( $tableName, __METHOD__ ) );
431 $this->postDatabaseUpdateMaintenance[] = $class;
440 return $this->extensionUpdates;
449 return $this->postDatabaseUpdateMaintenance;
460 $this->updatesSkipped = [];
466 $this->updatesSkipped[] = $origParams;
490 public function doUpdates( array $what = [
'core',
'extensions',
'stats' ] ) {
493 $what = array_fill_keys( $what,
true );
494 $this->skipSchema = isset( $what[
'noschema'] ) || $this->fileHandle !==
null;
495 if ( isset( $what[
'core'] ) ) {
499 if ( isset( $what[
'extensions'] ) ) {
504 if ( isset( $what[
'stats'] ) ) {
508 if ( $this->fileHandle ) {
509 $this->skipSchema =
false;
521 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
525 foreach ( $updates as $params ) {
526 $origParams = $params;
527 $func = array_shift( $params );
528 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
529 $func = [ $this, $func ];
530 } elseif ( $passSelf ) {
531 array_unshift( $params, $this );
533 $ret = $func( ...$params );
535 if ( $ret !==
false ) {
536 $updatesDone[] = $origParams;
537 $lbFactory->waitForReplication( [
'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
542 $this->updatesSkipped = array_merge( $this->updatesSkipped,
$updatesSkipped );
543 $this->updates = array_merge( $this->updates, $updatesDone );
554 $row = $this->db->selectRow(
558 [
'ul_key' => $key ],
580 $values = [
'ul_key' => $key ];
582 $values[
'ul_value'] = $val;
584 $this->db->insert(
'updatelog', $values, __METHOD__, [
'IGNORE' ] );
597 return $this->db->tableExists(
'updatelog', __METHOD__ ) &&
598 $this->db->fieldExists(
'updatelog',
'ul_value', __METHOD__ );
619 $this->
output(
"...skipping update to shared table $name.\n" );
643 $this->db->sourceFile(
668 if ( fwrite( $this->fileHandle,
$line ) ===
false ) {
687 if ( $msg ===
null ) {
688 $msg =
"Applying $path patch";
690 if ( $this->skipSchema ) {
691 $this->
output(
"...skipping schema change ($msg).\n" );
696 $this->
output(
"$msg ..." );
698 if ( !$isFullPath ) {
701 if ( $this->fileHandle !==
null ) {
704 $this->db->sourceFile(
$path );
706 $this->
output(
"done.\n" );
723 if ( file_exists(
"$IP/maintenance/$dbType/archives/$patch" ) ) {
724 return "$IP/maintenance/$dbType/archives/$patch";
726 return "$IP/maintenance/archives/$patch";
741 protected function addTable( $name, $patch, $fullpath =
false ) {
742 if ( !$this->
doTable( $name ) ) {
746 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
747 $this->
output(
"...$name table already exists.\n" );
749 return $this->
applyPatch( $patch, $fullpath,
"Creating $name table" );
767 protected function addField( $table, $field, $patch, $fullpath =
false ) {
768 if ( !$this->
doTable( $table ) ) {
772 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
773 $this->
output(
"...$table table does not exist, skipping new field patch.\n" );
774 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
775 $this->
output(
"...have $field field in $table table.\n" );
777 return $this->
applyPatch( $patch, $fullpath,
"Adding $field field to table $table" );
795 protected function addIndex( $table, $index, $patch, $fullpath =
false ) {
796 if ( !$this->
doTable( $table ) ) {
800 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
801 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
802 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
803 $this->
output(
"...index $index already set on $table table.\n" );
805 return $this->
applyPatch( $patch, $fullpath,
"Adding index $index to table $table" );
822 if ( !$this->
doTable( $table ) ) {
826 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
827 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
831 $newIndex = $indexes[0];
832 foreach ( $indexes as $index ) {
833 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
835 "...skipping index $newIndex because index $index already set on $table table.\n"
841 return $this->
applyPatch( $patch, $fullpath,
"Adding index $index to table $table" );
856 protected function dropField( $table, $field, $patch, $fullpath =
false ) {
857 if ( !$this->
doTable( $table ) ) {
861 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
862 return $this->
applyPatch( $patch, $fullpath,
"Table $table contains $field field. Dropping" );
864 $this->
output(
"...$table table does not contain $field field.\n" );
882 protected function dropIndex( $table, $index, $patch, $fullpath =
false ) {
883 if ( !$this->
doTable( $table ) ) {
887 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
888 return $this->
applyPatch( $patch, $fullpath,
"Dropping $index index from table $table" );
890 $this->
output(
"...$index key doesn't exist.\n" );
913 $skipBothIndexExistWarning, $patch, $fullpath =
false
915 if ( !$this->
doTable( $table ) ) {
920 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
921 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
927 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
928 $this->
output(
"...index $newIndex already set on $table table.\n" );
929 if ( !$skipBothIndexExistWarning &&
930 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
932 $this->
output(
"...WARNING: $oldIndex still exists, despite it has " .
933 "been renamed into $newIndex (which also exists).\n" .
934 " $oldIndex should be manually removed if not needed anymore.\n" );
941 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
942 $this->
output(
"...skipping: index $oldIndex doesn't exist.\n" );
951 "Renaming index $oldIndex into $newIndex to table $table"
969 protected function dropTable( $table, $patch =
false, $fullpath =
false ) {
970 if ( !$this->
doTable( $table ) ) {
974 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
975 $msg =
"Dropping table $table";
977 if ( $patch ===
false ) {
978 $this->
output(
"$msg ..." );
979 $this->db->dropTable( $table, __METHOD__ );
980 $this->
output(
"done.\n" );
982 return $this->
applyPatch( $patch, $fullpath, $msg );
985 $this->
output(
"...$table doesn't exist.\n" );
1005 protected function modifyField( $table, $field, $patch, $fullpath =
false ) {
1006 if ( !$this->
doTable( $table ) ) {
1010 $updateKey =
"$table-$field-$patch";
1011 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1012 $this->
output(
"...$table table does not exist, skipping modify field patch.\n" );
1013 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1014 $this->
output(
"...$field field does not exist in $table table, " .
1015 "skipping modify field patch.\n" );
1017 $this->
output(
"...$field in table $table already modified by patch $patch.\n" );
1019 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying $field field of table $table" );
1043 if ( !$this->
doTable( $table ) ) {
1047 $updateKey =
"$table-$patch";
1048 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1049 $this->
output(
"...$table table does not exist, skipping modify table patch.\n" );
1051 $this->
output(
"...table $table already modified by patch $patch.\n" );
1053 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying table $table" );
1083 $this->
output(
"Running $script...\n" );
1084 $task = $this->maintenance->runChild( $class );
1085 $ok = $task->execute();
1087 throw new RuntimeException(
"Execution of $script did not complete successfully." );
1089 $this->
output(
"done.\n" );
1098 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1099 $zonePath = $repo->getZonePath(
'temp' );
1100 if ( $repo->getBackend()->directoryExists( [
'dir' => $zonePath ] ) ) {
1102 $status = $repo->getBackend()->secure( [
1107 if ( $status->isOK() ) {
1108 $this->
output(
"Set the local repo temp zone container to be private.\n" );
1110 $this->
output(
"Failed to set the local repo temp zone container to be private.\n" );
1122 $this->
output(
"Purging caches..." );
1125 $this->db->delete(
'objectcache',
'*', __METHOD__ );
1133 $services = MediaWikiServices::getInstance();
1135 $services->getResourceLoader(),
1137 $services->getMainWANObjectCache()
1139 $blobStore->clear();
1142 $this->db->delete(
'module_deps',
'*', __METHOD__ );
1143 $this->
output(
"done.\n" );
1150 $this->
output(
"...site_stats is populated..." );
1151 $row = $this->db->selectRow(
'site_stats',
'*', [
'ss_row_id' => 1 ], __METHOD__ );
1152 if ( $row ===
false ) {
1153 $this->
output(
"data is missing! rebuilding...\n" );
1154 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1155 $this->
output(
"missing ss_total_pages, rebuilding...\n" );
1157 $this->
output(
"done.\n" );
1164 # Common updater functions
1171 if ( $this->db->selectField(
1178 $this->
output(
"...collations up-to-date.\n" );
1183 $this->
output(
"Updating category collations..." );
1184 $task = $this->maintenance->runChild( UpdateCollation::class );
1186 $this->
output(
"...done.\n" );
1196 $cl = $this->maintenance->runChild(
1197 RebuildLocalisationCache::class,
'rebuildLocalisationCache.php'
1199 '@phan-var RebuildLocalisationCache $cl';
1200 $this->
output(
"Rebuilding localisation cache...\n" );
1203 $this->
output(
"done.\n" );
1213 "Migrating comments to the 'comments' table, printing progress markers. For large\n" .
1214 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1215 "maintenance/migrateComments.php.\n"
1217 $task = $this->maintenance->runChild( MigrateComments::class,
'migrateComments.php' );
1218 $ok = $task->execute();
1219 $this->
output( $ok ?
"done.\n" :
"errors were encountered.\n" );
1228 if ( $this->
tableExists(
'image_comment_temp' ) ) {
1229 $this->
output(
"Merging image_comment_temp into the image table\n" );
1230 $task = $this->maintenance->runChild(
1231 MigrateImageCommentTemp::class,
'migrateImageCommentTemp.php'
1235 $ok = $task->execute();
1236 $this->
output( $ok ?
"done.\n" :
"errors were encountered.\n" );
1238 $this->
dropTable(
'image_comment_temp' );
1250 "Migrating actors to the 'actor' table, printing progress markers. For large\n" .
1251 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1252 "maintenance/migrateActors.php.\n"
1254 $task = $this->maintenance->runChild(
'MigrateActors',
'migrateActors.php' );
1255 $ok = $task->execute();
1256 $this->
output( $ok ?
"done.\n" :
"errors were encountered.\n" );
1265 if ( $this->db->fieldExists(
'archive',
'ar_text', __METHOD__ ) ) {
1266 $this->
output(
"Migrating archive ar_text to modern storage.\n" );
1267 $task = $this->maintenance->runChild( MigrateArchiveText::class,
'migrateArchiveText.php' );
1270 if ( $task->execute() ) {
1271 $this->
applyPatch(
'patch-drop-ar_text.sql',
false,
1272 'Dropping ar_text and ar_flags columns' );
1282 $info = $this->db->fieldInfo(
'archive',
'ar_rev_id' );
1284 throw new MWException(
'Missing ar_rev_id field of archive table. Should not happen.' );
1286 if ( $info->isNullable() ) {
1287 $this->
output(
"Populating ar_rev_id.\n" );
1288 $task = $this->maintenance->runChild(
'PopulateArchiveRevId',
'populateArchiveRevId.php' );
1289 if ( $task->execute() ) {
1290 $this->
applyPatch(
'patch-ar_rev_id-not-null.sql',
false,
1291 'Making ar_rev_id not nullable' );
1301 if ( !$this->
updateRowExists(
'populate externallinks.el_index_60' ) ) {
1303 "Populating el_index_60 field, printing progress markers. For large\n" .
1304 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1305 "maintenance/populateExternallinksIndex60.php.\n"
1307 $task = $this->maintenance->runChild(
'PopulateExternallinksIndex60',
1308 'populateExternallinksIndex60.php' );
1310 $this->
output(
"done.\n" );
1321 "Migrating revision data to the MCR 'slot' and 'content' tables, printing progress markers.\n" .
1322 "For large databases, you may want to hit Ctrl-C and do this manually with\n" .
1323 "maintenance/populateContentTables.php.\n"
1325 $task = $this->maintenance->runChild(
1326 PopulateContentTables::class,
'populateContentTables.php'
1328 $ok = $task->execute();
1329 $this->
output( $ok ?
"done.\n" :
"errors were encountered.\n" );
1360 if ( $table === $this ) {
1363 $func = array_shift( $params );
1366 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1370 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1371 $func = [ $this, $func ];
1372 } elseif ( $passSelf ) {
1373 array_unshift( $params, $this );
1377 return $func( ...$params );
1397 if ( $table === $this ) {
1401 $func = array_shift( $params );
1404 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1405 !$this->db->fieldExists( $table, $field, __METHOD__ )
1410 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1411 $func = [ $this, $func ];
1412 } elseif ( $passSelf ) {
1413 array_unshift( $params, $this );
1417 return $func( ...$params );
$wgAutoloadClasses
Array mapping class names to filenames, for autoloading.
$wgCategoryCollation
Specify how category names should be sorted, when listed on a category page.
$wgSharedDB
Shared database for multiple wikis.
$wgLocalisationCacheConf
Localisation cache configuration.
global $wgCommandLineMode
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Class for handling database updates.
purgeCache()
Purge various database caches.
addExtensionTable( $tableName, $sqlPath)
Convenience wrapper for addExtensionUpdate() when adding a new table (which is the most common usage ...
getDB()
Get a database connection to run updates.
setFileAccess()
Set any .htaccess files or equivilent for storage repos.
migrateImageCommentTemp()
Merge image_comment_temp into the image table.
loadExtensions()
Loads LocalSettings.php, if needed, and initialises everything needed for LoadExtensionSchemaUpdates ...
addIndex( $table, $index, $patch, $fullpath=false)
Add a new index to an existing table.
array $updates
Array of updates to perform on the database.
loadExtensionSchemaUpdates()
Cause extensions to register any updates they need to perform.
modifyExtensionTable( $tableName, $sqlPath)
Modify an existing extension table.
bool $skipSchema
Flag specifying whether or not to skip schema (e.g.
populateArchiveRevId()
Populate ar_rev_id, then make it not nullable.
runMaintenance( $class, $script)
Run a maintenance script.
migrateArchiveText()
Migrate ar_text to modern storage.
addField( $table, $field, $patch, $fullpath=false)
Add a new field to an existing table.
addExtensionIndex( $tableName, $indexName, $sqlPath)
Add an index to an existing extension table.
modifyTable( $table, $patch, $fullpath=false)
Modify an existing table, similar to modifyField.
doUpdates(array $what=[ 'core', 'extensions', 'stats'])
Do all the updates.
ifTableNotExists( $table, $func,... $params)
Only run a function if a table does not exist.
updateRowExists( $key)
Helper function: check if the given key is present in the updatelog table.
const REPLICATION_WAIT_TIMEOUT
runUpdates(array $updates, $passSelf)
Helper function for doUpdates()
getCoreUpdateList()
Get an array of updates to perform on the database.
populateExternallinksIndex60()
Populates the externallinks.el_index_60 field.
output( $str)
Output some text.
getPostDatabaseUpdateMaintenance()
getExtensionUpdates()
Get the list of extension-defined updates.
insertUpdateRow( $key, $val=null)
Helper function: Add a key to the updatelog table.
setAutoExtensionHookContainer(HookContainer $hookContainer)
Set the HookContainer to use for loading extension schema updates.
canUseNewUpdatelog()
Updatelog was changed in 1.17 to have a ul_value column so we can record more information about what ...
patchPath(IDatabase $db, $patch)
Get the full path of a patch file.
copyFile( $filename)
Append an SQL fragment to the open file handle.
string[] $postDatabaseUpdateMaintenance
Scripts to run after database update Should be a subclass of LoggedUpdateMaintenance.
HookContainer null $autoExtensionHookContainer
dropExtensionTable( $tableName, $sqlPath=false)
Drop an extension table.
resource null $fileHandle
File handle for SQL output.
renameIndex( $table, $oldIndex, $newIndex, $skipBothIndexExistWarning, $patch, $fullpath=false)
Rename an index from an existing table.
writeSchemaUpdateFile(array $schemaUpdate=[])
rebuildLocalisationCache()
Rebuilds the localisation cache.
addExtensionField( $tableName, $columnName, $sqlPath)
Add a field to an existing extension table.
IMaintainableDatabase $db
Handle to the database subclass.
ifFieldExists( $table, $field, $func,... $params)
Only run a function if the named field exists.
addTable( $name, $patch, $fullpath=false)
Add a new table to the database.
modifyField( $table, $field, $patch, $fullpath=false)
Modify an existing field.
addIndexIfNoneExist( $table, $indexes, $patch, $fullpath=false)
Add a new index to an existing table if none of the given indexes exist.
dropExtensionField( $tableName, $columnName, $sqlPath)
Drop a field from an extension table.
static newForDB(IMaintainableDatabase $db, $shared=false, Maintenance $maintenance=null)
addPostDatabaseUpdateMaintenance( $class)
Add a maintenance script to be run after the database updates are complete.
dropField( $table, $field, $patch, $fullpath=false)
Drop a field from an existing table.
doCollationUpdate()
Update CategoryLinks collation.
renameExtensionIndex( $tableName, $oldIndexName, $newIndexName, $sqlPath, $skipBothIndexExistWarning=false)
Rename an index on an extension table Intended for use in LoadExtensionSchemaUpdates hook handlers.
migrateActors()
Migrate actors to the new 'actor' table.
addExtensionUpdate(array $update)
Add a new update coming from an extension.
array $updatesSkipped
Array of updates that were skipped.
appendLine( $line)
Append a line to the open filehandle.
dropExtensionIndex( $tableName, $indexName, $sqlPath)
Drop an index from an extension table Intended for use in LoadExtensionSchemaUpdates hook handlers.
array $extensionUpdates
List of extension-provided database updates.
checkStats()
Check the site_stats table is not properly populated.
modifyExtensionField( $tableName, $fieldName, $sqlPath)
Modify an existing field in an extension table.
applyPatch( $path, $isFullPath=false, $msg=null)
Applies a SQL patch.
dropIndex( $table, $index, $patch, $fullpath=false)
Drop an index from an existing table.
populateContentTables()
Populates the MCR content tables.
__construct(IMaintainableDatabase &$db, $shared, Maintenance $maintenance=null)
dropTable( $table, $patch=false, $fullpath=false)
If the specified table exists, drop it, or execute the patch if one is provided.
migrateComments()
Migrate comments to the new 'comment' table.
ifNoActorTable( $func,... $params)
getSchemaVars()
Get appropriate schema variables in the current database connection.
doTable( $name)
Returns whether updates should be executed on the database table $name.
Fake maintenance wrapper, mostly used for the web installer/updater.
static getExistingLocalSettings()
Determine if LocalSettings.php exists.
static getDBTypes()
Get a list of known DB types.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
setDB(IMaintainableDatabase $db)
Sets database object to be returned by getDB().
This class generates message blobs for use by ResourceLoader.
static doAllAndCommit( $database, array $options=[])
Do all updates and commit them.
Advanced database interface for IDatabase handles that include maintenance methods.