53use UnexpectedValueException;
59require_once __DIR__ .
'/../../maintenance/Maintenance.php';
118 DeleteDefaultMessages::class,
119 PopulateRevisionLength::class,
120 PopulateRevisionSha1::class,
121 PopulateImageSha1::class,
122 PopulateFilearchiveSha1::class,
123 PopulateBacklinkNamespace::class,
124 FixDefaultJsonContentPages::class,
125 CleanupEmptyCategories::class,
126 AddRFCandPMIDInterwiki::class,
127 PopulatePPSortKey::class,
128 PopulateIpChanges::class,
170 private function loadExtensionSchemaUpdates() {
171 $hookContainer = $this->loadExtensions();
172 (
new HookRunner( $hookContainer ) )->onLoadExtensionSchemaUpdates( $this );
181 private function loadExtensions() {
182 if ( $this->autoExtensionHookContainer ) {
186 if ( defined(
'MW_EXTENSIONS_LOADED' ) ) {
187 throw new LogicException( __METHOD__ .
188 ' apparently called from installer but no hook container was injected' );
190 if ( !defined(
'MEDIAWIKI_INSTALL' ) ) {
197 $queue = $registry->getQueue();
199 $registry->clearQueue();
202 $extInfo = $registry->readFromQueue( $queue );
206 $legacySchemaHooks = $extInfo[
'globals'][
'wgHooks'][
'LoadExtensionSchemaUpdates'] ?? [];
207 if ( $vars && isset( $vars[
'wgHooks'][
'LoadExtensionSchemaUpdates'] ) ) {
208 $legacySchemaHooks = array_merge( $legacySchemaHooks, $vars[
'wgHooks'][
'LoadExtensionSchemaUpdates'] );
213 if ( $vars && isset( $vars[
'wgAutoloadClasses'] ) ) {
218 if ( !isset( $extInfo[
'autoloaderPaths'] )
219 || !isset( $extInfo[
'autoloaderClasses'] )
220 || !isset( $extInfo[
'autoloaderNS'] )
224 throw new LogicException(
'Missing autoloader keys from extracted extension info' );
230 return new HookContainer(
231 new StaticHookRegistry(
232 [
'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
233 $extInfo[
'attributes'][
'Hooks'] ?? [],
234 $extInfo[
'attributes'][
'DeprecatedHooks'] ?? []
253 $class =
'\\MediaWiki\\Installer\\' . ucfirst( $type ) .
'Updater';
258 throw new UnexpectedValueException( __METHOD__ .
' called for unsupported DB type' );
269 $this->autoExtensionHookContainer = $hookContainer;
288 if ( $this->maintenance->isQuiet() ) {
292 $str = htmlspecialchars( $str );
311 $this->extensionUpdates[] = $update;
324 $this->extensionUpdatesWithVirtualDomains[] = $update;
338 $this->extensionUpdates[] = [
'addTable', $tableName, $sqlPath, true ];
352 $this->extensionUpdates[] = [
'addIndex', $tableName, $indexName, $sqlPath, true ];
366 $this->extensionUpdates[] = [
'addField', $tableName, $columnName, $sqlPath, true ];
380 $this->extensionUpdates[] = [
'dropField', $tableName, $columnName, $sqlPath, true ];
394 $this->extensionUpdates[] = [
'dropIndex', $tableName, $indexName, $sqlPath, true ];
407 $this->extensionUpdates[] = [
'dropTable', $tableName, $sqlPath, true ];
424 $sqlPath, $skipBothIndexExistWarning =
false
426 $this->extensionUpdates[] = [
431 $skipBothIndexExistWarning,
448 $this->extensionUpdates[] = [
'modifyField', $tableName, $fieldName, $sqlPath, true ];
461 $this->extensionUpdates[] = [
'modifyTable', $tableName, $sqlPath, true ];
471 return ( $this->db->tableExists( $tableName, __METHOD__ ) );
482 return ( $this->db->fieldExists( $tableName, $fieldName, __METHOD__ ) );
495 $this->postDatabaseUpdateMaintenance[] = $class;
521 private function writeSchemaUpdateFile() {
523 $this->updatesSkipped = [];
525 foreach (
$updates as [ $func, $args, $origParams ] ) {
529 $this->updatesSkipped[] = $origParams;
552 public function doUpdates( array $what = [
'core',
'extensions',
'stats' ] ) {
555 $what = array_fill_keys( $what,
true );
556 $this->skipSchema = isset( $what[
'noschema'] ) || $this->fileHandle !==
null;
557 if ( isset( $what[
'core'] ) ) {
561 if ( isset( $what[
'extensions'] ) ) {
562 $this->loadExtensionSchemaUpdates();
564 $this->runUpdates( $this->extensionUpdatesWithVirtualDomains,
true,
true );
567 if ( isset( $what[
'stats'] ) ) {
571 if ( $this->fileHandle ) {
572 $this->skipSchema =
false;
573 $this->writeSchemaUpdateFile();
584 private function runUpdates( array
$updates, $passSelf, $hasVirtualDomain =
false ) {
591 $virtualDomain =
null;
592 if ( $hasVirtualDomain ===
true ) {
593 $virtualDomain = array_shift(
$params );
595 $virtualDb = $lbFactory->getPrimaryDatabase( $virtualDomain );
596 '@phan-var IMaintainableDatabase $virtualDb';
597 $this->maintenance->setDB( $virtualDb );
598 $this->db = $virtualDb;
600 $func = array_shift(
$params );
601 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
602 $func = [ $this, $func ];
603 } elseif ( $passSelf ) {
604 array_unshift(
$params, $this );
607 if ( $hasVirtualDomain ===
true && $oldDb ) {
609 $this->maintenance->setDB( $oldDb );
613 if ( $ret !==
false ) {
614 $updatesDone[] = $origParams;
615 $lbFactory->waitForReplication( [
'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
617 if ( $hasVirtualDomain ===
true ) {
619 $func = array_shift(
$params );
624 $this->updatesSkipped = array_merge( $this->updatesSkipped,
$updatesSkipped );
625 $this->updates = array_merge( $this->updates, $updatesDone );
635 $row = $this->db->newSelectQueryBuilder()
637 ->from(
'updatelog' )
638 ->where( [
'ul_key' => $key ] )
639 ->caller( __METHOD__ )->fetchRow();
656 $values = [
'ul_key' => $key ];
658 $values[
'ul_value'] = $val;
660 $this->db->newInsertQueryBuilder()
661 ->insertInto(
'updatelog' )
664 ->caller( __METHOD__ )->execute();
686 $this->
output(
"...skipping update to shared table $name.\n" );
710 $this->db->sourceFile(
733 $line = rtrim( $line ) .
";\n";
734 if ( fwrite( $this->fileHandle, $line ) ===
false ) {
735 throw new RuntimeException(
"trouble writing file" );
753 $msg ??=
"Applying $path patch";
754 if ( $this->skipSchema ) {
755 $this->
output(
"...skipping schema change ($msg).\n" );
760 $this->
output(
"{$msg}..." );
762 if ( !$isFullPath ) {
765 if ( $this->fileHandle !==
null ) {
768 $this->db->sourceFile(
$path );
770 $this->
output(
"done.\n" );
787 if ( file_exists(
"$baseDir/maintenance/$dbType/archives/$patch" ) ) {
788 return "$baseDir/maintenance/$dbType/archives/$patch";
791 return "$baseDir/maintenance/archives/$patch";
805 protected function addTable( $name, $patch, $fullpath =
false ) {
806 if ( !$this->
doTable( $name ) ) {
810 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
811 $this->
output(
"...$name table already exists.\n" );
815 return $this->
applyPatch( $patch, $fullpath,
"Creating $name table" );
830 protected function addField( $table, $field, $patch, $fullpath =
false ) {
831 if ( !$this->
doTable( $table ) ) {
835 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
836 $this->
output(
"...$table table does not exist, skipping new field patch.\n" );
837 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
838 $this->
output(
"...have $field field in $table table.\n" );
840 return $this->
applyPatch( $patch, $fullpath,
"Adding $field field to table $table" );
858 protected function addIndex( $table, $index, $patch, $fullpath =
false ) {
859 if ( !$this->
doTable( $table ) ) {
863 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
864 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
865 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
866 $this->
output(
"...index $index already set on $table table.\n" );
868 return $this->
applyPatch( $patch, $fullpath,
"Adding index $index to table $table" );
886 protected function dropField( $table, $field, $patch, $fullpath =
false ) {
887 if ( !$this->
doTable( $table ) ) {
891 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
892 return $this->
applyPatch( $patch, $fullpath,
"Table $table contains $field field. Dropping" );
895 $this->
output(
"...$table table does not contain $field field.\n" );
911 protected function dropIndex( $table, $index, $patch, $fullpath =
false ) {
912 if ( !$this->
doTable( $table ) ) {
916 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
917 return $this->
applyPatch( $patch, $fullpath,
"Dropping $index index from table $table" );
920 $this->
output(
"...$index key doesn't exist.\n" );
939 $skipBothIndexExistWarning, $patch, $fullpath =
false
941 if ( !$this->
doTable( $table ) ) {
946 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
947 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
953 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
954 $this->
output(
"...index $newIndex already set on $table table.\n" );
955 if ( !$skipBothIndexExistWarning &&
956 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
958 $this->
output(
"...WARNING: $oldIndex still exists, despite it has " .
959 "been renamed into $newIndex (which also exists).\n" .
960 " $oldIndex should be manually removed if not needed anymore.\n" );
967 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
968 $this->
output(
"...skipping: index $oldIndex doesn't exist.\n" );
977 "Renaming index $oldIndex into $newIndex to table $table"
995 protected function dropTable( $table, $patch =
false, $fullpath =
false ) {
996 if ( !$this->
doTable( $table ) ) {
1000 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1001 $msg =
"Dropping table $table";
1003 if ( $patch ===
false ) {
1004 $this->
output(
"$msg ..." );
1005 $this->db->dropTable( $table, __METHOD__ );
1006 $this->
output(
"done.\n" );
1008 return $this->
applyPatch( $patch, $fullpath, $msg );
1011 $this->
output(
"...$table doesn't exist.\n" );
1031 protected function modifyField( $table, $field, $patch, $fullpath =
false ) {
1032 if ( !$this->
doTable( $table ) ) {
1036 $updateKey =
"$table-$field-$patch";
1037 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1038 $this->
output(
"...$table table does not exist, skipping modify field patch.\n" );
1039 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1040 $this->
output(
"...$field field does not exist in $table table, " .
1041 "skipping modify field patch.\n" );
1043 $this->
output(
"...$field in table $table already modified by patch $patch.\n" );
1045 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying $field field of table $table" );
1069 if ( !$this->
doTable( $table ) ) {
1073 $updateKey =
"$table-$patch";
1074 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1075 $this->
output(
"...$table table does not exist, skipping modify table patch.\n" );
1077 $this->
output(
"...table $table already modified by patch $patch.\n" );
1079 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying table $table" );
1109 $this->
output(
"Running $class...\n" );
1110 $task = $this->maintenance->runChild( $class );
1111 $ok = $task->execute();
1113 throw new RuntimeException(
"Execution of $class did not complete successfully." );
1115 $this->
output(
"done.\n" );
1125 $zonePath = $repo->getZonePath(
'temp' );
1126 if ( $repo->getBackend()->directoryExists( [
'dir' => $zonePath ] ) ) {
1128 $status = $repo->getBackend()->secure( [
1133 if ( $status->isOK() ) {
1134 $this->
output(
"Set the local repo temp zone container to be private.\n" );
1136 $this->
output(
"Failed to set the local repo temp zone container to be private.\n" );
1148 $this->
output(
"Purging caches..." );
1151 $this->db->newDeleteQueryBuilder()
1152 ->deleteFrom(
'objectcache' )
1153 ->where( ISQLPlatform::ALL_ROWS )
1154 ->caller( __METHOD__ )
1165 $services->getMainWANObjectCache()
1169 $this->db->newDeleteQueryBuilder()
1170 ->deleteFrom(
'module_deps' )
1171 ->where( ISQLPlatform::ALL_ROWS )
1172 ->caller( __METHOD__ )
1174 $this->
output(
"done.\n" );
1181 $this->
output(
"...site_stats is populated..." );
1182 $row = $this->db->newSelectQueryBuilder()
1184 ->from(
'site_stats' )
1185 ->where( [
'ss_row_id' => 1 ] )
1186 ->caller( __METHOD__ )->fetchRow();
1187 if ( $row ===
false ) {
1188 $this->
output(
"data is missing! rebuilding...\n" );
1189 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1190 $this->
output(
"missing ss_total_pages, rebuilding...\n" );
1192 $this->
output(
"done.\n" );
1199 # Common updater functions
1206 if ( $this->
updateRowExists(
'UpdateCollation::' . $wgCategoryCollation ) ) {
1207 $this->
output(
"...collations up-to-date.\n" );
1210 $this->
output(
"Updating category collations...\n" );
1211 $task = $this->maintenance->runChild( UpdateCollation::class );
1212 $ok = $task->execute();
1213 if ( $ok !==
false ) {
1214 $this->
output(
"...done.\n" );
1223 $this->
output(
"Converting djvu metadata..." );
1224 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1225 '@phan-var RefreshImageMetadata $task';
1226 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1228 'mediatype' =>
'OFFICE',
1229 'mime' =>
'image/*',
1233 $ok = $task->execute();
1234 if ( $ok !==
false ) {
1235 $this->
output(
"...done.\n" );
1247 $cl = $this->maintenance->runChild(
1248 RebuildLocalisationCache::class,
'rebuildLocalisationCache.php'
1250 '@phan-var RebuildLocalisationCache $cl';
1251 $this->
output(
"Rebuilding localisation cache...\n" );
1254 $this->
output(
"done.\n" );
1258 if ( $this->
updateRowExists( MigrateLinksTable::class .
'templatelinks' ) ) {
1259 $this->
output(
"...templatelinks table has already been migrated.\n" );
1265 $task = $this->maintenance->runChild(
1266 MigrateLinksTable::class,
'migrateLinksTable.php'
1268 '@phan-var MigrateLinksTable $task';
1269 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1271 'table' =>
'templatelinks'
1273 $this->
output(
"Running migrateLinksTable.php on templatelinks...\n" );
1275 $this->
output(
"done.\n" );
1279 if ( $this->
updateRowExists( MigrateLinksTable::class .
'pagelinks' ) ) {
1280 $this->
output(
"...pagelinks table has already been migrated.\n" );
1286 $task = $this->maintenance->runChild(
1287 MigrateLinksTable::class,
'migrateLinksTable.php'
1289 '@phan-var MigrateLinksTable $task';
1290 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1292 'table' =>
'pagelinks'
1294 $this->
output(
"Running migrateLinksTable.php on pagelinks...\n" );
1296 $this->
output(
"done.\n" );
1315 if ( $table === $this ) {
1318 $func = array_shift(
$params );
1321 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1325 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1326 $func = [ $this, $func ];
1327 } elseif ( $passSelf ) {
1328 array_unshift(
$params, $this );
1352 if ( $table === $this ) {
1356 $func = array_shift(
$params );
1359 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1360 !$this->db->fieldExists( $table, $field, __METHOD__ )
1365 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1366 $func = [ $this, $func ];
1367 } elseif ( $passSelf ) {
1368 array_unshift(
$params, $this );
1378class_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_actor refers to an IP acto...
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.