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;
57
58require_once __DIR__ . '/../../maintenance/Maintenance.php';
59
66abstract class DatabaseUpdater {
67 public const REPLICATION_WAIT_TIMEOUT = 300;
68
74 protected $updates = [];
75
81 protected $updatesSkipped = [];
82
87 protected $extensionUpdates = [];
88
94
100 protected $db;
101
105 protected $maintenance;
106
107 protected $shared = false;
108
111
117 DeleteDefaultMessages::class,
118 PopulateRevisionLength::class,
119 PopulateRevisionSha1::class,
120 PopulateImageSha1::class,
121 PopulateFilearchiveSha1::class,
122 PopulateBacklinkNamespace::class,
123 FixDefaultJsonContentPages::class,
124 CleanupEmptyCategories::class,
125 AddRFCandPMIDInterwiki::class,
126 PopulatePPSortKey::class,
127 PopulateIpChanges::class,
128 ];
129
135 protected $fileHandle = null;
136
142 protected $skipSchema = false;
143
149 protected function __construct(
151 $shared,
153 ) {
154 $this->db = $db;
155 $this->db->setFlag( DBO_DDLMODE );
156 $this->shared = $shared;
157 if ( $maintenance ) {
158 $this->maintenance = $maintenance;
159 $this->fileHandle = $maintenance->fileHandle;
160 } else {
161 $this->maintenance = new FakeMaintenance;
162 }
163 $this->maintenance->setDB( $db );
164 }
165
169 private function loadExtensionSchemaUpdates() {
170 $hookContainer = $this->loadExtensions();
171 ( new HookRunner( $hookContainer ) )->onLoadExtensionSchemaUpdates( $this );
172 }
173
180 private function loadExtensions() {
181 if ( $this->autoExtensionHookContainer ) {
182 // Already injected by installer
184 }
185 if ( defined( 'MW_EXTENSIONS_LOADED' ) ) {
186 throw new LogicException( __METHOD__ .
187 ' apparently called from installer but no hook container was injected' );
188 }
189 if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
190 // Running under update.php: use the global locator
191 return MediaWikiServices::getInstance()->getHookContainer();
192 }
194
195 $registry = ExtensionRegistry::getInstance();
196 $queue = $registry->getQueue();
197 // Don't accidentally load extensions in the future
198 $registry->clearQueue();
199
200 // Read extension.json files
201 $extInfo = $registry->readFromQueue( $queue );
202
203 // Merge extension attribute hooks with hooks defined by a .php
204 // registration file included from LocalSettings.php
205 $legacySchemaHooks = $extInfo['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ?? [];
206 if ( $vars && isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
207 $legacySchemaHooks = array_merge( $legacySchemaHooks, $vars['wgHooks']['LoadExtensionSchemaUpdates'] );
208 }
209
210 // Register classes defined by extensions that are loaded by including of a file that
211 // updates global variables, rather than having an extension.json manifest.
212 if ( $vars && isset( $vars['wgAutoloadClasses'] ) ) {
213 AutoLoader::registerClasses( $vars['wgAutoloadClasses'] );
214 }
215
216 // Register class definitions from extension.json files
217 if ( !isset( $extInfo['autoloaderPaths'] )
218 || !isset( $extInfo['autoloaderClasses'] )
219 || !isset( $extInfo['autoloaderNS'] )
220 ) {
221 // NOTE: protect against changes to the structure of $extInfo.
222 // It's volatile, and this usage is easy to miss.
223 throw new LogicException( 'Missing autoloader keys from extracted extension info' );
224 }
225 AutoLoader::loadFiles( $extInfo['autoloaderPaths'] );
226 AutoLoader::registerClasses( $extInfo['autoloaderClasses'] );
227 AutoLoader::registerNamespaces( $extInfo['autoloaderNS'] );
228
229 return new HookContainer(
230 new StaticHookRegistry(
231 [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
232 $extInfo['attributes']['Hooks'] ?? [],
233 $extInfo['attributes']['DeprecatedHooks'] ?? []
234 ),
235 MediaWikiServices::getInstance()->getObjectFactory()
236 );
237 }
238
245 public static function newForDB(
247 $shared = false,
249 ) {
250 $type = $db->getType();
251 if ( in_array( $type, Installer::getDBTypes() ) ) {
252 $class = '\\MediaWiki\\Installer\\' . ucfirst( $type ) . 'Updater';
253
254 return new $class( $db, $shared, $maintenance );
255 }
256
257 throw new UnexpectedValueException( __METHOD__ . ' called for unsupported DB type' );
258 }
259
267 public function setAutoExtensionHookContainer( HookContainer $hookContainer ) {
268 $this->autoExtensionHookContainer = $hookContainer;
269 }
270
276 public function getDB() {
277 return $this->db;
278 }
279
286 public function output( $str ) {
287 if ( $this->maintenance->isQuiet() ) {
288 return;
289 }
290 if ( MW_ENTRY_POINT !== 'cli' ) {
291 $str = htmlspecialchars( $str );
292 }
293 echo $str;
294 flush();
295 }
296
309 public function addExtensionUpdate( array $update ) {
310 $this->extensionUpdates[] = $update;
311 }
312
322 public function addExtensionUpdateOnVirtualDomain( array $update ) {
323 $this->extensionUpdatesWithVirtualDomains[] = $update;
324 }
325
336 public function addExtensionTable( $tableName, $sqlPath ) {
337 $this->extensionUpdates[] = [ 'addTable', $tableName, $sqlPath, true ];
338 }
339
350 public function addExtensionIndex( $tableName, $indexName, $sqlPath ) {
351 $this->extensionUpdates[] = [ 'addIndex', $tableName, $indexName, $sqlPath, true ];
352 }
353
364 public function addExtensionField( $tableName, $columnName, $sqlPath ) {
365 $this->extensionUpdates[] = [ 'addField', $tableName, $columnName, $sqlPath, true ];
366 }
367
378 public function dropExtensionField( $tableName, $columnName, $sqlPath ) {
379 $this->extensionUpdates[] = [ 'dropField', $tableName, $columnName, $sqlPath, true ];
380 }
381
392 public function dropExtensionIndex( $tableName, $indexName, $sqlPath ) {
393 $this->extensionUpdates[] = [ 'dropIndex', $tableName, $indexName, $sqlPath, true ];
394 }
395
405 public function dropExtensionTable( $tableName, $sqlPath = false ) {
406 $this->extensionUpdates[] = [ 'dropTable', $tableName, $sqlPath, true ];
407 }
408
422 public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName,
423 $sqlPath, $skipBothIndexExistWarning = false
424 ) {
425 $this->extensionUpdates[] = [
426 'renameIndex',
427 $tableName,
428 $oldIndexName,
429 $newIndexName,
430 $skipBothIndexExistWarning,
431 $sqlPath,
432 true
433 ];
434 }
435
446 public function modifyExtensionField( $tableName, $fieldName, $sqlPath ) {
447 $this->extensionUpdates[] = [ 'modifyField', $tableName, $fieldName, $sqlPath, true ];
448 }
449
459 public function modifyExtensionTable( $tableName, $sqlPath ) {
460 $this->extensionUpdates[] = [ 'modifyTable', $tableName, $sqlPath, true ];
461 }
462
469 public function tableExists( $tableName ) {
470 return ( $this->db->tableExists( $tableName, __METHOD__ ) );
471 }
472
480 public function fieldExists( $tableName, $fieldName ) {
481 return ( $this->db->fieldExists( $tableName, $fieldName, __METHOD__ ) );
482 }
483
493 public function addPostDatabaseUpdateMaintenance( $class ) {
494 $this->postDatabaseUpdateMaintenance[] = $class;
495 }
496
502 protected function getExtensionUpdates() {
504 }
505
514
520 private function writeSchemaUpdateFile() {
522 $this->updatesSkipped = [];
523
524 foreach ( $updates as [ $func, $args, $origParams ] ) {
525 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable
526 $func( ...$args );
527 flush();
528 $this->updatesSkipped[] = $origParams;
529 }
530 }
531
542 public function getSchemaVars() {
543 return []; // DB-type specific
544 }
545
551 public function doUpdates( array $what = [ 'core', 'extensions', 'stats' ] ) {
552 $this->db->setSchemaVars( $this->getSchemaVars() );
553
554 $what = array_fill_keys( $what, true );
555 $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
556 if ( isset( $what['core'] ) ) {
557 $this->doCollationUpdate();
558 $this->runUpdates( $this->getCoreUpdateList(), false );
559 }
560 if ( isset( $what['extensions'] ) ) {
561 $this->loadExtensionSchemaUpdates();
562 $this->runUpdates( $this->getExtensionUpdates(), true );
563 $this->runUpdates( $this->extensionUpdatesWithVirtualDomains, true, true );
564 }
565
566 if ( isset( $what['stats'] ) ) {
567 $this->checkStats();
568 }
569
570 if ( $this->fileHandle ) {
571 $this->skipSchema = false;
572 $this->writeSchemaUpdateFile();
573 }
574 }
575
583 private function runUpdates( array $updates, $passSelf, $hasVirtualDomain = false ) {
584 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
585 $updatesDone = [];
586 $updatesSkipped = [];
587 foreach ( $updates as $params ) {
588 $origParams = $params;
589 $oldDb = null;
590 $virtualDomain = null;
591 if ( $hasVirtualDomain === true ) {
592 $virtualDomain = array_shift( $params );
593 $oldDb = $this->db;
594 $this->db = $lbFactory->getPrimaryDatabase( $virtualDomain );
595 '@phan-var IMaintainableDatabase $this->db';
596 }
597 $func = array_shift( $params );
598 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
599 $func = [ $this, $func ];
600 } elseif ( $passSelf ) {
601 array_unshift( $params, $this );
602 }
603 $ret = $func( ...$params );
604 if ( $hasVirtualDomain === true && $oldDb ) {
605 $this->db = $oldDb;
606 }
607
608 flush();
609 if ( $ret !== false ) {
610 $updatesDone[] = $origParams;
611 $lbFactory->waitForReplication( [ 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
612 } else {
613 if ( $hasVirtualDomain === true ) {
614 $params = $origParams;
615 $func = array_shift( $params );
616 }
617 $updatesSkipped[] = [ $func, $params, $origParams ];
618 }
619 }
620 $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
621 $this->updates = array_merge( $this->updates, $updatesDone );
622 }
623
630 public function updateRowExists( $key ) {
631 $row = $this->db->newSelectQueryBuilder()
632 ->select( '1 AS X' ) // T67813
633 ->from( 'updatelog' )
634 ->where( [ 'ul_key' => $key ] )
635 ->caller( __METHOD__ )->fetchRow();
636
637 return (bool)$row;
638 }
639
650 public function insertUpdateRow( $key, $val = null ) {
651 $this->db->clearFlag( DBO_DDLMODE );
652 $values = [ 'ul_key' => $key ];
653 if ( $val ) {
654 $values['ul_value'] = $val;
655 }
656 $this->db->newInsertQueryBuilder()
657 ->insertInto( 'updatelog' )
658 ->ignore()
659 ->row( $values )
660 ->caller( __METHOD__ )->execute();
661 $this->db->setFlag( DBO_DDLMODE );
662 }
663
672 protected function doTable( $name ) {
674
675 // Don't bother to check $wgSharedTables if there isn't a shared database
676 // or the user actually also wants to do updates on the shared database.
677 if ( $wgSharedDB === null || $this->shared ) {
678 return true;
679 }
680
681 if ( in_array( $name, $wgSharedTables ) ) {
682 $this->output( "...skipping update to shared table $name.\n" );
683 return false;
684 }
685
686 return true;
687 }
688
696 abstract protected function getCoreUpdateList();
697
705 protected function copyFile( $filename ) {
706 $this->db->sourceFile(
707 $filename,
708 null,
709 null,
710 __METHOD__,
711 function ( $line ) {
712 return $this->appendLine( $line );
713 }
714 );
715 }
716
728 protected function appendLine( $line ) {
729 $line = rtrim( $line ) . ";\n";
730 if ( fwrite( $this->fileHandle, $line ) === false ) {
731 throw new RuntimeException( "trouble writing file" );
732 }
733
734 return false;
735 }
736
748 protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
749 $msg ??= "Applying $path patch";
750 if ( $this->skipSchema ) {
751 $this->output( "...skipping schema change ($msg).\n" );
752
753 return false;
754 }
755
756 $this->output( "{$msg}..." );
757
758 if ( !$isFullPath ) {
759 $path = $this->patchPath( $this->db, $path );
760 }
761 if ( $this->fileHandle !== null ) {
762 $this->copyFile( $path );
763 } else {
764 $this->db->sourceFile( $path );
765 }
766 $this->output( "done.\n" );
767
768 return true;
769 }
770
779 public function patchPath( IDatabase $db, $patch ) {
780 $baseDir = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::BaseDirectory );
781
782 $dbType = $db->getType();
783 if ( file_exists( "$baseDir/maintenance/$dbType/archives/$patch" ) ) {
784 return "$baseDir/maintenance/$dbType/archives/$patch";
785 }
786
787 return "$baseDir/maintenance/archives/$patch";
788 }
789
801 protected function addTable( $name, $patch, $fullpath = false ) {
802 if ( !$this->doTable( $name ) ) {
803 return true;
804 }
805
806 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
807 $this->output( "...$name table already exists.\n" );
808 return true;
809 }
810
811 return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
812 }
813
826 protected function addField( $table, $field, $patch, $fullpath = false ) {
827 if ( !$this->doTable( $table ) ) {
828 return true;
829 }
830
831 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
832 $this->output( "...$table table does not exist, skipping new field patch.\n" );
833 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
834 $this->output( "...have $field field in $table table.\n" );
835 } else {
836 return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
837 }
838
839 return true;
840 }
841
854 protected function addIndex( $table, $index, $patch, $fullpath = false ) {
855 if ( !$this->doTable( $table ) ) {
856 return true;
857 }
858
859 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
860 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
861 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
862 $this->output( "...index $index already set on $table table.\n" );
863 } else {
864 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
865 }
866
867 return true;
868 }
869
882 protected function dropField( $table, $field, $patch, $fullpath = false ) {
883 if ( !$this->doTable( $table ) ) {
884 return true;
885 }
886
887 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
888 return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
889 }
890
891 $this->output( "...$table table does not contain $field field.\n" );
892 return true;
893 }
894
907 protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
908 if ( !$this->doTable( $table ) ) {
909 return true;
910 }
911
912 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
913 return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
914 }
915
916 $this->output( "...$index key doesn't exist.\n" );
917 return true;
918 }
919
934 protected function renameIndex( $table, $oldIndex, $newIndex,
935 $skipBothIndexExistWarning, $patch, $fullpath = false
936 ) {
937 if ( !$this->doTable( $table ) ) {
938 return true;
939 }
940
941 // First requirement: the table must exist
942 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
943 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
944
945 return true;
946 }
947
948 // Second requirement: the new index must be missing
949 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
950 $this->output( "...index $newIndex already set on $table table.\n" );
951 if ( !$skipBothIndexExistWarning &&
952 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
953 ) {
954 $this->output( "...WARNING: $oldIndex still exists, despite it has " .
955 "been renamed into $newIndex (which also exists).\n" .
956 " $oldIndex should be manually removed if not needed anymore.\n" );
957 }
958
959 return true;
960 }
961
962 // Third requirement: the old index must exist
963 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
964 $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
965
966 return true;
967 }
968
969 // Requirements have been satisfied, the patch can be applied
970 return $this->applyPatch(
971 $patch,
972 $fullpath,
973 "Renaming index $oldIndex into $newIndex to table $table"
974 );
975 }
976
991 protected function dropTable( $table, $patch = false, $fullpath = false ) {
992 if ( !$this->doTable( $table ) ) {
993 return true;
994 }
995
996 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
997 $msg = "Dropping table $table";
998
999 if ( $patch === false ) {
1000 $this->output( "$msg ..." );
1001 $this->db->dropTable( $table, __METHOD__ );
1002 $this->output( "done.\n" );
1003 } else {
1004 return $this->applyPatch( $patch, $fullpath, $msg );
1005 }
1006 } else {
1007 $this->output( "...$table doesn't exist.\n" );
1008 }
1009
1010 return true;
1011 }
1012
1027 protected function modifyField( $table, $field, $patch, $fullpath = false ) {
1028 if ( !$this->doTable( $table ) ) {
1029 return true;
1030 }
1031
1032 $updateKey = "$table-$field-$patch";
1033 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1034 $this->output( "...$table table does not exist, skipping modify field patch.\n" );
1035 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1036 $this->output( "...$field field does not exist in $table table, " .
1037 "skipping modify field patch.\n" );
1038 } elseif ( $this->updateRowExists( $updateKey ) ) {
1039 $this->output( "...$field in table $table already modified by patch $patch.\n" );
1040 } else {
1041 $apply = $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
1042 if ( $apply ) {
1043 $this->insertUpdateRow( $updateKey );
1044 }
1045 return $apply;
1046 }
1047 return true;
1048 }
1049
1064 protected function modifyTable( $table, $patch, $fullpath = false ) {
1065 if ( !$this->doTable( $table ) ) {
1066 return true;
1067 }
1068
1069 $updateKey = "$table-$patch";
1070 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1071 $this->output( "...$table table does not exist, skipping modify table patch.\n" );
1072 } elseif ( $this->updateRowExists( $updateKey ) ) {
1073 $this->output( "...table $table already modified by patch $patch.\n" );
1074 } else {
1075 $apply = $this->applyPatch( $patch, $fullpath, "Modifying table $table" );
1076 if ( $apply ) {
1077 $this->insertUpdateRow( $updateKey );
1078 }
1079 return $apply;
1080 }
1081 return true;
1082 }
1083
1104 protected function runMaintenance( $class, $script ) {
1105 $this->output( "Running $script...\n" );
1106 $task = $this->maintenance->runChild( $class );
1107 $ok = $task->execute();
1108 if ( !$ok ) {
1109 throw new RuntimeException( "Execution of $script did not complete successfully." );
1110 }
1111 $this->output( "done.\n" );
1112 }
1113
1119 public function setFileAccess() {
1120 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1121 $zonePath = $repo->getZonePath( 'temp' );
1122 if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
1123 // If the directory was never made, then it will have the right ACLs when it is made
1124 $status = $repo->getBackend()->secure( [
1125 'dir' => $zonePath,
1126 'noAccess' => true,
1127 'noListing' => true
1128 ] );
1129 if ( $status->isOK() ) {
1130 $this->output( "Set the local repo temp zone container to be private.\n" );
1131 } else {
1132 $this->output( "Failed to set the local repo temp zone container to be private.\n" );
1133 }
1134 }
1135 }
1136
1140 public function purgeCache() {
1142 // We can't guarantee that the user will be able to use TRUNCATE,
1143 // but we know that DELETE is available to us
1144 $this->output( "Purging caches..." );
1145
1146 // ObjectCache
1147 $this->db->delete( 'objectcache', '*', __METHOD__ );
1148
1149 // LocalisationCache
1150 if ( $wgLocalisationCacheConf['manualRecache'] ) {
1151 $this->rebuildLocalisationCache();
1152 }
1153
1154 // ResourceLoader: Message cache
1155 $services = MediaWikiServices::getInstance();
1157 $services->getMainWANObjectCache()
1158 );
1159
1160 // ResourceLoader: File-dependency cache
1161 $this->db->delete( 'module_deps', '*', __METHOD__ );
1162 $this->output( "done.\n" );
1163 }
1164
1168 protected function checkStats() {
1169 $this->output( "...site_stats is populated..." );
1170 $row = $this->db->newSelectQueryBuilder()
1171 ->select( '*' )
1172 ->from( 'site_stats' )
1173 ->where( [ 'ss_row_id' => 1 ] )
1174 ->caller( __METHOD__ )->fetchRow();
1175 if ( $row === false ) {
1176 $this->output( "data is missing! rebuilding...\n" );
1177 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1178 $this->output( "missing ss_total_pages, rebuilding...\n" );
1179 } else {
1180 $this->output( "done.\n" );
1181
1182 return;
1183 }
1184 SiteStatsInit::doAllAndCommit( $this->db );
1185 }
1186
1187 # Common updater functions
1188
1192 protected function doCollationUpdate() {
1193 global $wgCategoryCollation;
1194 if ( $this->updateRowExists( 'UpdateCollation::' . $wgCategoryCollation ) ) {
1195 $this->output( "...collations up-to-date.\n" );
1196 return;
1197 }
1198 $this->output( "Updating category collations...\n" );
1199 $task = $this->maintenance->runChild( UpdateCollation::class );
1200 $ok = $task->execute();
1201 if ( $ok !== false ) {
1202 $this->output( "...done.\n" );
1203 $this->insertUpdateRow( 'UpdateCollation::' . $wgCategoryCollation );
1204 }
1205 }
1206
1207 protected function doConvertDjvuMetadata() {
1208 if ( $this->updateRowExists( 'ConvertDjvuMetadata' ) ) {
1209 return;
1210 }
1211 $this->output( "Converting djvu metadata..." );
1212 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1213 '@phan-var RefreshImageMetadata $task';
1214 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1215 'force' => true,
1216 'mediatype' => 'OFFICE',
1217 'mime' => 'image/*',
1218 'batch-size' => 1,
1219 'sleep' => 1
1220 ] );
1221 $ok = $task->execute();
1222 if ( $ok !== false ) {
1223 $this->output( "...done.\n" );
1224 $this->insertUpdateRow( 'ConvertDjvuMetadata' );
1225 }
1226 }
1227
1231 protected function rebuildLocalisationCache() {
1235 $cl = $this->maintenance->runChild(
1236 RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
1237 );
1238 '@phan-var RebuildLocalisationCache $cl';
1239 $this->output( "Rebuilding localisation cache...\n" );
1240 $cl->setForce();
1241 $cl->execute();
1242 $this->output( "done.\n" );
1243 }
1244
1245 protected function migrateTemplatelinks() {
1246 if ( $this->updateRowExists( MigrateLinksTable::class . 'templatelinks' ) ) {
1247 $this->output( "...templatelinks table has already been migrated.\n" );
1248 return;
1249 }
1253 $task = $this->maintenance->runChild(
1254 MigrateLinksTable::class, 'migrateLinksTable.php'
1255 );
1256 '@phan-var MigrateLinksTable $task';
1257 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1258 'force' => true,
1259 'table' => 'templatelinks'
1260 ] );
1261 $this->output( "Running migrateLinksTable.php on templatelinks...\n" );
1262 $task->execute();
1263 $this->output( "done.\n" );
1264 }
1265
1279 protected function ifTableNotExists( $table, $func, ...$params ) {
1280 // Handle $passSelf from runUpdates().
1281 $passSelf = false;
1282 if ( $table === $this ) {
1283 $passSelf = true;
1284 $table = $func;
1285 $func = array_shift( $params );
1286 }
1287
1288 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1289 return null;
1290 }
1291
1292 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1293 $func = [ $this, $func ];
1294 } elseif ( $passSelf ) {
1295 array_unshift( $params, $this );
1296 }
1297
1298 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1299 return $func( ...$params );
1300 }
1301
1316 protected function ifFieldExists( $table, $field, $func, ...$params ) {
1317 // Handle $passSelf from runUpdates().
1318 $passSelf = false;
1319 if ( $table === $this ) {
1320 $passSelf = true;
1321 $table = $field;
1322 $field = $func;
1323 $func = array_shift( $params );
1324 }
1325
1326 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1327 !$this->db->fieldExists( $table, $field, __METHOD__ )
1328 ) {
1329 return null;
1330 }
1331
1332 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1333 $func = [ $this, $func ];
1334 } elseif ( $passSelf ) {
1335 array_unshift( $params, $this );
1336 }
1337
1338 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1339 return $func( ...$params );
1340 }
1341
1342}
1343
1347class_alias( DatabaseUpdater::class, 'DatabaseUpdater' );
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.
const DBO_DDLMODE
Definition defines.php:16