53use UnexpectedValueException;
58require_once __DIR__ .
'/../../maintenance/Maintenance.php';
117 DeleteDefaultMessages::class,
118 PopulateRevisionLength::class,
119 PopulateRevisionSha1::class,
120 PopulateImageSha1::class,
121 PopulateFilearchiveSha1::class,
122 PopulateBacklinkNamespace::class,
123 FixDefaultJsonContentPages::class,
124 CleanupEmptyCategories::class,
125 AddRFCandPMIDInterwiki::class,
126 PopulatePPSortKey::class,
127 PopulateIpChanges::class,
169 private function loadExtensionSchemaUpdates() {
170 $hookContainer = $this->loadExtensions();
171 (
new HookRunner( $hookContainer ) )->onLoadExtensionSchemaUpdates( $this );
180 private function loadExtensions() {
181 if ( $this->autoExtensionHookContainer ) {
185 if ( defined(
'MW_EXTENSIONS_LOADED' ) ) {
186 throw new LogicException( __METHOD__ .
187 ' apparently called from installer but no hook container was injected' );
189 if ( !defined(
'MEDIAWIKI_INSTALL' ) ) {
196 $queue = $registry->getQueue();
198 $registry->clearQueue();
201 $extInfo = $registry->readFromQueue( $queue );
205 $legacySchemaHooks = $extInfo[
'globals'][
'wgHooks'][
'LoadExtensionSchemaUpdates'] ?? [];
206 if ( $vars && isset( $vars[
'wgHooks'][
'LoadExtensionSchemaUpdates'] ) ) {
207 $legacySchemaHooks = array_merge( $legacySchemaHooks, $vars[
'wgHooks'][
'LoadExtensionSchemaUpdates'] );
212 if ( $vars && isset( $vars[
'wgAutoloadClasses'] ) ) {
217 if ( !isset( $extInfo[
'autoloaderPaths'] )
218 || !isset( $extInfo[
'autoloaderClasses'] )
219 || !isset( $extInfo[
'autoloaderNS'] )
223 throw new LogicException(
'Missing autoloader keys from extracted extension info' );
229 return new HookContainer(
230 new StaticHookRegistry(
231 [
'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
232 $extInfo[
'attributes'][
'Hooks'] ?? [],
233 $extInfo[
'attributes'][
'DeprecatedHooks'] ?? []
252 $class =
'\\MediaWiki\\Installer\\' . ucfirst( $type ) .
'Updater';
257 throw new UnexpectedValueException( __METHOD__ .
' called for unsupported DB type' );
268 $this->autoExtensionHookContainer = $hookContainer;
287 if ( $this->maintenance->isQuiet() ) {
291 $str = htmlspecialchars( $str );
310 $this->extensionUpdates[] = $update;
323 $this->extensionUpdatesWithVirtualDomains[] = $update;
337 $this->extensionUpdates[] = [
'addTable', $tableName, $sqlPath, true ];
351 $this->extensionUpdates[] = [
'addIndex', $tableName, $indexName, $sqlPath, true ];
365 $this->extensionUpdates[] = [
'addField', $tableName, $columnName, $sqlPath, true ];
379 $this->extensionUpdates[] = [
'dropField', $tableName, $columnName, $sqlPath, true ];
393 $this->extensionUpdates[] = [
'dropIndex', $tableName, $indexName, $sqlPath, true ];
406 $this->extensionUpdates[] = [
'dropTable', $tableName, $sqlPath, true ];
423 $sqlPath, $skipBothIndexExistWarning =
false
425 $this->extensionUpdates[] = [
430 $skipBothIndexExistWarning,
447 $this->extensionUpdates[] = [
'modifyField', $tableName, $fieldName, $sqlPath, true ];
460 $this->extensionUpdates[] = [
'modifyTable', $tableName, $sqlPath, true ];
470 return ( $this->db->tableExists( $tableName, __METHOD__ ) );
481 return ( $this->db->fieldExists( $tableName, $fieldName, __METHOD__ ) );
494 $this->postDatabaseUpdateMaintenance[] = $class;
520 private function writeSchemaUpdateFile() {
522 $this->updatesSkipped = [];
524 foreach (
$updates as [ $func, $args, $origParams ] ) {
528 $this->updatesSkipped[] = $origParams;
551 public function doUpdates( array $what = [
'core',
'extensions',
'stats' ] ) {
554 $what = array_fill_keys( $what,
true );
555 $this->skipSchema = isset( $what[
'noschema'] ) || $this->fileHandle !==
null;
556 if ( isset( $what[
'core'] ) ) {
560 if ( isset( $what[
'extensions'] ) ) {
561 $this->loadExtensionSchemaUpdates();
563 $this->runUpdates( $this->extensionUpdatesWithVirtualDomains,
true,
true );
566 if ( isset( $what[
'stats'] ) ) {
570 if ( $this->fileHandle ) {
571 $this->skipSchema =
false;
572 $this->writeSchemaUpdateFile();
583 private function runUpdates( array
$updates, $passSelf, $hasVirtualDomain =
false ) {
590 $virtualDomain =
null;
591 if ( $hasVirtualDomain ===
true ) {
592 $virtualDomain = array_shift(
$params );
594 $this->db = $lbFactory->getPrimaryDatabase( $virtualDomain );
595 '@phan-var IMaintainableDatabase $this->db';
597 $func = array_shift(
$params );
598 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
599 $func = [ $this, $func ];
600 } elseif ( $passSelf ) {
601 array_unshift(
$params, $this );
604 if ( $hasVirtualDomain ===
true && $oldDb ) {
609 if ( $ret !==
false ) {
610 $updatesDone[] = $origParams;
611 $lbFactory->waitForReplication( [
'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
613 if ( $hasVirtualDomain ===
true ) {
615 $func = array_shift(
$params );
620 $this->updatesSkipped = array_merge( $this->updatesSkipped,
$updatesSkipped );
621 $this->updates = array_merge( $this->updates, $updatesDone );
631 $row = $this->db->newSelectQueryBuilder()
633 ->from(
'updatelog' )
634 ->where( [
'ul_key' => $key ] )
635 ->caller( __METHOD__ )->fetchRow();
652 $values = [
'ul_key' => $key ];
654 $values[
'ul_value'] = $val;
656 $this->db->newInsertQueryBuilder()
657 ->insertInto(
'updatelog' )
660 ->caller( __METHOD__ )->execute();
682 $this->
output(
"...skipping update to shared table $name.\n" );
706 $this->db->sourceFile(
729 $line = rtrim( $line ) .
";\n";
730 if ( fwrite( $this->fileHandle, $line ) ===
false ) {
731 throw new RuntimeException(
"trouble writing file" );
749 $msg ??=
"Applying $path patch";
750 if ( $this->skipSchema ) {
751 $this->
output(
"...skipping schema change ($msg).\n" );
756 $this->
output(
"{$msg}..." );
758 if ( !$isFullPath ) {
761 if ( $this->fileHandle !==
null ) {
764 $this->db->sourceFile(
$path );
766 $this->
output(
"done.\n" );
783 if ( file_exists(
"$baseDir/maintenance/$dbType/archives/$patch" ) ) {
784 return "$baseDir/maintenance/$dbType/archives/$patch";
787 return "$baseDir/maintenance/archives/$patch";
801 protected function addTable( $name, $patch, $fullpath =
false ) {
802 if ( !$this->
doTable( $name ) ) {
806 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
807 $this->
output(
"...$name table already exists.\n" );
811 return $this->
applyPatch( $patch, $fullpath,
"Creating $name table" );
826 protected function addField( $table, $field, $patch, $fullpath =
false ) {
827 if ( !$this->
doTable( $table ) ) {
831 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
832 $this->
output(
"...$table table does not exist, skipping new field patch.\n" );
833 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
834 $this->
output(
"...have $field field in $table table.\n" );
836 return $this->
applyPatch( $patch, $fullpath,
"Adding $field field to table $table" );
854 protected function addIndex( $table, $index, $patch, $fullpath =
false ) {
855 if ( !$this->
doTable( $table ) ) {
859 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
860 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
861 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
862 $this->
output(
"...index $index already set on $table table.\n" );
864 return $this->
applyPatch( $patch, $fullpath,
"Adding index $index to table $table" );
882 protected function dropField( $table, $field, $patch, $fullpath =
false ) {
883 if ( !$this->
doTable( $table ) ) {
887 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
888 return $this->
applyPatch( $patch, $fullpath,
"Table $table contains $field field. Dropping" );
891 $this->
output(
"...$table table does not contain $field field.\n" );
907 protected function dropIndex( $table, $index, $patch, $fullpath =
false ) {
908 if ( !$this->
doTable( $table ) ) {
912 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
913 return $this->
applyPatch( $patch, $fullpath,
"Dropping $index index from table $table" );
916 $this->
output(
"...$index key doesn't exist.\n" );
935 $skipBothIndexExistWarning, $patch, $fullpath =
false
937 if ( !$this->
doTable( $table ) ) {
942 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
943 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
949 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
950 $this->
output(
"...index $newIndex already set on $table table.\n" );
951 if ( !$skipBothIndexExistWarning &&
952 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
954 $this->
output(
"...WARNING: $oldIndex still exists, despite it has " .
955 "been renamed into $newIndex (which also exists).\n" .
956 " $oldIndex should be manually removed if not needed anymore.\n" );
963 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
964 $this->
output(
"...skipping: index $oldIndex doesn't exist.\n" );
973 "Renaming index $oldIndex into $newIndex to table $table"
991 protected function dropTable( $table, $patch =
false, $fullpath =
false ) {
992 if ( !$this->
doTable( $table ) ) {
996 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
997 $msg =
"Dropping table $table";
999 if ( $patch ===
false ) {
1000 $this->
output(
"$msg ..." );
1001 $this->db->dropTable( $table, __METHOD__ );
1002 $this->
output(
"done.\n" );
1004 return $this->
applyPatch( $patch, $fullpath, $msg );
1007 $this->
output(
"...$table doesn't exist.\n" );
1027 protected function modifyField( $table, $field, $patch, $fullpath =
false ) {
1028 if ( !$this->
doTable( $table ) ) {
1032 $updateKey =
"$table-$field-$patch";
1033 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1034 $this->
output(
"...$table table does not exist, skipping modify field patch.\n" );
1035 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1036 $this->
output(
"...$field field does not exist in $table table, " .
1037 "skipping modify field patch.\n" );
1039 $this->
output(
"...$field in table $table already modified by patch $patch.\n" );
1041 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying $field field of table $table" );
1065 if ( !$this->
doTable( $table ) ) {
1069 $updateKey =
"$table-$patch";
1070 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1071 $this->
output(
"...$table table does not exist, skipping modify table patch.\n" );
1073 $this->
output(
"...table $table already modified by patch $patch.\n" );
1075 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying table $table" );
1105 $this->
output(
"Running $script...\n" );
1106 $task = $this->maintenance->runChild( $class );
1107 $ok = $task->execute();
1109 throw new RuntimeException(
"Execution of $script did not complete successfully." );
1111 $this->
output(
"done.\n" );
1121 $zonePath = $repo->getZonePath(
'temp' );
1122 if ( $repo->getBackend()->directoryExists( [
'dir' => $zonePath ] ) ) {
1124 $status = $repo->getBackend()->secure( [
1129 if ( $status->isOK() ) {
1130 $this->
output(
"Set the local repo temp zone container to be private.\n" );
1132 $this->
output(
"Failed to set the local repo temp zone container to be private.\n" );
1144 $this->
output(
"Purging caches..." );
1147 $this->db->delete(
'objectcache',
'*', __METHOD__ );
1157 $services->getMainWANObjectCache()
1161 $this->db->delete(
'module_deps',
'*', __METHOD__ );
1162 $this->
output(
"done.\n" );
1169 $this->
output(
"...site_stats is populated..." );
1170 $row = $this->db->newSelectQueryBuilder()
1172 ->from(
'site_stats' )
1173 ->where( [
'ss_row_id' => 1 ] )
1174 ->caller( __METHOD__ )->fetchRow();
1175 if ( $row ===
false ) {
1176 $this->
output(
"data is missing! rebuilding...\n" );
1177 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1178 $this->
output(
"missing ss_total_pages, rebuilding...\n" );
1180 $this->
output(
"done.\n" );
1187 # Common updater functions
1194 if ( $this->
updateRowExists(
'UpdateCollation::' . $wgCategoryCollation ) ) {
1195 $this->
output(
"...collations up-to-date.\n" );
1198 $this->
output(
"Updating category collations...\n" );
1199 $task = $this->maintenance->runChild( UpdateCollation::class );
1200 $ok = $task->execute();
1201 if ( $ok !==
false ) {
1202 $this->
output(
"...done.\n" );
1211 $this->
output(
"Converting djvu metadata..." );
1212 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1213 '@phan-var RefreshImageMetadata $task';
1214 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1216 'mediatype' =>
'OFFICE',
1217 'mime' =>
'image/*',
1221 $ok = $task->execute();
1222 if ( $ok !==
false ) {
1223 $this->
output(
"...done.\n" );
1235 $cl = $this->maintenance->runChild(
1236 RebuildLocalisationCache::class,
'rebuildLocalisationCache.php'
1238 '@phan-var RebuildLocalisationCache $cl';
1239 $this->
output(
"Rebuilding localisation cache...\n" );
1242 $this->
output(
"done.\n" );
1246 if ( $this->
updateRowExists( MigrateLinksTable::class .
'templatelinks' ) ) {
1247 $this->
output(
"...templatelinks table has already been migrated.\n" );
1253 $task = $this->maintenance->runChild(
1254 MigrateLinksTable::class,
'migrateLinksTable.php'
1256 '@phan-var MigrateLinksTable $task';
1257 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1259 'table' =>
'templatelinks'
1261 $this->
output(
"Running migrateLinksTable.php on templatelinks...\n" );
1263 $this->
output(
"done.\n" );
1282 if ( $table === $this ) {
1285 $func = array_shift(
$params );
1288 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1292 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1293 $func = [ $this, $func ];
1294 } elseif ( $passSelf ) {
1295 array_unshift(
$params, $this );
1319 if ( $table === $this ) {
1323 $func = array_shift(
$params );
1326 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1327 !$this->db->fieldExists( $table, $field, __METHOD__ )
1332 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1333 $func = [ $this, $func ];
1334 } elseif ( $passSelf ) {
1335 array_unshift(
$params, $this );
1347class_alias( DatabaseUpdater::class,
'DatabaseUpdater' );
array $params
The job parameters.
Run automatically with update.php.
Locations of core classes Extension classes are specified with $wgAutoloadClasses.
static registerClasses(array $files)
Register a file to load the given class from.
static loadFiles(array $files)
Batch version of loadFile()
static registerNamespaces(array $dirs)
Register a directory to load the classes of a given namespace from, per PSR4.
Maintenance script to clean up empty categories in the category table.
Maintenance script that deletes all pages in the MediaWiki namespace which were last edited by "Media...
Load JSON files, and uses a Processor to extract information.
Fake maintenance wrapper, mostly used for the web installer/updater.
Usage: fixDefaultJsonContentPages.php.
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().
A class containing constants representing the names of configuration variables.
const BaseDirectory
Name constant for the BaseDirectory setting, for use with Config::get()
Maintenance script that populates normalization column in links tables.
Maintenance script to populate *_from_namespace fields.
Maintenance script to populate the fa_sha1 field.
Maintenance script to populate the img_sha1 field.
Maintenance script that will find all rows in the revision table where rev_user = 0 (user is an IP),...
Usage: populatePPSortKey.php.
Maintenance script that populates the rev_len and ar_len fields when they are NULL.
Maintenance script that fills the rev_sha1 and ar_sha1 columns of revision and archive tables for rev...
Maintenance script to rebuild the localisation cache.
Maintenance script that will find all rows in the categorylinks table whose collation is out-of-date.
$wgSharedTables
Config variable stub for the SharedTables setting, for use by phpdoc and IDEs.
$wgCategoryCollation
Config variable stub for the CategoryCollation setting, for use by phpdoc and IDEs.
$wgSharedDB
Config variable stub for the SharedDB setting, for use by phpdoc and IDEs.
$wgLocalisationCacheConf
Config variable stub for the LocalisationCacheConf setting, for use by phpdoc and IDEs.
Advanced database interface for IDatabase handles that include maintenance methods.