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 $this->db = $lbFactory->getPrimaryDatabase( $virtualDomain );
596 '@phan-var IMaintainableDatabase $this->db';
598 $func = array_shift(
$params );
599 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
600 $func = [ $this, $func ];
601 } elseif ( $passSelf ) {
602 array_unshift(
$params, $this );
605 if ( $hasVirtualDomain ===
true && $oldDb ) {
610 if ( $ret !==
false ) {
611 $updatesDone[] = $origParams;
612 $lbFactory->waitForReplication( [
'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
614 if ( $hasVirtualDomain ===
true ) {
616 $func = array_shift(
$params );
621 $this->updatesSkipped = array_merge( $this->updatesSkipped,
$updatesSkipped );
622 $this->updates = array_merge( $this->updates, $updatesDone );
632 $row = $this->db->newSelectQueryBuilder()
634 ->from(
'updatelog' )
635 ->where( [
'ul_key' => $key ] )
636 ->caller( __METHOD__ )->fetchRow();
653 $values = [
'ul_key' => $key ];
655 $values[
'ul_value'] = $val;
657 $this->db->newInsertQueryBuilder()
658 ->insertInto(
'updatelog' )
661 ->caller( __METHOD__ )->execute();
683 $this->
output(
"...skipping update to shared table $name.\n" );
707 $this->db->sourceFile(
730 $line = rtrim( $line ) .
";\n";
731 if ( fwrite( $this->fileHandle, $line ) ===
false ) {
732 throw new RuntimeException(
"trouble writing file" );
750 $msg ??=
"Applying $path patch";
751 if ( $this->skipSchema ) {
752 $this->
output(
"...skipping schema change ($msg).\n" );
757 $this->
output(
"{$msg}..." );
759 if ( !$isFullPath ) {
762 if ( $this->fileHandle !==
null ) {
765 $this->db->sourceFile(
$path );
767 $this->
output(
"done.\n" );
784 if ( file_exists(
"$baseDir/maintenance/$dbType/archives/$patch" ) ) {
785 return "$baseDir/maintenance/$dbType/archives/$patch";
788 return "$baseDir/maintenance/archives/$patch";
802 protected function addTable( $name, $patch, $fullpath =
false ) {
803 if ( !$this->
doTable( $name ) ) {
807 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
808 $this->
output(
"...$name table already exists.\n" );
812 return $this->
applyPatch( $patch, $fullpath,
"Creating $name table" );
827 protected function addField( $table, $field, $patch, $fullpath =
false ) {
828 if ( !$this->
doTable( $table ) ) {
832 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
833 $this->
output(
"...$table table does not exist, skipping new field patch.\n" );
834 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
835 $this->
output(
"...have $field field in $table table.\n" );
837 return $this->
applyPatch( $patch, $fullpath,
"Adding $field field to table $table" );
855 protected function addIndex( $table, $index, $patch, $fullpath =
false ) {
856 if ( !$this->
doTable( $table ) ) {
860 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
861 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
862 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
863 $this->
output(
"...index $index already set on $table table.\n" );
865 return $this->
applyPatch( $patch, $fullpath,
"Adding index $index to table $table" );
883 protected function dropField( $table, $field, $patch, $fullpath =
false ) {
884 if ( !$this->
doTable( $table ) ) {
888 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
889 return $this->
applyPatch( $patch, $fullpath,
"Table $table contains $field field. Dropping" );
892 $this->
output(
"...$table table does not contain $field field.\n" );
908 protected function dropIndex( $table, $index, $patch, $fullpath =
false ) {
909 if ( !$this->
doTable( $table ) ) {
913 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
914 return $this->
applyPatch( $patch, $fullpath,
"Dropping $index index from table $table" );
917 $this->
output(
"...$index key doesn't exist.\n" );
936 $skipBothIndexExistWarning, $patch, $fullpath =
false
938 if ( !$this->
doTable( $table ) ) {
943 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
944 $this->
output(
"...skipping: '$table' table doesn't exist yet.\n" );
950 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
951 $this->
output(
"...index $newIndex already set on $table table.\n" );
952 if ( !$skipBothIndexExistWarning &&
953 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
955 $this->
output(
"...WARNING: $oldIndex still exists, despite it has " .
956 "been renamed into $newIndex (which also exists).\n" .
957 " $oldIndex should be manually removed if not needed anymore.\n" );
964 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
965 $this->
output(
"...skipping: index $oldIndex doesn't exist.\n" );
974 "Renaming index $oldIndex into $newIndex to table $table"
992 protected function dropTable( $table, $patch =
false, $fullpath =
false ) {
993 if ( !$this->
doTable( $table ) ) {
997 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
998 $msg =
"Dropping table $table";
1000 if ( $patch ===
false ) {
1001 $this->
output(
"$msg ..." );
1002 $this->db->dropTable( $table, __METHOD__ );
1003 $this->
output(
"done.\n" );
1005 return $this->
applyPatch( $patch, $fullpath, $msg );
1008 $this->
output(
"...$table doesn't exist.\n" );
1028 protected function modifyField( $table, $field, $patch, $fullpath =
false ) {
1029 if ( !$this->
doTable( $table ) ) {
1033 $updateKey =
"$table-$field-$patch";
1034 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1035 $this->
output(
"...$table table does not exist, skipping modify field patch.\n" );
1036 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1037 $this->
output(
"...$field field does not exist in $table table, " .
1038 "skipping modify field patch.\n" );
1040 $this->
output(
"...$field in table $table already modified by patch $patch.\n" );
1042 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying $field field of table $table" );
1066 if ( !$this->
doTable( $table ) ) {
1070 $updateKey =
"$table-$patch";
1071 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1072 $this->
output(
"...$table table does not exist, skipping modify table patch.\n" );
1074 $this->
output(
"...table $table already modified by patch $patch.\n" );
1076 $apply = $this->
applyPatch( $patch, $fullpath,
"Modifying table $table" );
1106 $this->
output(
"Running $script...\n" );
1107 $task = $this->maintenance->runChild( $class );
1108 $ok = $task->execute();
1110 throw new RuntimeException(
"Execution of $script did not complete successfully." );
1112 $this->
output(
"done.\n" );
1122 $zonePath = $repo->getZonePath(
'temp' );
1123 if ( $repo->getBackend()->directoryExists( [
'dir' => $zonePath ] ) ) {
1125 $status = $repo->getBackend()->secure( [
1130 if ( $status->isOK() ) {
1131 $this->
output(
"Set the local repo temp zone container to be private.\n" );
1133 $this->
output(
"Failed to set the local repo temp zone container to be private.\n" );
1145 $this->
output(
"Purging caches..." );
1148 $this->db->newDeleteQueryBuilder()
1149 ->deleteFrom(
'objectcache' )
1150 ->where( ISQLPlatform::ALL_ROWS )
1151 ->caller( __METHOD__ )
1162 $services->getMainWANObjectCache()
1166 $this->db->newDeleteQueryBuilder()
1167 ->deleteFrom(
'module_deps' )
1168 ->where( ISQLPlatform::ALL_ROWS )
1169 ->caller( __METHOD__ )
1171 $this->
output(
"done.\n" );
1178 $this->
output(
"...site_stats is populated..." );
1179 $row = $this->db->newSelectQueryBuilder()
1181 ->from(
'site_stats' )
1182 ->where( [
'ss_row_id' => 1 ] )
1183 ->caller( __METHOD__ )->fetchRow();
1184 if ( $row ===
false ) {
1185 $this->
output(
"data is missing! rebuilding...\n" );
1186 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1187 $this->
output(
"missing ss_total_pages, rebuilding...\n" );
1189 $this->
output(
"done.\n" );
1196 # Common updater functions
1203 if ( $this->
updateRowExists(
'UpdateCollation::' . $wgCategoryCollation ) ) {
1204 $this->
output(
"...collations up-to-date.\n" );
1207 $this->
output(
"Updating category collations...\n" );
1208 $task = $this->maintenance->runChild( UpdateCollation::class );
1209 $ok = $task->execute();
1210 if ( $ok !==
false ) {
1211 $this->
output(
"...done.\n" );
1220 $this->
output(
"Converting djvu metadata..." );
1221 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1222 '@phan-var RefreshImageMetadata $task';
1223 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1225 'mediatype' =>
'OFFICE',
1226 'mime' =>
'image/*',
1230 $ok = $task->execute();
1231 if ( $ok !==
false ) {
1232 $this->
output(
"...done.\n" );
1244 $cl = $this->maintenance->runChild(
1245 RebuildLocalisationCache::class,
'rebuildLocalisationCache.php'
1247 '@phan-var RebuildLocalisationCache $cl';
1248 $this->
output(
"Rebuilding localisation cache...\n" );
1251 $this->
output(
"done.\n" );
1255 if ( $this->
updateRowExists( MigrateLinksTable::class .
'templatelinks' ) ) {
1256 $this->
output(
"...templatelinks table has already been migrated.\n" );
1262 $task = $this->maintenance->runChild(
1263 MigrateLinksTable::class,
'migrateLinksTable.php'
1265 '@phan-var MigrateLinksTable $task';
1266 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1268 'table' =>
'templatelinks'
1270 $this->
output(
"Running migrateLinksTable.php on templatelinks...\n" );
1272 $this->
output(
"done.\n" );
1291 if ( $table === $this ) {
1294 $func = array_shift(
$params );
1297 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1301 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1302 $func = [ $this, $func ];
1303 } elseif ( $passSelf ) {
1304 array_unshift(
$params, $this );
1328 if ( $table === $this ) {
1332 $func = array_shift(
$params );
1335 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1336 !$this->db->fieldExists( $table, $field, __METHOD__ )
1341 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1342 $func = [ $this, $func ];
1343 } elseif ( $passSelf ) {
1344 array_unshift(
$params, $this );
1354class_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.