44use UnexpectedValueException;
50require_once __DIR__ .
'/../../maintenance/Maintenance.php';
110 DeleteDefaultMessages::class,
111 CleanupEmptyCategories::class,
153 private function loadExtensionSchemaUpdates() {
154 $hookContainer = $this->loadExtensions();
155 (
new HookRunner( $hookContainer ) )->onLoadExtensionSchemaUpdates( $this );
164 private function loadExtensions() {
165 if ( $this->autoExtensionHookContainer ) {
169 if ( defined(
'MW_EXTENSIONS_LOADED' ) ) {
170 throw new LogicException( __METHOD__ .
171 ' apparently called from installer but no hook container was injected' );
173 if ( !defined(
'MEDIAWIKI_INSTALL' ) ) {
179 $registry = ExtensionRegistry::getInstance();
180 $queue = $registry->getQueue();
182 $registry->clearQueue();
185 $extInfo = $registry->readFromQueue( $queue );
189 $legacySchemaHooks = $extInfo[
'globals'][
'wgHooks'][
'LoadExtensionSchemaUpdates'] ?? [];
190 if ( $vars && isset( $vars[
'wgHooks'][
'LoadExtensionSchemaUpdates'] ) ) {
191 $legacySchemaHooks = array_merge( $legacySchemaHooks, $vars[
'wgHooks'][
'LoadExtensionSchemaUpdates'] );
196 if ( $vars && isset( $vars[
'wgAutoloadClasses'] ) ) {
201 if ( !isset( $extInfo[
'autoloaderPaths'] )
202 || !isset( $extInfo[
'autoloaderClasses'] )
203 || !isset( $extInfo[
'autoloaderNS'] )
207 throw new LogicException(
'Missing autoloader keys from extracted extension info' );
213 return new HookContainer(
214 new StaticHookRegistry(
215 [
'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
216 $extInfo[
'attributes'][
'Hooks'] ?? [],
217 $extInfo[
'attributes'][
'DeprecatedHooks'] ?? []
236 $class =
'\\MediaWiki\\Installer\\' . ucfirst( $type ) .
'Updater';
241 throw new UnexpectedValueException( __METHOD__ .
' called for unsupported DB type' );
252 $this->autoExtensionHookContainer = $hookContainer;
271 if ( $this->maintenance->isQuiet() ) {
275 $str = htmlspecialchars( $str );
294 $this->extensionUpdates[] = $update;
307 $this->extensionUpdatesWithVirtualDomains[] = $update;
321 $this->extensionUpdates[] = [
'addTable', $tableName, $sqlPath, true ];
335 $this->extensionUpdates[] = [
'addIndex', $tableName, $indexName, $sqlPath, true ];
349 $this->extensionUpdates[] = [
'addField', $tableName, $columnName, $sqlPath, true ];
363 $this->extensionUpdates[] = [
'dropField', $tableName, $columnName, $sqlPath, true ];
377 $this->extensionUpdates[] = [
'dropIndex', $tableName, $indexName, $sqlPath, true ];
390 $this->extensionUpdates[] = [
'dropTable', $tableName, $sqlPath, true ];
407 $sqlPath, $skipBothIndexExistWarning =
false
409 $this->extensionUpdates[] = [
414 $skipBothIndexExistWarning,
431 $this->extensionUpdates[] = [
'modifyField', $tableName, $fieldName, $sqlPath, true ];
444 $this->extensionUpdates[] = [
'modifyTable', $tableName, $sqlPath, true ];
454 return ( $this->db->tableExists( $tableName, __METHOD__ ) );
465 return ( $this->db->fieldExists( $tableName, $fieldName, __METHOD__ ) );
478 $this->postDatabaseUpdateMaintenance[] = $class;
504 private function writeSchemaUpdateFile() {
506 $this->updatesSkipped = [];
508 foreach (
$updates as [ $func, $args, $origParams ] ) {
512 $this->updatesSkipped[] = $origParams;
535 public function doUpdates( array $what = [
'core',
'extensions',
'stats' ] ) {
538 $what = array_fill_keys( $what,
true );
539 $this->skipSchema = isset( $what[
'noschema'] ) || $this->fileHandle !==
null;
541 if ( isset( $what[
'initial'] ) ) {
542 $this->
output(
'Inserting initial update keys...' );
544 $this->
output(
"done.\n" );
546 if ( isset( $what[
'core'] ) ) {
550 if ( isset( $what[
'extensions'] ) ) {
551 $this->loadExtensionSchemaUpdates();
553 $this->runUpdates( $this->extensionUpdatesWithVirtualDomains,
true,
true );
556 if ( isset( $what[
'stats'] ) ) {
560 if ( $this->fileHandle ) {
561 $this->skipSchema =
false;
562 $this->writeSchemaUpdateFile();
573 private function runUpdates( array
$updates, $passSelf, $hasVirtualDomain =
false ) {
580 $virtualDomain =
null;
581 if ( $hasVirtualDomain ===
true ) {
582 $virtualDomain = array_shift(
$params );
584 $virtualDb = $lbFactory->getPrimaryDatabase( $virtualDomain );
585 '@phan-var IMaintainableDatabase $virtualDb';
586 $this->maintenance->setDB( $virtualDb );
587 $this->db = $virtualDb;
589 $func = array_shift(
$params );
590 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
591 $func = [ $this, $func ];
592 } elseif ( $passSelf ) {
593 array_unshift(
$params, $this );
596 if ( $hasVirtualDomain ===
true && $oldDb ) {
598 $this->maintenance->setDB( $oldDb );
602 if ( $ret !==
false ) {
603 $updatesDone[] = $origParams;
604 $lbFactory->waitForReplication( [
'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
606 if ( $hasVirtualDomain ===
true ) {
608 $func = array_shift(
$params );
613 $this->updatesSkipped = array_merge( $this->updatesSkipped,
$updatesSkipped );
614 $this->updates = array_merge( $this->updates, $updatesDone );
626 if ( !$this->db->tableExists(
'updatelog', __METHOD__ ) ) {
630 $row = $this->db->newSelectQueryBuilder()
632 ->from(
'updatelog' )
633 ->where( [
'ul_key' => $key ] )
634 ->caller( __METHOD__ )->fetchRow();
652 if ( !$this->db->tableExists(
'updatelog', __METHOD__ ) ) {
657 $values = [
'ul_key' => $key ];
659 $values[
'ul_value'] = $val;
661 $this->db->newInsertQueryBuilder()
662 ->insertInto(
'updatelog' )
665 ->caller( __METHOD__ )->execute();
674 $iqb = $this->db->newInsertQueryBuilder()
675 ->insertInto(
'updatelog' )
677 ->caller( __METHOD__ );
679 $iqb->row( [
'ul_key' => $key ] );
703 $this->
output(
"...skipping update to shared table $name.\n" );
739 $this->db->sourceFile(
762 $line = rtrim( $line ) .
";\n";
763 if ( fwrite( $this->fileHandle, $line ) ===
false ) {
764 throw new RuntimeException(
"trouble writing file" );
782 $msg ??=
"Applying $path patch";
783 if ( $this->skipSchema ) {
784 $this->
output(
"...skipping schema change ($msg).\n" );
789 $this->
output(
"{$msg}..." );
791 if ( !$isFullPath ) {
794 if ( $this->fileHandle !==
null ) {
797 $this->db->sourceFile(
$path );
799 $this->
output(
"done.\n" );
816 if ( file_exists(
"$baseDir/maintenance/$dbType/archives/$patch" ) ) {
817 return "$baseDir/maintenance/$dbType/archives/$patch";
820 return "$baseDir/maintenance/archives/$patch";
834 protected function addTable( $name, $patch, $fullpath =
false ) {
835 if ( !$this->
doTable( $name ) ) {
839 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
840 $this->
output(
"...$name table already exists.\n" );
844 return $this->
applyPatch( $patch, $fullpath,
"Creating $name table" );
859 protected function addField( $table, $field, $patch, $fullpath =
false ) {
860 if ( !$this->
doTable( $table ) ) {
864 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
865 $this->
output(
"...$table table does not exist, skipping new field patch.\n" );
866 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
867 $this->
output(
"...have $field field in $table table.\n" );
869 return $this->
applyPatch( $patch, $fullpath,
"Adding $field field to table $table" );
887 protected function addIndex( $table, $index, $patch, $fullpath =
false ) {
888 if ( !$this->
doTable( $table ) ) {
892 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
893 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
894 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
895 $this->
output(
"...index $index already set on $table table.\n" );
897 return $this->
applyPatch( $patch, $fullpath,
"Adding index $index to table $table" );
915 protected function dropField( $table, $field, $patch, $fullpath =
false ) {
916 if ( !$this->
doTable( $table ) ) {
920 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
921 return $this->
applyPatch( $patch, $fullpath,
"Table $table contains $field field. Dropping" );
924 $this->
output(
"...$table table does not contain $field field.\n" );
940 protected function dropIndex( $table, $index, $patch, $fullpath =
false ) {
941 if ( !$this->
doTable( $table ) ) {
945 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
946 return $this->
applyPatch( $patch, $fullpath,
"Dropping $index index from table $table" );
949 $this->
output(
"...$index key doesn't exist.\n" );
968 $skipBothIndexExistWarning, $patch, $fullpath =
false
970 if ( !$this->
doTable( $table ) ) {
975 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
976 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
982 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
983 $this->
output(
"...index $newIndex already set on $table table.\n" );
984 if ( !$skipBothIndexExistWarning &&
985 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
987 $this->
output(
"...WARNING: $oldIndex still exists, despite it has " .
988 "been renamed into $newIndex (which also exists).\n" .
989 " $oldIndex should be manually removed if not needed anymore.\n" );
996 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
997 $this->
output(
"...skipping: index $oldIndex doesn't exist.\n" );
1006 "Renaming index $oldIndex into $newIndex to table $table"
1024 protected function dropTable( $table, $patch =
false, $fullpath =
false ) {
1025 if ( !$this->
doTable( $table ) ) {
1029 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1030 $msg =
"Dropping table $table";
1032 if ( $patch ===
false ) {
1033 $this->
output(
"$msg ..." );
1034 $this->db->dropTable( $table, __METHOD__ );
1035 $this->
output(
"done.\n" );
1037 return $this->
applyPatch( $patch, $fullpath, $msg );
1040 $this->
output(
"...$table doesn't exist.\n" );
1060 protected function modifyField( $table, $field, $patch, $fullpath =
false ) {
1061 if ( !$this->
doTable( $table ) ) {
1065 $updateKey =
"$table-$field-$patch";
1066 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1067 $this->
output(
"...$table table does not exist, skipping modify field patch.\n" );
1068 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1069 $this->
output(
"...$field field does not exist in $table table, " .
1070 "skipping modify field patch.\n" );
1072 $this->
output(
"...$field in table $table already modified by patch $patch.\n" );
1074 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying $field field of table $table" );
1098 if ( !$this->
doTable( $table ) ) {
1102 $updateKey =
"$table-$patch";
1103 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1104 $this->
output(
"...$table table does not exist, skipping modify table patch.\n" );
1106 $this->
output(
"...table $table already modified by patch $patch.\n" );
1108 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying table $table" );
1138 $this->
output(
"Running $class...\n" );
1139 $task = $this->maintenance->runChild( $class );
1140 $ok = $task->execute();
1142 throw new RuntimeException(
"Execution of $class did not complete successfully." );
1144 $this->
output(
"done.\n" );
1154 $zonePath = $repo->getZonePath(
'temp' );
1155 if ( $repo->getBackend()->directoryExists( [
'dir' => $zonePath ] ) ) {
1157 $status = $repo->getBackend()->secure( [
1162 if ( $status->isOK() ) {
1163 $this->
output(
"Set the local repo temp zone container to be private.\n" );
1165 $this->
output(
"Failed to set the local repo temp zone container to be private.\n" );
1177 $this->
output(
"Purging caches..." );
1180 $this->db->newDeleteQueryBuilder()
1181 ->deleteFrom(
'objectcache' )
1182 ->where( ISQLPlatform::ALL_ROWS )
1183 ->caller( __METHOD__ )
1194 $services->getMainWANObjectCache()
1198 $this->db->newDeleteQueryBuilder()
1199 ->deleteFrom(
'module_deps' )
1200 ->where( ISQLPlatform::ALL_ROWS )
1201 ->caller( __METHOD__ )
1203 $this->
output(
"done.\n" );
1210 $this->
output(
"...site_stats is populated..." );
1211 $row = $this->db->newSelectQueryBuilder()
1213 ->from(
'site_stats' )
1214 ->where( [
'ss_row_id' => 1 ] )
1215 ->caller( __METHOD__ )->fetchRow();
1216 if ( $row ===
false ) {
1217 $this->
output(
"data is missing! rebuilding...\n" );
1218 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1219 $this->
output(
"missing ss_total_pages, rebuilding...\n" );
1221 $this->
output(
"done.\n" );
1228 # Common updater functions
1235 if ( $this->
updateRowExists(
'UpdateCollation::' . $wgCategoryCollation ) ) {
1236 $this->
output(
"...collations up-to-date.\n" );
1239 $this->
output(
"Updating category collations...\n" );
1240 $task = $this->maintenance->runChild( UpdateCollation::class );
1241 $ok = $task->execute();
1242 if ( $ok !==
false ) {
1243 $this->
output(
"...done.\n" );
1252 $this->
output(
"Converting djvu metadata..." );
1253 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1254 '@phan-var RefreshImageMetadata $task';
1255 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1257 'mediatype' =>
'OFFICE',
1258 'mime' =>
'image/*',
1262 $ok = $task->execute();
1263 if ( $ok !==
false ) {
1264 $this->
output(
"...done.\n" );
1276 $cl = $this->maintenance->runChild(
1277 RebuildLocalisationCache::class,
'rebuildLocalisationCache.php'
1279 '@phan-var RebuildLocalisationCache $cl';
1280 $this->
output(
"Rebuilding localisation cache...\n" );
1283 $this->
output(
"done.\n" );
1287 if ( $this->
updateRowExists( MigrateLinksTable::class .
'templatelinks' ) ) {
1288 $this->
output(
"...templatelinks table has already been migrated.\n" );
1294 $task = $this->maintenance->runChild(
1295 MigrateLinksTable::class,
'migrateLinksTable.php'
1297 '@phan-var MigrateLinksTable $task';
1298 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1300 'table' =>
'templatelinks'
1302 $this->
output(
"Running migrateLinksTable.php on templatelinks...\n" );
1304 $this->
output(
"done.\n" );
1308 if ( $this->
updateRowExists( MigrateLinksTable::class .
'pagelinks' ) ) {
1309 $this->
output(
"...pagelinks table has already been migrated.\n" );
1315 $task = $this->maintenance->runChild(
1316 MigrateLinksTable::class,
'migrateLinksTable.php'
1318 '@phan-var MigrateLinksTable $task';
1319 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1321 'table' =>
'pagelinks'
1323 $this->
output(
"Running migrateLinksTable.php on pagelinks...\n" );
1325 $this->
output(
"done.\n" );
1344 if ( $table === $this ) {
1347 $func = array_shift(
$params );
1350 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1354 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1355 $func = [ $this, $func ];
1356 } elseif ( $passSelf ) {
1357 array_unshift(
$params, $this );
1381 if ( $table === $this ) {
1385 $func = array_shift(
$params );
1388 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1389 !$this->db->fieldExists( $table, $field, __METHOD__ )
1394 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1395 $func = [ $this, $func ];
1396 } elseif ( $passSelf ) {
1397 array_unshift(
$params, $this );
1407class_alias( DatabaseUpdater::class,
'DatabaseUpdater' );
array $params
The job parameters.
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...
A class containing constants representing the names of configuration variables.
const BaseDirectory
Name constant for the BaseDirectory setting, for use with Config::get()
Fake maintenance wrapper, mostly used for the web installer/updater.
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().
Maintenance script that populates normalization column in links tables.
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.