MediaWiki master
DatabaseUpdater.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Installer;
25
27use AutoLoader;
33use LogicException;
34use Maintenance;
52use RuntimeException;
53use UnexpectedValueException;
58
59require_once __DIR__ . '/../../maintenance/Maintenance.php';
60
67abstract class DatabaseUpdater {
68 public const REPLICATION_WAIT_TIMEOUT = 300;
69
75 protected $updates = [];
76
82 protected $updatesSkipped = [];
83
88 protected $extensionUpdates = [];
89
95
101 protected $db;
102
106 protected $maintenance;
107
108 protected $shared = false;
109
112
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,
129 ];
130
136 protected $fileHandle = null;
137
143 protected $skipSchema = false;
144
150 protected function __construct(
152 $shared,
154 ) {
155 $this->db = $db;
156 $this->db->setFlag( DBO_DDLMODE );
157 $this->shared = $shared;
158 if ( $maintenance ) {
159 $this->maintenance = $maintenance;
160 $this->fileHandle = $maintenance->fileHandle;
161 } else {
162 $this->maintenance = new FakeMaintenance;
163 }
164 $this->maintenance->setDB( $db );
165 }
166
170 private function loadExtensionSchemaUpdates() {
171 $hookContainer = $this->loadExtensions();
172 ( new HookRunner( $hookContainer ) )->onLoadExtensionSchemaUpdates( $this );
173 }
174
181 private function loadExtensions() {
182 if ( $this->autoExtensionHookContainer ) {
183 // Already injected by installer
185 }
186 if ( defined( 'MW_EXTENSIONS_LOADED' ) ) {
187 throw new LogicException( __METHOD__ .
188 ' apparently called from installer but no hook container was injected' );
189 }
190 if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
191 // Running under update.php: use the global locator
192 return MediaWikiServices::getInstance()->getHookContainer();
193 }
195
196 $registry = ExtensionRegistry::getInstance();
197 $queue = $registry->getQueue();
198 // Don't accidentally load extensions in the future
199 $registry->clearQueue();
200
201 // Read extension.json files
202 $extInfo = $registry->readFromQueue( $queue );
203
204 // Merge extension attribute hooks with hooks defined by a .php
205 // registration file included from LocalSettings.php
206 $legacySchemaHooks = $extInfo['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ?? [];
207 if ( $vars && isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
208 $legacySchemaHooks = array_merge( $legacySchemaHooks, $vars['wgHooks']['LoadExtensionSchemaUpdates'] );
209 }
210
211 // Register classes defined by extensions that are loaded by including of a file that
212 // updates global variables, rather than having an extension.json manifest.
213 if ( $vars && isset( $vars['wgAutoloadClasses'] ) ) {
214 AutoLoader::registerClasses( $vars['wgAutoloadClasses'] );
215 }
216
217 // Register class definitions from extension.json files
218 if ( !isset( $extInfo['autoloaderPaths'] )
219 || !isset( $extInfo['autoloaderClasses'] )
220 || !isset( $extInfo['autoloaderNS'] )
221 ) {
222 // NOTE: protect against changes to the structure of $extInfo.
223 // It's volatile, and this usage is easy to miss.
224 throw new LogicException( 'Missing autoloader keys from extracted extension info' );
225 }
226 AutoLoader::loadFiles( $extInfo['autoloaderPaths'] );
227 AutoLoader::registerClasses( $extInfo['autoloaderClasses'] );
228 AutoLoader::registerNamespaces( $extInfo['autoloaderNS'] );
229
230 return new HookContainer(
231 new StaticHookRegistry(
232 [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
233 $extInfo['attributes']['Hooks'] ?? [],
234 $extInfo['attributes']['DeprecatedHooks'] ?? []
235 ),
236 MediaWikiServices::getInstance()->getObjectFactory()
237 );
238 }
239
246 public static function newForDB(
248 $shared = false,
250 ) {
251 $type = $db->getType();
252 if ( in_array( $type, Installer::getDBTypes() ) ) {
253 $class = '\\MediaWiki\\Installer\\' . ucfirst( $type ) . 'Updater';
254
255 return new $class( $db, $shared, $maintenance );
256 }
257
258 throw new UnexpectedValueException( __METHOD__ . ' called for unsupported DB type' );
259 }
260
268 public function setAutoExtensionHookContainer( HookContainer $hookContainer ) {
269 $this->autoExtensionHookContainer = $hookContainer;
270 }
271
277 public function getDB() {
278 return $this->db;
279 }
280
287 public function output( $str ) {
288 if ( $this->maintenance->isQuiet() ) {
289 return;
290 }
291 if ( MW_ENTRY_POINT !== 'cli' ) {
292 $str = htmlspecialchars( $str );
293 }
294 echo $str;
295 flush();
296 }
297
310 public function addExtensionUpdate( array $update ) {
311 $this->extensionUpdates[] = $update;
312 }
313
323 public function addExtensionUpdateOnVirtualDomain( array $update ) {
324 $this->extensionUpdatesWithVirtualDomains[] = $update;
325 }
326
337 public function addExtensionTable( $tableName, $sqlPath ) {
338 $this->extensionUpdates[] = [ 'addTable', $tableName, $sqlPath, true ];
339 }
340
351 public function addExtensionIndex( $tableName, $indexName, $sqlPath ) {
352 $this->extensionUpdates[] = [ 'addIndex', $tableName, $indexName, $sqlPath, true ];
353 }
354
365 public function addExtensionField( $tableName, $columnName, $sqlPath ) {
366 $this->extensionUpdates[] = [ 'addField', $tableName, $columnName, $sqlPath, true ];
367 }
368
379 public function dropExtensionField( $tableName, $columnName, $sqlPath ) {
380 $this->extensionUpdates[] = [ 'dropField', $tableName, $columnName, $sqlPath, true ];
381 }
382
393 public function dropExtensionIndex( $tableName, $indexName, $sqlPath ) {
394 $this->extensionUpdates[] = [ 'dropIndex', $tableName, $indexName, $sqlPath, true ];
395 }
396
406 public function dropExtensionTable( $tableName, $sqlPath = false ) {
407 $this->extensionUpdates[] = [ 'dropTable', $tableName, $sqlPath, true ];
408 }
409
423 public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName,
424 $sqlPath, $skipBothIndexExistWarning = false
425 ) {
426 $this->extensionUpdates[] = [
427 'renameIndex',
428 $tableName,
429 $oldIndexName,
430 $newIndexName,
431 $skipBothIndexExistWarning,
432 $sqlPath,
433 true
434 ];
435 }
436
447 public function modifyExtensionField( $tableName, $fieldName, $sqlPath ) {
448 $this->extensionUpdates[] = [ 'modifyField', $tableName, $fieldName, $sqlPath, true ];
449 }
450
460 public function modifyExtensionTable( $tableName, $sqlPath ) {
461 $this->extensionUpdates[] = [ 'modifyTable', $tableName, $sqlPath, true ];
462 }
463
470 public function tableExists( $tableName ) {
471 return ( $this->db->tableExists( $tableName, __METHOD__ ) );
472 }
473
481 public function fieldExists( $tableName, $fieldName ) {
482 return ( $this->db->fieldExists( $tableName, $fieldName, __METHOD__ ) );
483 }
484
494 public function addPostDatabaseUpdateMaintenance( $class ) {
495 $this->postDatabaseUpdateMaintenance[] = $class;
496 }
497
503 protected function getExtensionUpdates() {
505 }
506
515
521 private function writeSchemaUpdateFile() {
523 $this->updatesSkipped = [];
524
525 foreach ( $updates as [ $func, $args, $origParams ] ) {
526 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable
527 $func( ...$args );
528 flush();
529 $this->updatesSkipped[] = $origParams;
530 }
531 }
532
543 public function getSchemaVars() {
544 return []; // DB-type specific
545 }
546
552 public function doUpdates( array $what = [ 'core', 'extensions', 'stats' ] ) {
553 $this->db->setSchemaVars( $this->getSchemaVars() );
554
555 $what = array_fill_keys( $what, true );
556 $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
557 if ( isset( $what['core'] ) ) {
558 $this->doCollationUpdate();
559 $this->runUpdates( $this->getCoreUpdateList(), false );
560 }
561 if ( isset( $what['extensions'] ) ) {
562 $this->loadExtensionSchemaUpdates();
563 $this->runUpdates( $this->getExtensionUpdates(), true );
564 $this->runUpdates( $this->extensionUpdatesWithVirtualDomains, true, true );
565 }
566
567 if ( isset( $what['stats'] ) ) {
568 $this->checkStats();
569 }
570
571 if ( $this->fileHandle ) {
572 $this->skipSchema = false;
573 $this->writeSchemaUpdateFile();
574 }
575 }
576
584 private function runUpdates( array $updates, $passSelf, $hasVirtualDomain = false ) {
585 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
586 $updatesDone = [];
587 $updatesSkipped = [];
588 foreach ( $updates as $params ) {
589 $origParams = $params;
590 $oldDb = null;
591 $virtualDomain = null;
592 if ( $hasVirtualDomain === true ) {
593 $virtualDomain = array_shift( $params );
594 $oldDb = $this->db;
595 $virtualDb = $lbFactory->getPrimaryDatabase( $virtualDomain );
596 '@phan-var IMaintainableDatabase $virtualDb';
597 $this->maintenance->setDB( $virtualDb );
598 $this->db = $virtualDb;
599 }
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 );
605 }
606 $ret = $func( ...$params );
607 if ( $hasVirtualDomain === true && $oldDb ) {
608 $this->db = $oldDb;
609 $this->maintenance->setDB( $oldDb );
610 }
611
612 flush();
613 if ( $ret !== false ) {
614 $updatesDone[] = $origParams;
615 $lbFactory->waitForReplication( [ 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
616 } else {
617 if ( $hasVirtualDomain === true ) {
618 $params = $origParams;
619 $func = array_shift( $params );
620 }
621 $updatesSkipped[] = [ $func, $params, $origParams ];
622 }
623 }
624 $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
625 $this->updates = array_merge( $this->updates, $updatesDone );
626 }
627
634 public function updateRowExists( $key ) {
635 $row = $this->db->newSelectQueryBuilder()
636 ->select( '1 AS X' ) // T67813
637 ->from( 'updatelog' )
638 ->where( [ 'ul_key' => $key ] )
639 ->caller( __METHOD__ )->fetchRow();
640
641 return (bool)$row;
642 }
643
654 public function insertUpdateRow( $key, $val = null ) {
655 $this->db->clearFlag( DBO_DDLMODE );
656 $values = [ 'ul_key' => $key ];
657 if ( $val ) {
658 $values['ul_value'] = $val;
659 }
660 $this->db->newInsertQueryBuilder()
661 ->insertInto( 'updatelog' )
662 ->ignore()
663 ->row( $values )
664 ->caller( __METHOD__ )->execute();
665 $this->db->setFlag( DBO_DDLMODE );
666 }
667
676 protected function doTable( $name ) {
678
679 // Don't bother to check $wgSharedTables if there isn't a shared database
680 // or the user actually also wants to do updates on the shared database.
681 if ( $wgSharedDB === null || $this->shared ) {
682 return true;
683 }
684
685 if ( in_array( $name, $wgSharedTables ) ) {
686 $this->output( "...skipping update to shared table $name.\n" );
687 return false;
688 }
689
690 return true;
691 }
692
700 abstract protected function getCoreUpdateList();
701
709 protected function copyFile( $filename ) {
710 $this->db->sourceFile(
711 $filename,
712 null,
713 null,
714 __METHOD__,
715 function ( $line ) {
716 return $this->appendLine( $line );
717 }
718 );
719 }
720
732 protected function appendLine( $line ) {
733 $line = rtrim( $line ) . ";\n";
734 if ( fwrite( $this->fileHandle, $line ) === false ) {
735 throw new RuntimeException( "trouble writing file" );
736 }
737
738 return false;
739 }
740
752 protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
753 $msg ??= "Applying $path patch";
754 if ( $this->skipSchema ) {
755 $this->output( "...skipping schema change ($msg).\n" );
756
757 return false;
758 }
759
760 $this->output( "{$msg}..." );
761
762 if ( !$isFullPath ) {
763 $path = $this->patchPath( $this->db, $path );
764 }
765 if ( $this->fileHandle !== null ) {
766 $this->copyFile( $path );
767 } else {
768 $this->db->sourceFile( $path );
769 }
770 $this->output( "done.\n" );
771
772 return true;
773 }
774
783 public function patchPath( IDatabase $db, $patch ) {
784 $baseDir = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::BaseDirectory );
785
786 $dbType = $db->getType();
787 if ( file_exists( "$baseDir/maintenance/$dbType/archives/$patch" ) ) {
788 return "$baseDir/maintenance/$dbType/archives/$patch";
789 }
790
791 return "$baseDir/maintenance/archives/$patch";
792 }
793
805 protected function addTable( $name, $patch, $fullpath = false ) {
806 if ( !$this->doTable( $name ) ) {
807 return true;
808 }
809
810 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
811 $this->output( "...$name table already exists.\n" );
812 return true;
813 }
814
815 return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
816 }
817
830 protected function addField( $table, $field, $patch, $fullpath = false ) {
831 if ( !$this->doTable( $table ) ) {
832 return true;
833 }
834
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" );
839 } else {
840 return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
841 }
842
843 return true;
844 }
845
858 protected function addIndex( $table, $index, $patch, $fullpath = false ) {
859 if ( !$this->doTable( $table ) ) {
860 return true;
861 }
862
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" );
867 } else {
868 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
869 }
870
871 return true;
872 }
873
886 protected function dropField( $table, $field, $patch, $fullpath = false ) {
887 if ( !$this->doTable( $table ) ) {
888 return true;
889 }
890
891 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
892 return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
893 }
894
895 $this->output( "...$table table does not contain $field field.\n" );
896 return true;
897 }
898
911 protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
912 if ( !$this->doTable( $table ) ) {
913 return true;
914 }
915
916 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
917 return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
918 }
919
920 $this->output( "...$index key doesn't exist.\n" );
921 return true;
922 }
923
938 protected function renameIndex( $table, $oldIndex, $newIndex,
939 $skipBothIndexExistWarning, $patch, $fullpath = false
940 ) {
941 if ( !$this->doTable( $table ) ) {
942 return true;
943 }
944
945 // First requirement: the table must exist
946 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
947 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
948
949 return true;
950 }
951
952 // Second requirement: the new index must be missing
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__ )
957 ) {
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" );
961 }
962
963 return true;
964 }
965
966 // Third requirement: the old index must exist
967 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
968 $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
969
970 return true;
971 }
972
973 // Requirements have been satisfied, the patch can be applied
974 return $this->applyPatch(
975 $patch,
976 $fullpath,
977 "Renaming index $oldIndex into $newIndex to table $table"
978 );
979 }
980
995 protected function dropTable( $table, $patch = false, $fullpath = false ) {
996 if ( !$this->doTable( $table ) ) {
997 return true;
998 }
999
1000 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1001 $msg = "Dropping table $table";
1002
1003 if ( $patch === false ) {
1004 $this->output( "$msg ..." );
1005 $this->db->dropTable( $table, __METHOD__ );
1006 $this->output( "done.\n" );
1007 } else {
1008 return $this->applyPatch( $patch, $fullpath, $msg );
1009 }
1010 } else {
1011 $this->output( "...$table doesn't exist.\n" );
1012 }
1013
1014 return true;
1015 }
1016
1031 protected function modifyField( $table, $field, $patch, $fullpath = false ) {
1032 if ( !$this->doTable( $table ) ) {
1033 return true;
1034 }
1035
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" );
1042 } elseif ( $this->updateRowExists( $updateKey ) ) {
1043 $this->output( "...$field in table $table already modified by patch $patch.\n" );
1044 } else {
1045 $apply = $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
1046 if ( $apply ) {
1047 $this->insertUpdateRow( $updateKey );
1048 }
1049 return $apply;
1050 }
1051 return true;
1052 }
1053
1068 protected function modifyTable( $table, $patch, $fullpath = false ) {
1069 if ( !$this->doTable( $table ) ) {
1070 return true;
1071 }
1072
1073 $updateKey = "$table-$patch";
1074 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1075 $this->output( "...$table table does not exist, skipping modify table patch.\n" );
1076 } elseif ( $this->updateRowExists( $updateKey ) ) {
1077 $this->output( "...table $table already modified by patch $patch.\n" );
1078 } else {
1079 $apply = $this->applyPatch( $patch, $fullpath, "Modifying table $table" );
1080 if ( $apply ) {
1081 $this->insertUpdateRow( $updateKey );
1082 }
1083 return $apply;
1084 }
1085 return true;
1086 }
1087
1108 protected function runMaintenance( $class, $unused = '' ) {
1109 $this->output( "Running $class...\n" );
1110 $task = $this->maintenance->runChild( $class );
1111 $ok = $task->execute();
1112 if ( !$ok ) {
1113 throw new RuntimeException( "Execution of $class did not complete successfully." );
1114 }
1115 $this->output( "done.\n" );
1116 }
1117
1123 public function setFileAccess() {
1124 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1125 $zonePath = $repo->getZonePath( 'temp' );
1126 if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
1127 // If the directory was never made, then it will have the right ACLs when it is made
1128 $status = $repo->getBackend()->secure( [
1129 'dir' => $zonePath,
1130 'noAccess' => true,
1131 'noListing' => true
1132 ] );
1133 if ( $status->isOK() ) {
1134 $this->output( "Set the local repo temp zone container to be private.\n" );
1135 } else {
1136 $this->output( "Failed to set the local repo temp zone container to be private.\n" );
1137 }
1138 }
1139 }
1140
1144 public function purgeCache() {
1146 // We can't guarantee that the user will be able to use TRUNCATE,
1147 // but we know that DELETE is available to us
1148 $this->output( "Purging caches..." );
1149
1150 // ObjectCache
1151 $this->db->newDeleteQueryBuilder()
1152 ->deleteFrom( 'objectcache' )
1153 ->where( ISQLPlatform::ALL_ROWS )
1154 ->caller( __METHOD__ )
1155 ->execute();
1156
1157 // LocalisationCache
1158 if ( $wgLocalisationCacheConf['manualRecache'] ) {
1159 $this->rebuildLocalisationCache();
1160 }
1161
1162 // ResourceLoader: Message cache
1163 $services = MediaWikiServices::getInstance();
1165 $services->getMainWANObjectCache()
1166 );
1167
1168 // ResourceLoader: File-dependency cache
1169 $this->db->newDeleteQueryBuilder()
1170 ->deleteFrom( 'module_deps' )
1171 ->where( ISQLPlatform::ALL_ROWS )
1172 ->caller( __METHOD__ )
1173 ->execute();
1174 $this->output( "done.\n" );
1175 }
1176
1180 protected function checkStats() {
1181 $this->output( "...site_stats is populated..." );
1182 $row = $this->db->newSelectQueryBuilder()
1183 ->select( '*' )
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" );
1191 } else {
1192 $this->output( "done.\n" );
1193
1194 return;
1195 }
1196 SiteStatsInit::doAllAndCommit( $this->db );
1197 }
1198
1199 # Common updater functions
1200
1204 protected function doCollationUpdate() {
1205 global $wgCategoryCollation;
1206 if ( $this->updateRowExists( 'UpdateCollation::' . $wgCategoryCollation ) ) {
1207 $this->output( "...collations up-to-date.\n" );
1208 return;
1209 }
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" );
1215 $this->insertUpdateRow( 'UpdateCollation::' . $wgCategoryCollation );
1216 }
1217 }
1218
1219 protected function doConvertDjvuMetadata() {
1220 if ( $this->updateRowExists( 'ConvertDjvuMetadata' ) ) {
1221 return;
1222 }
1223 $this->output( "Converting djvu metadata..." );
1224 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1225 '@phan-var RefreshImageMetadata $task';
1226 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1227 'force' => true,
1228 'mediatype' => 'OFFICE',
1229 'mime' => 'image/*',
1230 'batch-size' => 1,
1231 'sleep' => 1
1232 ] );
1233 $ok = $task->execute();
1234 if ( $ok !== false ) {
1235 $this->output( "...done.\n" );
1236 $this->insertUpdateRow( 'ConvertDjvuMetadata' );
1237 }
1238 }
1239
1243 protected function rebuildLocalisationCache() {
1247 $cl = $this->maintenance->runChild(
1248 RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
1249 );
1250 '@phan-var RebuildLocalisationCache $cl';
1251 $this->output( "Rebuilding localisation cache...\n" );
1252 $cl->setForce();
1253 $cl->execute();
1254 $this->output( "done.\n" );
1255 }
1256
1257 protected function migrateTemplatelinks() {
1258 if ( $this->updateRowExists( MigrateLinksTable::class . 'templatelinks' ) ) {
1259 $this->output( "...templatelinks table has already been migrated.\n" );
1260 return;
1261 }
1265 $task = $this->maintenance->runChild(
1266 MigrateLinksTable::class, 'migrateLinksTable.php'
1267 );
1268 '@phan-var MigrateLinksTable $task';
1269 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1270 'force' => true,
1271 'table' => 'templatelinks'
1272 ] );
1273 $this->output( "Running migrateLinksTable.php on templatelinks...\n" );
1274 $task->execute();
1275 $this->output( "done.\n" );
1276 }
1277
1278 protected function migratePagelinks() {
1279 if ( $this->updateRowExists( MigrateLinksTable::class . 'pagelinks' ) ) {
1280 $this->output( "...pagelinks table has already been migrated.\n" );
1281 return;
1282 }
1286 $task = $this->maintenance->runChild(
1287 MigrateLinksTable::class, 'migrateLinksTable.php'
1288 );
1289 '@phan-var MigrateLinksTable $task';
1290 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1291 'force' => true,
1292 'table' => 'pagelinks'
1293 ] );
1294 $this->output( "Running migrateLinksTable.php on pagelinks...\n" );
1295 $task->execute();
1296 $this->output( "done.\n" );
1297 }
1298
1312 protected function ifTableNotExists( $table, $func, ...$params ) {
1313 // Handle $passSelf from runUpdates().
1314 $passSelf = false;
1315 if ( $table === $this ) {
1316 $passSelf = true;
1317 $table = $func;
1318 $func = array_shift( $params );
1319 }
1320
1321 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1322 return null;
1323 }
1324
1325 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1326 $func = [ $this, $func ];
1327 } elseif ( $passSelf ) {
1328 array_unshift( $params, $this );
1329 }
1330
1331 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1332 return $func( ...$params );
1333 }
1334
1349 protected function ifFieldExists( $table, $field, $func, ...$params ) {
1350 // Handle $passSelf from runUpdates().
1351 $passSelf = false;
1352 if ( $table === $this ) {
1353 $passSelf = true;
1354 $table = $field;
1355 $field = $func;
1356 $func = array_shift( $params );
1357 }
1358
1359 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1360 !$this->db->fieldExists( $table, $field, __METHOD__ )
1361 ) {
1362 return null;
1363 }
1364
1365 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1366 $func = [ $this, $func ];
1367 } elseif ( $passSelf ) {
1368 array_unshift( $params, $this );
1369 }
1370
1371 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1372 return $func( ...$params );
1373 }
1374
1375}
1376
1378class_alias( DatabaseUpdater::class, 'DatabaseUpdater' );
array $params
The job parameters.
const MW_ENTRY_POINT
Definition api.php:35
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().
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
This is a simple immutable HookRegistry which can be used to set up a local HookContainer in tests an...
Class for handling database updates.
array $extensionUpdates
List of extension-provided database updates.
dropExtensionTable( $tableName, $sqlPath=false)
Drop an extension table.
getSchemaVars()
Get appropriate schema variables in the current database connection.
applyPatch( $path, $isFullPath=false, $msg=null)
Applies a SQL patch.
checkStats()
Check the site_stats table is not properly populated.
modifyExtensionField( $tableName, $fieldName, $sqlPath)
Modify an existing field in an extension table.
array $updates
Array of updates to perform on the database.
addExtensionField( $tableName, $columnName, $sqlPath)
Add a field to an existing extension table.
addExtensionIndex( $tableName, $indexName, $sqlPath)
Add an index to an existing extension table.
dropField( $table, $field, $patch, $fullpath=false)
Drop a field from an existing table.
copyFile( $filename)
Append an SQL fragment to the open file handle.
addField( $table, $field, $patch, $fullpath=false)
Add a new field to an existing table.
ifTableNotExists( $table, $func,... $params)
Only run a function if a table does not exist.
dropIndex( $table, $index, $patch, $fullpath=false)
Drop an index from an existing table.
patchPath(IDatabase $db, $patch)
Get the full path of a patch file.
renameIndex( $table, $oldIndex, $newIndex, $skipBothIndexExistWarning, $patch, $fullpath=false)
Rename an index from an existing table.
getDB()
Get a database connection to run updates.
appendLine( $line)
Append a line to the open file handle.
bool $skipSchema
Flag specifying whether to skip schema (e.g., SQL-only) updates.
modifyField( $table, $field, $patch, $fullpath=false)
Modify an existing field.
doCollationUpdate()
Update CategoryLinks collation.
string[] $postDatabaseUpdateMaintenance
Scripts to run after database update Should be a subclass of LoggedUpdateMaintenance.
ifFieldExists( $table, $field, $func,... $params)
Only run a function if the named field exists.
dropExtensionField( $tableName, $columnName, $sqlPath)
Drop a field from an extension table.
modifyTable( $table, $patch, $fullpath=false)
Modify an existing table, similar to modifyField.
renameExtensionIndex( $tableName, $oldIndexName, $newIndexName, $sqlPath, $skipBothIndexExistWarning=false)
Rename an index on an extension table Intended for use in LoadExtensionSchemaUpdates hook handlers.
getCoreUpdateList()
Get an array of updates to perform on the database.
doTable( $name)
Returns whether updates should be executed on the database table $name.
setAutoExtensionHookContainer(HookContainer $hookContainer)
Set the HookContainer to use for loading extension schema updates.
fieldExists( $tableName, $fieldName)
array $extensionUpdatesWithVirtualDomains
List of extension-provided database updates on virtual domain dbs.
updateRowExists( $key)
Helper function: check if the given key is present in the updatelog table.
addExtensionUpdateOnVirtualDomain(array $update)
Add a new update coming from an extension on virtual domain databases.
addPostDatabaseUpdateMaintenance( $class)
Add a maintenance script to be run after the database updates are complete.
insertUpdateRow( $key, $val=null)
Helper function: Add a key to the updatelog table.
IMaintainableDatabase $db
Handle to the database subclass.
addIndex( $table, $index, $patch, $fullpath=false)
Add a new index to an existing table.
array $updatesSkipped
Array of updates that were skipped.
addExtensionUpdate(array $update)
Add a new update coming from an extension.
__construct(IMaintainableDatabase &$db, $shared, Maintenance $maintenance=null)
setFileAccess()
Set any .htaccess files or equivalent for storage repos.
static newForDB(IMaintainableDatabase $db, $shared=false, Maintenance $maintenance=null)
resource null $fileHandle
File handle for SQL output.
runMaintenance( $class, $unused='')
Run a maintenance script.
addTable( $name, $patch, $fullpath=false)
Add a new table to the database.
rebuildLocalisationCache()
Rebuilds the localisation cache.
doUpdates(array $what=[ 'core', 'extensions', 'stats'])
Do all the updates.
modifyExtensionTable( $tableName, $sqlPath)
Modify an existing extension table.
addExtensionTable( $tableName, $sqlPath)
Convenience wrapper for addExtensionUpdate() when adding a new table (which is the most common usage ...
getExtensionUpdates()
Get the list of extension-defined updates.
dropTable( $table, $patch=false, $fullpath=false)
If the specified table exists, drop it, or execute the patch if one is provided.
dropExtensionIndex( $tableName, $indexName, $sqlPath)
Drop an index from an extension table Intended for use in LoadExtensionSchemaUpdates hook handlers.
purgeCache()
Purge various database caches.
static getDBTypes()
Get a list of known DB types.
static getExistingLocalSettings()
Determine if LocalSettings.php exists.
A class containing constants representing the names of configuration variables.
const BaseDirectory
Name constant for the BaseDirectory setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
This class generates message blobs for use by ResourceLoader.
static clearGlobalCacheEntry(WANObjectCache $cache)
Invalidate cache keys for all known modules.
Class designed for counting of stats.
static doAllAndCommit( $database, array $options=[])
Do all updates and commit them.
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 to refresh image metadata fields.
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.
setFlag( $flag, $remember=self::REMEMBER_NOTHING)
Set a flag for this connection.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:39
Advanced database interface for IDatabase handles that include maintenance methods.
getType()
Get the RDBMS type of the server (e.g.
Interface for query language.
const DBO_DDLMODE
Definition defines.php:16