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 $virtualDb = $lbFactory->getPrimaryDatabase( $virtualDomain );
595 '@phan-var IMaintainableDatabase $virtualDb';
596 $this->maintenance->setDB( $virtualDb );
597 $this->db = $virtualDb;
599 $func = array_shift(
$params );
600 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
601 $func = [ $this, $func ];
602 } elseif ( $passSelf ) {
603 array_unshift(
$params, $this );
606 if ( $hasVirtualDomain ===
true && $oldDb ) {
608 $this->maintenance->setDB( $oldDb );
612 if ( $ret !==
false ) {
613 $updatesDone[] = $origParams;
614 $lbFactory->waitForReplication( [
'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
616 if ( $hasVirtualDomain ===
true ) {
618 $func = array_shift(
$params );
623 $this->updatesSkipped = array_merge( $this->updatesSkipped,
$updatesSkipped );
624 $this->updates = array_merge( $this->updates, $updatesDone );
634 $row = $this->db->newSelectQueryBuilder()
636 ->from(
'updatelog' )
637 ->where( [
'ul_key' => $key ] )
638 ->caller( __METHOD__ )->fetchRow();
655 $values = [
'ul_key' => $key ];
657 $values[
'ul_value'] = $val;
659 $this->db->newInsertQueryBuilder()
660 ->insertInto(
'updatelog' )
663 ->caller( __METHOD__ )->execute();
685 $this->
output(
"...skipping update to shared table $name.\n" );
709 $this->db->sourceFile(
732 $line = rtrim( $line ) .
";\n";
733 if ( fwrite( $this->fileHandle, $line ) ===
false ) {
734 throw new RuntimeException(
"trouble writing file" );
752 $msg ??=
"Applying $path patch";
753 if ( $this->skipSchema ) {
754 $this->
output(
"...skipping schema change ($msg).\n" );
759 $this->
output(
"{$msg}..." );
761 if ( !$isFullPath ) {
764 if ( $this->fileHandle !==
null ) {
767 $this->db->sourceFile(
$path );
769 $this->
output(
"done.\n" );
786 if ( file_exists(
"$baseDir/maintenance/$dbType/archives/$patch" ) ) {
787 return "$baseDir/maintenance/$dbType/archives/$patch";
790 return "$baseDir/maintenance/archives/$patch";
804 protected function addTable( $name, $patch, $fullpath =
false ) {
805 if ( !$this->
doTable( $name ) ) {
809 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
810 $this->
output(
"...$name table already exists.\n" );
814 return $this->
applyPatch( $patch, $fullpath,
"Creating $name table" );
829 protected function addField( $table, $field, $patch, $fullpath =
false ) {
830 if ( !$this->
doTable( $table ) ) {
834 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
835 $this->
output(
"...$table table does not exist, skipping new field patch.\n" );
836 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
837 $this->
output(
"...have $field field in $table table.\n" );
839 return $this->
applyPatch( $patch, $fullpath,
"Adding $field field to table $table" );
857 protected function addIndex( $table, $index, $patch, $fullpath =
false ) {
858 if ( !$this->
doTable( $table ) ) {
862 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
863 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
864 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
865 $this->
output(
"...index $index already set on $table table.\n" );
867 return $this->
applyPatch( $patch, $fullpath,
"Adding index $index to table $table" );
885 protected function dropField( $table, $field, $patch, $fullpath =
false ) {
886 if ( !$this->
doTable( $table ) ) {
890 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
891 return $this->
applyPatch( $patch, $fullpath,
"Table $table contains $field field. Dropping" );
894 $this->
output(
"...$table table does not contain $field field.\n" );
910 protected function dropIndex( $table, $index, $patch, $fullpath =
false ) {
911 if ( !$this->
doTable( $table ) ) {
915 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
916 return $this->
applyPatch( $patch, $fullpath,
"Dropping $index index from table $table" );
919 $this->
output(
"...$index key doesn't exist.\n" );
938 $skipBothIndexExistWarning, $patch, $fullpath =
false
940 if ( !$this->
doTable( $table ) ) {
945 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
946 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
952 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
953 $this->
output(
"...index $newIndex already set on $table table.\n" );
954 if ( !$skipBothIndexExistWarning &&
955 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
957 $this->
output(
"...WARNING: $oldIndex still exists, despite it has " .
958 "been renamed into $newIndex (which also exists).\n" .
959 " $oldIndex should be manually removed if not needed anymore.\n" );
966 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
967 $this->
output(
"...skipping: index $oldIndex doesn't exist.\n" );
976 "Renaming index $oldIndex into $newIndex to table $table"
994 protected function dropTable( $table, $patch =
false, $fullpath =
false ) {
995 if ( !$this->
doTable( $table ) ) {
999 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1000 $msg =
"Dropping table $table";
1002 if ( $patch ===
false ) {
1003 $this->
output(
"$msg ..." );
1004 $this->db->dropTable( $table, __METHOD__ );
1005 $this->
output(
"done.\n" );
1007 return $this->
applyPatch( $patch, $fullpath, $msg );
1010 $this->
output(
"...$table doesn't exist.\n" );
1030 protected function modifyField( $table, $field, $patch, $fullpath =
false ) {
1031 if ( !$this->
doTable( $table ) ) {
1035 $updateKey =
"$table-$field-$patch";
1036 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1037 $this->
output(
"...$table table does not exist, skipping modify field patch.\n" );
1038 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1039 $this->
output(
"...$field field does not exist in $table table, " .
1040 "skipping modify field patch.\n" );
1042 $this->
output(
"...$field in table $table already modified by patch $patch.\n" );
1044 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying $field field of table $table" );
1068 if ( !$this->
doTable( $table ) ) {
1072 $updateKey =
"$table-$patch";
1073 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1074 $this->
output(
"...$table table does not exist, skipping modify table patch.\n" );
1076 $this->
output(
"...table $table already modified by patch $patch.\n" );
1078 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying table $table" );
1108 $this->
output(
"Running $script...\n" );
1109 $task = $this->maintenance->runChild( $class );
1110 $ok = $task->execute();
1112 throw new RuntimeException(
"Execution of $script did not complete successfully." );
1114 $this->
output(
"done.\n" );
1124 $zonePath = $repo->getZonePath(
'temp' );
1125 if ( $repo->getBackend()->directoryExists( [
'dir' => $zonePath ] ) ) {
1127 $status = $repo->getBackend()->secure( [
1132 if ( $status->isOK() ) {
1133 $this->
output(
"Set the local repo temp zone container to be private.\n" );
1135 $this->
output(
"Failed to set the local repo temp zone container to be private.\n" );
1147 $this->
output(
"Purging caches..." );
1150 $this->db->delete(
'objectcache',
'*', __METHOD__ );
1160 $services->getMainWANObjectCache()
1164 $this->db->delete(
'module_deps',
'*', __METHOD__ );
1165 $this->
output(
"done.\n" );
1172 $this->
output(
"...site_stats is populated..." );
1173 $row = $this->db->newSelectQueryBuilder()
1175 ->from(
'site_stats' )
1176 ->where( [
'ss_row_id' => 1 ] )
1177 ->caller( __METHOD__ )->fetchRow();
1178 if ( $row ===
false ) {
1179 $this->
output(
"data is missing! rebuilding...\n" );
1180 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1181 $this->
output(
"missing ss_total_pages, rebuilding...\n" );
1183 $this->
output(
"done.\n" );
1190 # Common updater functions
1197 if ( $this->
updateRowExists(
'UpdateCollation::' . $wgCategoryCollation ) ) {
1198 $this->
output(
"...collations up-to-date.\n" );
1201 $this->
output(
"Updating category collations...\n" );
1202 $task = $this->maintenance->runChild( UpdateCollation::class );
1203 $ok = $task->execute();
1204 if ( $ok !==
false ) {
1205 $this->
output(
"...done.\n" );
1214 $this->
output(
"Converting djvu metadata..." );
1215 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1216 '@phan-var RefreshImageMetadata $task';
1217 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1219 'mediatype' =>
'OFFICE',
1220 'mime' =>
'image/*',
1224 $ok = $task->execute();
1225 if ( $ok !==
false ) {
1226 $this->
output(
"...done.\n" );
1238 $cl = $this->maintenance->runChild(
1239 RebuildLocalisationCache::class,
'rebuildLocalisationCache.php'
1241 '@phan-var RebuildLocalisationCache $cl';
1242 $this->
output(
"Rebuilding localisation cache...\n" );
1245 $this->
output(
"done.\n" );
1249 if ( $this->
updateRowExists( MigrateLinksTable::class .
'templatelinks' ) ) {
1250 $this->
output(
"...templatelinks table has already been migrated.\n" );
1256 $task = $this->maintenance->runChild(
1257 MigrateLinksTable::class,
'migrateLinksTable.php'
1259 '@phan-var MigrateLinksTable $task';
1260 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1262 'table' =>
'templatelinks'
1264 $this->
output(
"Running migrateLinksTable.php on templatelinks...\n" );
1266 $this->
output(
"done.\n" );
1285 if ( $table === $this ) {
1288 $func = array_shift(
$params );
1291 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1295 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1296 $func = [ $this, $func ];
1297 } elseif ( $passSelf ) {
1298 array_unshift(
$params, $this );
1322 if ( $table === $this ) {
1326 $func = array_shift(
$params );
1329 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1330 !$this->db->fieldExists( $table, $field, __METHOD__ )
1335 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1336 $func = [ $this, $func ];
1337 } elseif ( $passSelf ) {
1338 array_unshift(
$params, $this );
1348class_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.