MediaWiki 1.42.0
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 $virtualDb = $lbFactory->getPrimaryDatabase( $virtualDomain );
595 '@phan-var IMaintainableDatabase $virtualDb';
596 $this->maintenance->setDB( $virtualDb );
597 $this->db = $virtualDb;
598 }
599 $func = array_shift( $params );
600 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
601 $func = [ $this, $func ];
602 } elseif ( $passSelf ) {
603 array_unshift( $params, $this );
604 }
605 $ret = $func( ...$params );
606 if ( $hasVirtualDomain === true && $oldDb ) {
607 $this->db = $oldDb;
608 $this->maintenance->setDB( $oldDb );
609 }
610
611 flush();
612 if ( $ret !== false ) {
613 $updatesDone[] = $origParams;
614 $lbFactory->waitForReplication( [ 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
615 } else {
616 if ( $hasVirtualDomain === true ) {
617 $params = $origParams;
618 $func = array_shift( $params );
619 }
620 $updatesSkipped[] = [ $func, $params, $origParams ];
621 }
622 }
623 $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
624 $this->updates = array_merge( $this->updates, $updatesDone );
625 }
626
633 public function updateRowExists( $key ) {
634 $row = $this->db->newSelectQueryBuilder()
635 ->select( '1 AS X' ) // T67813
636 ->from( 'updatelog' )
637 ->where( [ 'ul_key' => $key ] )
638 ->caller( __METHOD__ )->fetchRow();
639
640 return (bool)$row;
641 }
642
653 public function insertUpdateRow( $key, $val = null ) {
654 $this->db->clearFlag( DBO_DDLMODE );
655 $values = [ 'ul_key' => $key ];
656 if ( $val ) {
657 $values['ul_value'] = $val;
658 }
659 $this->db->newInsertQueryBuilder()
660 ->insertInto( 'updatelog' )
661 ->ignore()
662 ->row( $values )
663 ->caller( __METHOD__ )->execute();
664 $this->db->setFlag( DBO_DDLMODE );
665 }
666
675 protected function doTable( $name ) {
677
678 // Don't bother to check $wgSharedTables if there isn't a shared database
679 // or the user actually also wants to do updates on the shared database.
680 if ( $wgSharedDB === null || $this->shared ) {
681 return true;
682 }
683
684 if ( in_array( $name, $wgSharedTables ) ) {
685 $this->output( "...skipping update to shared table $name.\n" );
686 return false;
687 }
688
689 return true;
690 }
691
699 abstract protected function getCoreUpdateList();
700
708 protected function copyFile( $filename ) {
709 $this->db->sourceFile(
710 $filename,
711 null,
712 null,
713 __METHOD__,
714 function ( $line ) {
715 return $this->appendLine( $line );
716 }
717 );
718 }
719
731 protected function appendLine( $line ) {
732 $line = rtrim( $line ) . ";\n";
733 if ( fwrite( $this->fileHandle, $line ) === false ) {
734 throw new RuntimeException( "trouble writing file" );
735 }
736
737 return false;
738 }
739
751 protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
752 $msg ??= "Applying $path patch";
753 if ( $this->skipSchema ) {
754 $this->output( "...skipping schema change ($msg).\n" );
755
756 return false;
757 }
758
759 $this->output( "{$msg}..." );
760
761 if ( !$isFullPath ) {
762 $path = $this->patchPath( $this->db, $path );
763 }
764 if ( $this->fileHandle !== null ) {
765 $this->copyFile( $path );
766 } else {
767 $this->db->sourceFile( $path );
768 }
769 $this->output( "done.\n" );
770
771 return true;
772 }
773
782 public function patchPath( IDatabase $db, $patch ) {
783 $baseDir = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::BaseDirectory );
784
785 $dbType = $db->getType();
786 if ( file_exists( "$baseDir/maintenance/$dbType/archives/$patch" ) ) {
787 return "$baseDir/maintenance/$dbType/archives/$patch";
788 }
789
790 return "$baseDir/maintenance/archives/$patch";
791 }
792
804 protected function addTable( $name, $patch, $fullpath = false ) {
805 if ( !$this->doTable( $name ) ) {
806 return true;
807 }
808
809 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
810 $this->output( "...$name table already exists.\n" );
811 return true;
812 }
813
814 return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
815 }
816
829 protected function addField( $table, $field, $patch, $fullpath = false ) {
830 if ( !$this->doTable( $table ) ) {
831 return true;
832 }
833
834 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
835 $this->output( "...$table table does not exist, skipping new field patch.\n" );
836 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
837 $this->output( "...have $field field in $table table.\n" );
838 } else {
839 return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
840 }
841
842 return true;
843 }
844
857 protected function addIndex( $table, $index, $patch, $fullpath = false ) {
858 if ( !$this->doTable( $table ) ) {
859 return true;
860 }
861
862 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
863 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
864 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
865 $this->output( "...index $index already set on $table table.\n" );
866 } else {
867 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
868 }
869
870 return true;
871 }
872
885 protected function dropField( $table, $field, $patch, $fullpath = false ) {
886 if ( !$this->doTable( $table ) ) {
887 return true;
888 }
889
890 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
891 return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
892 }
893
894 $this->output( "...$table table does not contain $field field.\n" );
895 return true;
896 }
897
910 protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
911 if ( !$this->doTable( $table ) ) {
912 return true;
913 }
914
915 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
916 return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
917 }
918
919 $this->output( "...$index key doesn't exist.\n" );
920 return true;
921 }
922
937 protected function renameIndex( $table, $oldIndex, $newIndex,
938 $skipBothIndexExistWarning, $patch, $fullpath = false
939 ) {
940 if ( !$this->doTable( $table ) ) {
941 return true;
942 }
943
944 // First requirement: the table must exist
945 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
946 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
947
948 return true;
949 }
950
951 // Second requirement: the new index must be missing
952 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
953 $this->output( "...index $newIndex already set on $table table.\n" );
954 if ( !$skipBothIndexExistWarning &&
955 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
956 ) {
957 $this->output( "...WARNING: $oldIndex still exists, despite it has " .
958 "been renamed into $newIndex (which also exists).\n" .
959 " $oldIndex should be manually removed if not needed anymore.\n" );
960 }
961
962 return true;
963 }
964
965 // Third requirement: the old index must exist
966 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
967 $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
968
969 return true;
970 }
971
972 // Requirements have been satisfied, the patch can be applied
973 return $this->applyPatch(
974 $patch,
975 $fullpath,
976 "Renaming index $oldIndex into $newIndex to table $table"
977 );
978 }
979
994 protected function dropTable( $table, $patch = false, $fullpath = false ) {
995 if ( !$this->doTable( $table ) ) {
996 return true;
997 }
998
999 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1000 $msg = "Dropping table $table";
1001
1002 if ( $patch === false ) {
1003 $this->output( "$msg ..." );
1004 $this->db->dropTable( $table, __METHOD__ );
1005 $this->output( "done.\n" );
1006 } else {
1007 return $this->applyPatch( $patch, $fullpath, $msg );
1008 }
1009 } else {
1010 $this->output( "...$table doesn't exist.\n" );
1011 }
1012
1013 return true;
1014 }
1015
1030 protected function modifyField( $table, $field, $patch, $fullpath = false ) {
1031 if ( !$this->doTable( $table ) ) {
1032 return true;
1033 }
1034
1035 $updateKey = "$table-$field-$patch";
1036 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1037 $this->output( "...$table table does not exist, skipping modify field patch.\n" );
1038 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1039 $this->output( "...$field field does not exist in $table table, " .
1040 "skipping modify field patch.\n" );
1041 } elseif ( $this->updateRowExists( $updateKey ) ) {
1042 $this->output( "...$field in table $table already modified by patch $patch.\n" );
1043 } else {
1044 $apply = $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
1045 if ( $apply ) {
1046 $this->insertUpdateRow( $updateKey );
1047 }
1048 return $apply;
1049 }
1050 return true;
1051 }
1052
1067 protected function modifyTable( $table, $patch, $fullpath = false ) {
1068 if ( !$this->doTable( $table ) ) {
1069 return true;
1070 }
1071
1072 $updateKey = "$table-$patch";
1073 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1074 $this->output( "...$table table does not exist, skipping modify table patch.\n" );
1075 } elseif ( $this->updateRowExists( $updateKey ) ) {
1076 $this->output( "...table $table already modified by patch $patch.\n" );
1077 } else {
1078 $apply = $this->applyPatch( $patch, $fullpath, "Modifying table $table" );
1079 if ( $apply ) {
1080 $this->insertUpdateRow( $updateKey );
1081 }
1082 return $apply;
1083 }
1084 return true;
1085 }
1086
1107 protected function runMaintenance( $class, $script ) {
1108 $this->output( "Running $script...\n" );
1109 $task = $this->maintenance->runChild( $class );
1110 $ok = $task->execute();
1111 if ( !$ok ) {
1112 throw new RuntimeException( "Execution of $script did not complete successfully." );
1113 }
1114 $this->output( "done.\n" );
1115 }
1116
1122 public function setFileAccess() {
1123 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1124 $zonePath = $repo->getZonePath( 'temp' );
1125 if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
1126 // If the directory was never made, then it will have the right ACLs when it is made
1127 $status = $repo->getBackend()->secure( [
1128 'dir' => $zonePath,
1129 'noAccess' => true,
1130 'noListing' => true
1131 ] );
1132 if ( $status->isOK() ) {
1133 $this->output( "Set the local repo temp zone container to be private.\n" );
1134 } else {
1135 $this->output( "Failed to set the local repo temp zone container to be private.\n" );
1136 }
1137 }
1138 }
1139
1143 public function purgeCache() {
1145 // We can't guarantee that the user will be able to use TRUNCATE,
1146 // but we know that DELETE is available to us
1147 $this->output( "Purging caches..." );
1148
1149 // ObjectCache
1150 $this->db->delete( 'objectcache', '*', __METHOD__ );
1151
1152 // LocalisationCache
1153 if ( $wgLocalisationCacheConf['manualRecache'] ) {
1154 $this->rebuildLocalisationCache();
1155 }
1156
1157 // ResourceLoader: Message cache
1158 $services = MediaWikiServices::getInstance();
1160 $services->getMainWANObjectCache()
1161 );
1162
1163 // ResourceLoader: File-dependency cache
1164 $this->db->delete( 'module_deps', '*', __METHOD__ );
1165 $this->output( "done.\n" );
1166 }
1167
1171 protected function checkStats() {
1172 $this->output( "...site_stats is populated..." );
1173 $row = $this->db->newSelectQueryBuilder()
1174 ->select( '*' )
1175 ->from( 'site_stats' )
1176 ->where( [ 'ss_row_id' => 1 ] )
1177 ->caller( __METHOD__ )->fetchRow();
1178 if ( $row === false ) {
1179 $this->output( "data is missing! rebuilding...\n" );
1180 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1181 $this->output( "missing ss_total_pages, rebuilding...\n" );
1182 } else {
1183 $this->output( "done.\n" );
1184
1185 return;
1186 }
1187 SiteStatsInit::doAllAndCommit( $this->db );
1188 }
1189
1190 # Common updater functions
1191
1195 protected function doCollationUpdate() {
1196 global $wgCategoryCollation;
1197 if ( $this->updateRowExists( 'UpdateCollation::' . $wgCategoryCollation ) ) {
1198 $this->output( "...collations up-to-date.\n" );
1199 return;
1200 }
1201 $this->output( "Updating category collations...\n" );
1202 $task = $this->maintenance->runChild( UpdateCollation::class );
1203 $ok = $task->execute();
1204 if ( $ok !== false ) {
1205 $this->output( "...done.\n" );
1206 $this->insertUpdateRow( 'UpdateCollation::' . $wgCategoryCollation );
1207 }
1208 }
1209
1210 protected function doConvertDjvuMetadata() {
1211 if ( $this->updateRowExists( 'ConvertDjvuMetadata' ) ) {
1212 return;
1213 }
1214 $this->output( "Converting djvu metadata..." );
1215 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1216 '@phan-var RefreshImageMetadata $task';
1217 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1218 'force' => true,
1219 'mediatype' => 'OFFICE',
1220 'mime' => 'image/*',
1221 'batch-size' => 1,
1222 'sleep' => 1
1223 ] );
1224 $ok = $task->execute();
1225 if ( $ok !== false ) {
1226 $this->output( "...done.\n" );
1227 $this->insertUpdateRow( 'ConvertDjvuMetadata' );
1228 }
1229 }
1230
1234 protected function rebuildLocalisationCache() {
1238 $cl = $this->maintenance->runChild(
1239 RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
1240 );
1241 '@phan-var RebuildLocalisationCache $cl';
1242 $this->output( "Rebuilding localisation cache...\n" );
1243 $cl->setForce();
1244 $cl->execute();
1245 $this->output( "done.\n" );
1246 }
1247
1248 protected function migrateTemplatelinks() {
1249 if ( $this->updateRowExists( MigrateLinksTable::class . 'templatelinks' ) ) {
1250 $this->output( "...templatelinks table has already been migrated.\n" );
1251 return;
1252 }
1256 $task = $this->maintenance->runChild(
1257 MigrateLinksTable::class, 'migrateLinksTable.php'
1258 );
1259 '@phan-var MigrateLinksTable $task';
1260 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1261 'force' => true,
1262 'table' => 'templatelinks'
1263 ] );
1264 $this->output( "Running migrateLinksTable.php on templatelinks...\n" );
1265 $task->execute();
1266 $this->output( "done.\n" );
1267 }
1268
1282 protected function ifTableNotExists( $table, $func, ...$params ) {
1283 // Handle $passSelf from runUpdates().
1284 $passSelf = false;
1285 if ( $table === $this ) {
1286 $passSelf = true;
1287 $table = $func;
1288 $func = array_shift( $params );
1289 }
1290
1291 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1292 return null;
1293 }
1294
1295 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1296 $func = [ $this, $func ];
1297 } elseif ( $passSelf ) {
1298 array_unshift( $params, $this );
1299 }
1300
1301 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1302 return $func( ...$params );
1303 }
1304
1319 protected function ifFieldExists( $table, $field, $func, ...$params ) {
1320 // Handle $passSelf from runUpdates().
1321 $passSelf = false;
1322 if ( $table === $this ) {
1323 $passSelf = true;
1324 $table = $field;
1325 $field = $func;
1326 $func = array_shift( $params );
1327 }
1328
1329 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1330 !$this->db->fieldExists( $table, $field, __METHOD__ )
1331 ) {
1332 return null;
1333 }
1334
1335 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1336 $func = [ $this, $func ];
1337 } elseif ( $passSelf ) {
1338 array_unshift( $params, $this );
1339 }
1340
1341 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1342 return $func( ...$params );
1343 }
1344
1345}
1346
1348class_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.
const DBO_DDLMODE
Definition defines.php:16