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 $this->db = $lbFactory->getPrimaryDatabase( $virtualDomain );
596 '@phan-var IMaintainableDatabase $this->db';
597 }
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 );
603 }
604 $ret = $func( ...$params );
605 if ( $hasVirtualDomain === true && $oldDb ) {
606 $this->db = $oldDb;
607 }
608
609 flush();
610 if ( $ret !== false ) {
611 $updatesDone[] = $origParams;
612 $lbFactory->waitForReplication( [ 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
613 } else {
614 if ( $hasVirtualDomain === true ) {
615 $params = $origParams;
616 $func = array_shift( $params );
617 }
618 $updatesSkipped[] = [ $func, $params, $origParams ];
619 }
620 }
621 $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
622 $this->updates = array_merge( $this->updates, $updatesDone );
623 }
624
631 public function updateRowExists( $key ) {
632 $row = $this->db->newSelectQueryBuilder()
633 ->select( '1 AS X' ) // T67813
634 ->from( 'updatelog' )
635 ->where( [ 'ul_key' => $key ] )
636 ->caller( __METHOD__ )->fetchRow();
637
638 return (bool)$row;
639 }
640
651 public function insertUpdateRow( $key, $val = null ) {
652 $this->db->clearFlag( DBO_DDLMODE );
653 $values = [ 'ul_key' => $key ];
654 if ( $val ) {
655 $values['ul_value'] = $val;
656 }
657 $this->db->newInsertQueryBuilder()
658 ->insertInto( 'updatelog' )
659 ->ignore()
660 ->row( $values )
661 ->caller( __METHOD__ )->execute();
662 $this->db->setFlag( DBO_DDLMODE );
663 }
664
673 protected function doTable( $name ) {
675
676 // Don't bother to check $wgSharedTables if there isn't a shared database
677 // or the user actually also wants to do updates on the shared database.
678 if ( $wgSharedDB === null || $this->shared ) {
679 return true;
680 }
681
682 if ( in_array( $name, $wgSharedTables ) ) {
683 $this->output( "...skipping update to shared table $name.\n" );
684 return false;
685 }
686
687 return true;
688 }
689
697 abstract protected function getCoreUpdateList();
698
706 protected function copyFile( $filename ) {
707 $this->db->sourceFile(
708 $filename,
709 null,
710 null,
711 __METHOD__,
712 function ( $line ) {
713 return $this->appendLine( $line );
714 }
715 );
716 }
717
729 protected function appendLine( $line ) {
730 $line = rtrim( $line ) . ";\n";
731 if ( fwrite( $this->fileHandle, $line ) === false ) {
732 throw new RuntimeException( "trouble writing file" );
733 }
734
735 return false;
736 }
737
749 protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
750 $msg ??= "Applying $path patch";
751 if ( $this->skipSchema ) {
752 $this->output( "...skipping schema change ($msg).\n" );
753
754 return false;
755 }
756
757 $this->output( "{$msg}..." );
758
759 if ( !$isFullPath ) {
760 $path = $this->patchPath( $this->db, $path );
761 }
762 if ( $this->fileHandle !== null ) {
763 $this->copyFile( $path );
764 } else {
765 $this->db->sourceFile( $path );
766 }
767 $this->output( "done.\n" );
768
769 return true;
770 }
771
780 public function patchPath( IDatabase $db, $patch ) {
781 $baseDir = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::BaseDirectory );
782
783 $dbType = $db->getType();
784 if ( file_exists( "$baseDir/maintenance/$dbType/archives/$patch" ) ) {
785 return "$baseDir/maintenance/$dbType/archives/$patch";
786 }
787
788 return "$baseDir/maintenance/archives/$patch";
789 }
790
802 protected function addTable( $name, $patch, $fullpath = false ) {
803 if ( !$this->doTable( $name ) ) {
804 return true;
805 }
806
807 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
808 $this->output( "...$name table already exists.\n" );
809 return true;
810 }
811
812 return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
813 }
814
827 protected function addField( $table, $field, $patch, $fullpath = false ) {
828 if ( !$this->doTable( $table ) ) {
829 return true;
830 }
831
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" );
836 } else {
837 return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
838 }
839
840 return true;
841 }
842
855 protected function addIndex( $table, $index, $patch, $fullpath = false ) {
856 if ( !$this->doTable( $table ) ) {
857 return true;
858 }
859
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" );
864 } else {
865 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
866 }
867
868 return true;
869 }
870
883 protected function dropField( $table, $field, $patch, $fullpath = false ) {
884 if ( !$this->doTable( $table ) ) {
885 return true;
886 }
887
888 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
889 return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
890 }
891
892 $this->output( "...$table table does not contain $field field.\n" );
893 return true;
894 }
895
908 protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
909 if ( !$this->doTable( $table ) ) {
910 return true;
911 }
912
913 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
914 return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
915 }
916
917 $this->output( "...$index key doesn't exist.\n" );
918 return true;
919 }
920
935 protected function renameIndex( $table, $oldIndex, $newIndex,
936 $skipBothIndexExistWarning, $patch, $fullpath = false
937 ) {
938 if ( !$this->doTable( $table ) ) {
939 return true;
940 }
941
942 // First requirement: the table must exist
943 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
944 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
945
946 return true;
947 }
948
949 // Second requirement: the new index must be missing
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__ )
954 ) {
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" );
958 }
959
960 return true;
961 }
962
963 // Third requirement: the old index must exist
964 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
965 $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
966
967 return true;
968 }
969
970 // Requirements have been satisfied, the patch can be applied
971 return $this->applyPatch(
972 $patch,
973 $fullpath,
974 "Renaming index $oldIndex into $newIndex to table $table"
975 );
976 }
977
992 protected function dropTable( $table, $patch = false, $fullpath = false ) {
993 if ( !$this->doTable( $table ) ) {
994 return true;
995 }
996
997 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
998 $msg = "Dropping table $table";
999
1000 if ( $patch === false ) {
1001 $this->output( "$msg ..." );
1002 $this->db->dropTable( $table, __METHOD__ );
1003 $this->output( "done.\n" );
1004 } else {
1005 return $this->applyPatch( $patch, $fullpath, $msg );
1006 }
1007 } else {
1008 $this->output( "...$table doesn't exist.\n" );
1009 }
1010
1011 return true;
1012 }
1013
1028 protected function modifyField( $table, $field, $patch, $fullpath = false ) {
1029 if ( !$this->doTable( $table ) ) {
1030 return true;
1031 }
1032
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" );
1039 } elseif ( $this->updateRowExists( $updateKey ) ) {
1040 $this->output( "...$field in table $table already modified by patch $patch.\n" );
1041 } else {
1042 $apply = $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
1043 if ( $apply ) {
1044 $this->insertUpdateRow( $updateKey );
1045 }
1046 return $apply;
1047 }
1048 return true;
1049 }
1050
1065 protected function modifyTable( $table, $patch, $fullpath = false ) {
1066 if ( !$this->doTable( $table ) ) {
1067 return true;
1068 }
1069
1070 $updateKey = "$table-$patch";
1071 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1072 $this->output( "...$table table does not exist, skipping modify table patch.\n" );
1073 } elseif ( $this->updateRowExists( $updateKey ) ) {
1074 $this->output( "...table $table already modified by patch $patch.\n" );
1075 } else {
1076 $apply = $this->applyPatch( $patch, $fullpath, "Modifying table $table" );
1077 if ( $apply ) {
1078 $this->insertUpdateRow( $updateKey );
1079 }
1080 return $apply;
1081 }
1082 return true;
1083 }
1084
1105 protected function runMaintenance( $class, $script ) {
1106 $this->output( "Running $script...\n" );
1107 $task = $this->maintenance->runChild( $class );
1108 $ok = $task->execute();
1109 if ( !$ok ) {
1110 throw new RuntimeException( "Execution of $script did not complete successfully." );
1111 }
1112 $this->output( "done.\n" );
1113 }
1114
1120 public function setFileAccess() {
1121 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1122 $zonePath = $repo->getZonePath( 'temp' );
1123 if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
1124 // If the directory was never made, then it will have the right ACLs when it is made
1125 $status = $repo->getBackend()->secure( [
1126 'dir' => $zonePath,
1127 'noAccess' => true,
1128 'noListing' => true
1129 ] );
1130 if ( $status->isOK() ) {
1131 $this->output( "Set the local repo temp zone container to be private.\n" );
1132 } else {
1133 $this->output( "Failed to set the local repo temp zone container to be private.\n" );
1134 }
1135 }
1136 }
1137
1141 public function purgeCache() {
1143 // We can't guarantee that the user will be able to use TRUNCATE,
1144 // but we know that DELETE is available to us
1145 $this->output( "Purging caches..." );
1146
1147 // ObjectCache
1148 $this->db->newDeleteQueryBuilder()
1149 ->deleteFrom( 'objectcache' )
1150 ->where( ISQLPlatform::ALL_ROWS )
1151 ->caller( __METHOD__ )
1152 ->execute();
1153
1154 // LocalisationCache
1155 if ( $wgLocalisationCacheConf['manualRecache'] ) {
1156 $this->rebuildLocalisationCache();
1157 }
1158
1159 // ResourceLoader: Message cache
1160 $services = MediaWikiServices::getInstance();
1162 $services->getMainWANObjectCache()
1163 );
1164
1165 // ResourceLoader: File-dependency cache
1166 $this->db->newDeleteQueryBuilder()
1167 ->deleteFrom( 'module_deps' )
1168 ->where( ISQLPlatform::ALL_ROWS )
1169 ->caller( __METHOD__ )
1170 ->execute();
1171 $this->output( "done.\n" );
1172 }
1173
1177 protected function checkStats() {
1178 $this->output( "...site_stats is populated..." );
1179 $row = $this->db->newSelectQueryBuilder()
1180 ->select( '*' )
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" );
1188 } else {
1189 $this->output( "done.\n" );
1190
1191 return;
1192 }
1193 SiteStatsInit::doAllAndCommit( $this->db );
1194 }
1195
1196 # Common updater functions
1197
1201 protected function doCollationUpdate() {
1202 global $wgCategoryCollation;
1203 if ( $this->updateRowExists( 'UpdateCollation::' . $wgCategoryCollation ) ) {
1204 $this->output( "...collations up-to-date.\n" );
1205 return;
1206 }
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" );
1212 $this->insertUpdateRow( 'UpdateCollation::' . $wgCategoryCollation );
1213 }
1214 }
1215
1216 protected function doConvertDjvuMetadata() {
1217 if ( $this->updateRowExists( 'ConvertDjvuMetadata' ) ) {
1218 return;
1219 }
1220 $this->output( "Converting djvu metadata..." );
1221 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1222 '@phan-var RefreshImageMetadata $task';
1223 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1224 'force' => true,
1225 'mediatype' => 'OFFICE',
1226 'mime' => 'image/*',
1227 'batch-size' => 1,
1228 'sleep' => 1
1229 ] );
1230 $ok = $task->execute();
1231 if ( $ok !== false ) {
1232 $this->output( "...done.\n" );
1233 $this->insertUpdateRow( 'ConvertDjvuMetadata' );
1234 }
1235 }
1236
1240 protected function rebuildLocalisationCache() {
1244 $cl = $this->maintenance->runChild(
1245 RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
1246 );
1247 '@phan-var RebuildLocalisationCache $cl';
1248 $this->output( "Rebuilding localisation cache...\n" );
1249 $cl->setForce();
1250 $cl->execute();
1251 $this->output( "done.\n" );
1252 }
1253
1254 protected function migrateTemplatelinks() {
1255 if ( $this->updateRowExists( MigrateLinksTable::class . 'templatelinks' ) ) {
1256 $this->output( "...templatelinks table has already been migrated.\n" );
1257 return;
1258 }
1262 $task = $this->maintenance->runChild(
1263 MigrateLinksTable::class, 'migrateLinksTable.php'
1264 );
1265 '@phan-var MigrateLinksTable $task';
1266 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1267 'force' => true,
1268 'table' => 'templatelinks'
1269 ] );
1270 $this->output( "Running migrateLinksTable.php on templatelinks...\n" );
1271 $task->execute();
1272 $this->output( "done.\n" );
1273 }
1274
1288 protected function ifTableNotExists( $table, $func, ...$params ) {
1289 // Handle $passSelf from runUpdates().
1290 $passSelf = false;
1291 if ( $table === $this ) {
1292 $passSelf = true;
1293 $table = $func;
1294 $func = array_shift( $params );
1295 }
1296
1297 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1298 return null;
1299 }
1300
1301 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1302 $func = [ $this, $func ];
1303 } elseif ( $passSelf ) {
1304 array_unshift( $params, $this );
1305 }
1306
1307 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1308 return $func( ...$params );
1309 }
1310
1325 protected function ifFieldExists( $table, $field, $func, ...$params ) {
1326 // Handle $passSelf from runUpdates().
1327 $passSelf = false;
1328 if ( $table === $this ) {
1329 $passSelf = true;
1330 $table = $field;
1331 $field = $func;
1332 $func = array_shift( $params );
1333 }
1334
1335 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1336 !$this->db->fieldExists( $table, $field, __METHOD__ )
1337 ) {
1338 return null;
1339 }
1340
1341 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1342 $func = [ $this, $func ];
1343 } elseif ( $passSelf ) {
1344 array_unshift( $params, $this );
1345 }
1346
1347 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1348 return $func( ...$params );
1349 }
1350
1351}
1352
1354class_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.
runMaintenance( $class, $script)
Run a maintenance script.
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.
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_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 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:36
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