MediaWiki REL1_39
DatabaseUpdater.php
Go to the documentation of this file.
1<?php
32
33require_once __DIR__ . '/../../maintenance/Maintenance.php';
34
42abstract class DatabaseUpdater {
43 public const REPLICATION_WAIT_TIMEOUT = 300;
44
50 protected $updates = [];
51
57 protected $updatesSkipped = [];
58
63 protected $extensionUpdates = [];
64
70 protected $db;
71
75 protected $maintenance;
76
77 protected $shared = false;
78
81
86 protected $postDatabaseUpdateMaintenance = [
87 DeleteDefaultMessages::class,
88 PopulateRevisionLength::class,
89 PopulateRevisionSha1::class,
90 PopulateImageSha1::class,
91 FixExtLinksProtocolRelative::class,
92 PopulateFilearchiveSha1::class,
93 PopulateBacklinkNamespace::class,
94 FixDefaultJsonContentPages::class,
95 CleanupEmptyCategories::class,
96 AddRFCandPMIDInterwiki::class,
97 PopulatePPSortKey::class,
98 PopulateIpChanges::class,
99 RefreshExternallinksIndex::class,
100 ];
101
107 protected $fileHandle = null;
108
114 protected $skipSchema = false;
115
122 protected function __construct(
124 $shared,
125 Maintenance $maintenance = null
126 ) {
127 $this->db = $db;
128 $this->db->setFlag( DBO_DDLMODE );
129 $this->shared = $shared;
130 if ( $maintenance ) {
131 $this->maintenance = $maintenance;
132 $this->fileHandle = $maintenance->fileHandle;
133 } else {
134 $this->maintenance = new FakeMaintenance;
135 }
136 $this->maintenance->setDB( $db );
137 }
138
142 private function loadExtensionSchemaUpdates() {
143 $hookContainer = $this->loadExtensions();
144 ( new HookRunner( $hookContainer ) )->onLoadExtensionSchemaUpdates( $this );
145 }
146
153 private function loadExtensions() {
154 if ( $this->autoExtensionHookContainer ) {
155 // Already injected by installer
156 return $this->autoExtensionHookContainer;
157 }
158 if ( defined( 'MW_EXTENSIONS_LOADED' ) ) {
159 throw new Exception( __METHOD__ .
160 ' apparently called from installer but no hook container was injected' );
161 }
162 if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
163 // Running under update.php: just use global locator
164 return MediaWikiServices::getInstance()->getHookContainer();
165 }
167
168 $registry = ExtensionRegistry::getInstance();
169 $queue = $registry->getQueue();
170 // Don't accidentally load extensions in the future
171 $registry->clearQueue();
172
173 // Read extension.json files
174 $extInfo = $registry->readFromQueue( $queue );
175
176 // Merge extension attribute hooks with hooks defined by a .php
177 // registration file included from LocalSettings.php
178 $legacySchemaHooks = $extInfo['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ?? [];
179 if ( $vars && isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
180 $legacySchemaHooks = array_merge( $legacySchemaHooks, $vars['wgHooks']['LoadExtensionSchemaUpdates'] );
181 }
182
183 // Register classes defined by extensions that are loaded by including of a file that
184 // updates global variables, rather than having an extension.json manifest.
185 if ( $vars && isset( $vars['wgAutoloadClasses'] ) ) {
186 AutoLoader::registerClasses( $vars['wgAutoloadClasses'] );
187 }
188
189 // Register class definitions from extension.json files
190 if ( !isset( $extInfo['autoloaderPaths'] )
191 || !isset( $extInfo['autoloaderClasses'] )
192 || !isset( $extInfo['autoloaderNS'] )
193 ) {
194 // NOTE: protect against changes to the structure of $extInfo. It's volatile, and this usage easy to miss.
195 throw new LogicException( 'Missing autoloader keys from extracted extension info' );
196 }
197 AutoLoader::loadFiles( $extInfo['autoloaderPaths'] );
198 AutoLoader::registerClasses( $extInfo['autoloaderClasses'] );
199 AutoLoader::registerNamespaces( $extInfo['autoloaderNS'] );
200
201 return new HookContainer(
203 [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
204 $extInfo['attributes']['Hooks'] ?? [],
205 $extInfo['attributes']['DeprecatedHooks'] ?? []
206 ),
207 MediaWikiServices::getInstance()->getObjectFactory()
208 );
209 }
210
219 public static function newForDB(
221 $shared = false,
222 Maintenance $maintenance = null
223 ) {
224 $type = $db->getType();
225 if ( in_array( $type, Installer::getDBTypes() ) ) {
226 $class = ucfirst( $type ) . 'Updater';
227
228 return new $class( $db, $shared, $maintenance );
229 } else {
230 throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' );
231 }
232 }
233
241 public function setAutoExtensionHookContainer( HookContainer $hookContainer ) {
242 $this->autoExtensionHookContainer = $hookContainer;
243 }
244
250 public function getDB() {
251 return $this->db;
252 }
253
260 public function output( $str ) {
261 if ( $this->maintenance->isQuiet() ) {
262 return;
263 }
264 global $wgCommandLineMode;
265 if ( !$wgCommandLineMode ) {
266 $str = htmlspecialchars( $str );
267 }
268 echo $str;
269 flush();
270 }
271
284 public function addExtensionUpdate( array $update ) {
285 $this->extensionUpdates[] = $update;
286 }
287
298 public function addExtensionTable( $tableName, $sqlPath ) {
299 $this->extensionUpdates[] = [ 'addTable', $tableName, $sqlPath, true ];
300 }
301
312 public function addExtensionIndex( $tableName, $indexName, $sqlPath ) {
313 $this->extensionUpdates[] = [ 'addIndex', $tableName, $indexName, $sqlPath, true ];
314 }
315
326 public function addExtensionField( $tableName, $columnName, $sqlPath ) {
327 $this->extensionUpdates[] = [ 'addField', $tableName, $columnName, $sqlPath, true ];
328 }
329
340 public function dropExtensionField( $tableName, $columnName, $sqlPath ) {
341 $this->extensionUpdates[] = [ 'dropField', $tableName, $columnName, $sqlPath, true ];
342 }
343
354 public function dropExtensionIndex( $tableName, $indexName, $sqlPath ) {
355 $this->extensionUpdates[] = [ 'dropIndex', $tableName, $indexName, $sqlPath, true ];
356 }
357
367 public function dropExtensionTable( $tableName, $sqlPath = false ) {
368 $this->extensionUpdates[] = [ 'dropTable', $tableName, $sqlPath, true ];
369 }
370
384 public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName,
385 $sqlPath, $skipBothIndexExistWarning = false
386 ) {
387 $this->extensionUpdates[] = [
388 'renameIndex',
389 $tableName,
390 $oldIndexName,
391 $newIndexName,
392 $skipBothIndexExistWarning,
393 $sqlPath,
394 true
395 ];
396 }
397
408 public function modifyExtensionField( $tableName, $fieldName, $sqlPath ) {
409 $this->extensionUpdates[] = [ 'modifyField', $tableName, $fieldName, $sqlPath, true ];
410 }
411
421 public function modifyExtensionTable( $tableName, $sqlPath ) {
422 $this->extensionUpdates[] = [ 'modifyTable', $tableName, $sqlPath, true ];
423 }
424
431 public function tableExists( $tableName ) {
432 return ( $this->db->tableExists( $tableName, __METHOD__ ) );
433 }
434
444 public function addPostDatabaseUpdateMaintenance( $class ) {
445 $this->postDatabaseUpdateMaintenance[] = $class;
446 }
447
453 protected function getExtensionUpdates() {
454 return $this->extensionUpdates;
455 }
456
463 return $this->postDatabaseUpdateMaintenance;
464 }
465
472 private function writeSchemaUpdateFile( array $schemaUpdate = [] ) {
473 $updates = $this->updatesSkipped;
474 $this->updatesSkipped = [];
475
476 foreach ( $updates as [ $func, $args, $origParams ] ) {
477 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable
478 $func( ...$args );
479 flush();
480 $this->updatesSkipped[] = $origParams;
481 }
482 }
483
495 public function getSchemaVars() {
496 return []; // DB-type specific
497 }
498
504 public function doUpdates( array $what = [ 'core', 'extensions', 'stats' ] ) {
505 $this->db->setSchemaVars( $this->getSchemaVars() );
506
507 $what = array_fill_keys( $what, true );
508 $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
509 if ( isset( $what['core'] ) ) {
510 $this->doCollationUpdate();
511 $this->runUpdates( $this->getCoreUpdateList(), false );
512 }
513 if ( isset( $what['extensions'] ) ) {
514 $this->loadExtensionSchemaUpdates();
515 $this->runUpdates( $this->getExtensionUpdates(), true );
516 }
517
518 if ( isset( $what['stats'] ) ) {
519 $this->checkStats();
520 }
521
522 if ( $this->fileHandle ) {
523 $this->skipSchema = false;
524 $this->writeSchemaUpdateFile();
525 }
526 }
527
534 private function runUpdates( array $updates, $passSelf ) {
535 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
536
537 $updatesDone = [];
538 $updatesSkipped = [];
539 foreach ( $updates as $params ) {
540 $origParams = $params;
541 $func = array_shift( $params );
542 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
543 $func = [ $this, $func ];
544 } elseif ( $passSelf ) {
545 array_unshift( $params, $this );
546 }
547 $ret = $func( ...$params );
548 flush();
549 if ( $ret !== false ) {
550 $updatesDone[] = $origParams;
551 $lbFactory->waitForReplication( [ 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
552 } else {
553 $updatesSkipped[] = [ $func, $params, $origParams ];
554 }
555 }
556 $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
557 $this->updates = array_merge( $this->updates, $updatesDone );
558 }
559
567 public function updateRowExists( $key ) {
568 // Return false if the updatelog table does not exist. This can occur if performing schema changes for tables
569 // that are on a virtual database domain.
570 if ( !$this->db->tableExists( 'updatelog', __METHOD__ ) ) {
571 return false;
572 }
573
574 $row = $this->db->selectRow(
575 'updatelog',
576 # T67813
577 '1 AS X',
578 [ 'ul_key' => $key ],
579 __METHOD__
580 );
581 return (bool)$row;
582 }
583
597 public function insertUpdateRow( $key, $val = null ) {
598 // We cannot insert anything to the updatelog table if it does not exist. This can occur for schema changes
599 // on tables that are on a virtual database domain.
600 if ( !$this->db->tableExists( 'updatelog', __METHOD__ ) ) {
601 return;
602 }
603
604 $this->db->clearFlag( DBO_DDLMODE );
605 $values = [ 'ul_key' => $key ];
606 if ( $val && $this->canUseNewUpdatelog() ) {
607 $values['ul_value'] = $val;
608 }
609 $this->db->insert( 'updatelog', $values, __METHOD__, [ 'IGNORE' ] );
610 $this->db->setFlag( DBO_DDLMODE );
611 }
612
621 protected function canUseNewUpdatelog() {
622 return $this->db->tableExists( 'updatelog', __METHOD__ ) &&
623 $this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ );
624 }
625
634 protected function doTable( $name ) {
636
637 // Don't bother to check $wgSharedTables if there isn't a shared database
638 // or the user actually also wants to do updates on the shared database.
639 if ( $wgSharedDB === null || $this->shared ) {
640 return true;
641 }
642
643 if ( in_array( $name, $wgSharedTables ) ) {
644 $this->output( "...skipping update to shared table $name.\n" );
645 return false;
646 } else {
647 return true;
648 }
649 }
650
658 abstract protected function getCoreUpdateList();
659
667 protected function copyFile( $filename ) {
668 $this->db->sourceFile(
669 $filename,
670 null,
671 null,
672 __METHOD__,
673 function ( $line ) {
674 return $this->appendLine( $line );
675 }
676 );
677 }
678
691 protected function appendLine( $line ) {
692 $line = rtrim( $line ) . ";\n";
693 if ( fwrite( $this->fileHandle, $line ) === false ) {
694 throw new MWException( "trouble writing file" );
695 }
696
697 return false;
698 }
699
711 protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
712 if ( $msg === null ) {
713 $msg = "Applying $path patch";
714 }
715 if ( $this->skipSchema ) {
716 $this->output( "...skipping schema change ($msg).\n" );
717
718 return false;
719 }
720
721 $this->output( "{$msg}..." );
722
723 if ( !$isFullPath ) {
724 $path = $this->patchPath( $this->db, $path );
725 }
726 if ( $this->fileHandle !== null ) {
727 $this->copyFile( $path );
728 } else {
729 $this->db->sourceFile( $path );
730 }
731 $this->output( "done.\n" );
732
733 return true;
734 }
735
744 public function patchPath( IDatabase $db, $patch ) {
745 $baseDir = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::BaseDirectory );
746
747 $dbType = $db->getType();
748 if ( file_exists( "$baseDir/maintenance/$dbType/archives/$patch" ) ) {
749 return "$baseDir/maintenance/$dbType/archives/$patch";
750 } else {
751 return "$baseDir/maintenance/archives/$patch";
752 }
753 }
754
766 protected function addTable( $name, $patch, $fullpath = false ) {
767 if ( !$this->doTable( $name ) ) {
768 return true;
769 }
770
771 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
772 $this->output( "...$name table already exists.\n" );
773 } else {
774 return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
775 }
776
777 return true;
778 }
779
792 protected function addField( $table, $field, $patch, $fullpath = false ) {
793 if ( !$this->doTable( $table ) ) {
794 return true;
795 }
796
797 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
798 $this->output( "...$table table does not exist, skipping new field patch.\n" );
799 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
800 $this->output( "...have $field field in $table table.\n" );
801 } else {
802 return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
803 }
804
805 return true;
806 }
807
820 protected function addIndex( $table, $index, $patch, $fullpath = false ) {
821 if ( !$this->doTable( $table ) ) {
822 return true;
823 }
824
825 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
826 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
827 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
828 $this->output( "...index $index already set on $table table.\n" );
829 } else {
830 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
831 }
832
833 return true;
834 }
835
848 protected function dropField( $table, $field, $patch, $fullpath = false ) {
849 if ( !$this->doTable( $table ) ) {
850 return true;
851 }
852
853 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
854 return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
855 } else {
856 $this->output( "...$table table does not contain $field field.\n" );
857 }
858
859 return true;
860 }
861
874 protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
875 if ( !$this->doTable( $table ) ) {
876 return true;
877 }
878
879 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
880 return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
881 } else {
882 $this->output( "...$index key doesn't exist.\n" );
883 }
884
885 return true;
886 }
887
904 protected function renameIndex( $table, $oldIndex, $newIndex,
905 $skipBothIndexExistWarning, $patch, $fullpath = false
906 ) {
907 if ( !$this->doTable( $table ) ) {
908 return true;
909 }
910
911 // First requirement: the table must exist
912 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
913 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
914
915 return true;
916 }
917
918 // Second requirement: the new index must be missing
919 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
920 $this->output( "...index $newIndex already set on $table table.\n" );
921 if ( !$skipBothIndexExistWarning &&
922 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
923 ) {
924 $this->output( "...WARNING: $oldIndex still exists, despite it has " .
925 "been renamed into $newIndex (which also exists).\n" .
926 " $oldIndex should be manually removed if not needed anymore.\n" );
927 }
928
929 return true;
930 }
931
932 // Third requirement: the old index must exist
933 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
934 $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
935
936 return true;
937 }
938
939 // Requirements have been satisfied, patch can be applied
940 return $this->applyPatch(
941 $patch,
942 $fullpath,
943 "Renaming index $oldIndex into $newIndex to table $table"
944 );
945 }
946
961 protected function dropTable( $table, $patch = false, $fullpath = false ) {
962 if ( !$this->doTable( $table ) ) {
963 return true;
964 }
965
966 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
967 $msg = "Dropping table $table";
968
969 if ( $patch === false ) {
970 $this->output( "$msg ..." );
971 $this->db->dropTable( $table, __METHOD__ );
972 $this->output( "done.\n" );
973 } else {
974 return $this->applyPatch( $patch, $fullpath, $msg );
975 }
976 } else {
977 $this->output( "...$table doesn't exist.\n" );
978 }
979
980 return true;
981 }
982
997 protected function modifyField( $table, $field, $patch, $fullpath = false ) {
998 if ( !$this->doTable( $table ) ) {
999 return true;
1000 }
1001
1002 $updateKey = "$table-$field-$patch";
1003 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1004 $this->output( "...$table table does not exist, skipping modify field patch.\n" );
1005 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1006 $this->output( "...$field field does not exist in $table table, " .
1007 "skipping modify field patch.\n" );
1008 } elseif ( $this->updateRowExists( $updateKey ) ) {
1009 $this->output( "...$field in table $table already modified by patch $patch.\n" );
1010 } else {
1011 $apply = $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
1012 if ( $apply ) {
1013 $this->insertUpdateRow( $updateKey );
1014 }
1015 return $apply;
1016 }
1017 return true;
1018 }
1019
1034 protected function modifyTable( $table, $patch, $fullpath = false ) {
1035 if ( !$this->doTable( $table ) ) {
1036 return true;
1037 }
1038
1039 $updateKey = "$table-$patch";
1040 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1041 $this->output( "...$table table does not exist, skipping modify table patch.\n" );
1042 } elseif ( $this->updateRowExists( $updateKey ) ) {
1043 $this->output( "...table $table already modified by patch $patch.\n" );
1044 } else {
1045 $apply = $this->applyPatch( $patch, $fullpath, "Modifying table $table" );
1046 if ( $apply ) {
1047 $this->insertUpdateRow( $updateKey );
1048 }
1049 return $apply;
1050 }
1051 return true;
1052 }
1053
1074 protected function runMaintenance( $class, $unused = '' ) {
1075 $this->output( "Running $class...\n" );
1076 $task = $this->maintenance->runChild( $class );
1077 $ok = $task->execute();
1078 if ( !$ok ) {
1079 throw new RuntimeException( "Execution of $class did not complete successfully." );
1080 }
1081 $this->output( "done.\n" );
1082 }
1083
1089 public function setFileAccess() {
1090 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1091 $zonePath = $repo->getZonePath( 'temp' );
1092 if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
1093 // If the directory was never made, then it will have the right ACLs when it is made
1094 $status = $repo->getBackend()->secure( [
1095 'dir' => $zonePath,
1096 'noAccess' => true,
1097 'noListing' => true
1098 ] );
1099 if ( $status->isOK() ) {
1100 $this->output( "Set the local repo temp zone container to be private.\n" );
1101 } else {
1102 $this->output( "Failed to set the local repo temp zone container to be private.\n" );
1103 }
1104 }
1105 }
1106
1110 public function purgeCache() {
1112 // We can't guarantee that the user will be able to use TRUNCATE,
1113 // but we know that DELETE is available to us
1114 $this->output( "Purging caches..." );
1115
1116 // ObjectCache
1117 $this->db->delete( 'objectcache', '*', __METHOD__ );
1118
1119 // LocalisationCache
1120 if ( $wgLocalisationCacheConf['manualRecache'] ) {
1121 $this->rebuildLocalisationCache();
1122 }
1123
1124 // ResourceLoader: Message cache
1125 $services = MediaWikiServices::getInstance();
1126 $blobStore = new MessageBlobStore(
1127 $services->getResourceLoader(),
1128 null,
1129 $services->getMainWANObjectCache()
1130 );
1131 $blobStore->clear();
1132
1133 // ResourceLoader: File-dependency cache
1134 $this->db->delete( 'module_deps', '*', __METHOD__ );
1135 $this->output( "done.\n" );
1136 }
1137
1141 protected function checkStats() {
1142 $this->output( "...site_stats is populated..." );
1143 $row = $this->db->selectRow( 'site_stats', '*', [ 'ss_row_id' => 1 ], __METHOD__ );
1144 if ( $row === false ) {
1145 $this->output( "data is missing! rebuilding...\n" );
1146 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1147 $this->output( "missing ss_total_pages, rebuilding...\n" );
1148 } else {
1149 $this->output( "done.\n" );
1150
1151 return;
1152 }
1153 SiteStatsInit::doAllAndCommit( $this->db );
1154 }
1155
1156 # Common updater functions
1157
1161 protected function doCollationUpdate() {
1162 global $wgCategoryCollation;
1163 if ( $this->db->selectField(
1164 'categorylinks',
1165 'COUNT(*)',
1166 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
1167 __METHOD__
1168 ) == 0
1169 ) {
1170 $this->output( "...collations up-to-date.\n" );
1171
1172 return;
1173 }
1174
1175 $this->output( "Updating category collations..." );
1176 $task = $this->maintenance->runChild( UpdateCollation::class );
1177 $task->execute();
1178 $this->output( "...done.\n" );
1179 }
1180
1181 protected function doConvertDjvuMetadata() {
1182 if ( $this->updateRowExists( 'ConvertDjvuMetadata' ) ) {
1183 return;
1184 }
1185 $this->output( "Converting djvu metadata..." );
1186 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1187 '@phan-var RefreshImageMetadata $task';
1188 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1189 'force' => true,
1190 'mediatype' => 'OFFICE',
1191 'mime' => 'image/*',
1192 'batch-size' => 1,
1193 'sleep' => 1
1194 ] );
1195 $ok = $task->execute();
1196 if ( $ok !== false ) {
1197 $this->output( "...done.\n" );
1198 $this->insertUpdateRow( 'ConvertDjvuMetadata' );
1199 }
1200 }
1201
1205 protected function rebuildLocalisationCache() {
1209 $cl = $this->maintenance->runChild(
1210 RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
1211 );
1212 '@phan-var RebuildLocalisationCache $cl';
1213 $this->output( "Rebuilding localisation cache...\n" );
1214 $cl->setForce();
1215 $cl->execute();
1216 $this->output( "done.\n" );
1217 }
1218
1219 protected function migrateTemplatelinks() {
1220 if ( $this->updateRowExists( MigrateLinksTable::class . 'templatelinks' ) ) {
1221 $this->output( "...templatelinks table has already been migrated.\n" );
1222 return;
1223 }
1227 $task = $this->maintenance->runChild(
1228 MigrateLinksTable::class, 'migrateLinksTable.php'
1229 );
1230 '@phan-var MigrateLinksTable $task';
1231 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1232 'force' => true,
1233 'table' => 'templatelinks'
1234 ] );
1235 $this->output( "Running migrateLinksTable.php on templatelinks...\n" );
1236 $task->execute();
1237 $this->output( "done.\n" );
1238 }
1239
1244 protected function migrateComments() {
1245 if ( !$this->updateRowExists( 'MigrateComments' ) ) {
1246 $this->output(
1247 "Migrating comments to the 'comments' table, printing progress markers. For large\n" .
1248 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1249 "maintenance/migrateComments.php.\n"
1250 );
1251 $task = $this->maintenance->runChild( MigrateComments::class, 'migrateComments.php' );
1252 $ok = $task->execute();
1253 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1254 }
1255 }
1256
1261 protected function migrateImageCommentTemp() {
1262 if ( $this->tableExists( 'image_comment_temp' ) ) {
1263 $this->output( "Merging image_comment_temp into the image table\n" );
1264 $task = $this->maintenance->runChild(
1265 MigrateImageCommentTemp::class, 'migrateImageCommentTemp.php'
1266 );
1267 // @phan-suppress-next-line PhanUndeclaredMethod
1268 $task->setForce();
1269 $ok = $task->execute();
1270 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1271 if ( $ok ) {
1272 $this->dropTable( 'image_comment_temp' );
1273 }
1274 }
1275 }
1276
1281 protected function migrateActors() {
1282 if ( !$this->updateRowExists( 'MigrateActors' ) ) {
1283 $this->output(
1284 "Migrating actors to the 'actor' table, printing progress markers. For large\n" .
1285 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1286 "maintenance/migrateActors.php.\n"
1287 );
1288 $task = $this->maintenance->runChild( MigrateActors::class, 'migrateActors.php' );
1289 $ok = $task->execute();
1290 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1291 }
1292 }
1293
1298 protected function migrateArchiveText() {
1299 if ( $this->db->fieldExists( 'archive', 'ar_text', __METHOD__ ) ) {
1300 $this->output( "Migrating archive ar_text to modern storage.\n" );
1301 $task = $this->maintenance->runChild( MigrateArchiveText::class, 'migrateArchiveText.php' );
1302 // @phan-suppress-next-line PhanUndeclaredMethod
1303 $task->setForce();
1304 if ( $task->execute() ) {
1305 $this->applyPatch( 'patch-drop-ar_text.sql', false,
1306 'Dropping ar_text and ar_flags columns' );
1307 }
1308 }
1309 }
1310
1315 protected function populateArchiveRevId() {
1316 $info = $this->db->fieldInfo( 'archive', 'ar_rev_id' );
1317 if ( !$info ) {
1318 throw new MWException( 'Missing ar_rev_id field of archive table. Should not happen.' );
1319 }
1320 if ( $info->isNullable() ) {
1321 $this->output( "Populating ar_rev_id.\n" );
1322 $task = $this->maintenance->runChild( PopulateArchiveRevId::class, 'populateArchiveRevId.php' );
1323 if ( $task->execute() ) {
1324 $this->applyPatch( 'patch-ar_rev_id-not-null.sql', false,
1325 'Making ar_rev_id not nullable' );
1326 }
1327 }
1328 }
1329
1334 protected function populateExternallinksIndex60() {
1335 if ( !$this->updateRowExists( 'populate externallinks.el_index_60' ) ) {
1336 $this->output(
1337 "Populating el_index_60 field, printing progress markers. For large\n" .
1338 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1339 "maintenance/populateExternallinksIndex60.php.\n"
1340 );
1341 $task = $this->maintenance->runChild( PopulateExternallinksIndex60::class,
1342 'populateExternallinksIndex60.php' );
1343 $task->execute();
1344 $this->output( "done.\n" );
1345 }
1346 }
1347
1352 protected function populateContentTables() {
1353 if ( !$this->updateRowExists( 'PopulateContentTables' ) ) {
1354 $this->output(
1355 "Migrating revision data to the MCR 'slot' and 'content' tables, printing progress markers.\n" .
1356 "For large databases, you may want to hit Ctrl-C and do this manually with\n" .
1357 "maintenance/populateContentTables.php.\n"
1358 );
1359 $task = $this->maintenance->runChild(
1360 PopulateContentTables::class, 'populateContentTables.php'
1361 );
1362 $ok = $task->execute();
1363 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1364 if ( $ok ) {
1365 $this->insertUpdateRow( 'PopulateContentTables' );
1366 }
1367 }
1368 }
1369
1383 protected function ifTableNotExists( $table, $func, ...$params ) {
1384 // Handle $passSelf from runUpdates().
1385 $passSelf = false;
1386 if ( $table === $this ) {
1387 $passSelf = true;
1388 $table = $func;
1389 $func = array_shift( $params );
1390 }
1391
1392 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1393 return null;
1394 }
1395
1396 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1397 $func = [ $this, $func ];
1398 } elseif ( $passSelf ) {
1399 array_unshift( $params, $this );
1400 }
1401
1402 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1403 return $func( ...$params );
1404 }
1405
1420 protected function ifFieldExists( $table, $field, $func, ...$params ) {
1421 // Handle $passSelf from runUpdates().
1422 $passSelf = false;
1423 if ( $table === $this ) {
1424 $passSelf = true;
1425 $table = $field;
1426 $field = $func;
1427 $func = array_shift( $params );
1428 }
1429
1430 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1431 !$this->db->fieldExists( $table, $field, __METHOD__ )
1432 ) {
1433 return null;
1434 }
1435
1436 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1437 $func = [ $this, $func ];
1438 } elseif ( $passSelf ) {
1439 array_unshift( $params, $this );
1440 }
1441
1442 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1443 return $func( ...$params );
1444 }
1445
1446}
global $wgCommandLineMode
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.
Class for handling database updates.
purgeCache()
Purge various database caches.
addExtensionTable( $tableName, $sqlPath)
Convenience wrapper for addExtensionUpdate() when adding a new table (which is the most common usage ...
getDB()
Get a database connection to run updates.
setFileAccess()
Set any .htaccess files or equivalent for storage repos.
migrateImageCommentTemp()
Merge image_comment_temp into the image table.
addIndex( $table, $index, $patch, $fullpath=false)
Add a new index to an existing table.
array $updates
Array of updates to perform on the database.
modifyExtensionTable( $tableName, $sqlPath)
Modify an existing extension table.
bool $skipSchema
Flag specifying whether or not to skip schema (e.g.
populateArchiveRevId()
Populate ar_rev_id, then make it not nullable.
runMaintenance( $class, $unused='')
Run a maintenance script.
migrateArchiveText()
Migrate ar_text to modern storage.
addField( $table, $field, $patch, $fullpath=false)
Add a new field to an existing table.
addExtensionIndex( $tableName, $indexName, $sqlPath)
Add an index to an existing extension table.
modifyTable( $table, $patch, $fullpath=false)
Modify an existing table, similar to modifyField.
doUpdates(array $what=[ 'core', 'extensions', 'stats'])
Do all the updates.
Maintenance $maintenance
ifTableNotExists( $table, $func,... $params)
Only run a function if a table does not exist.
updateRowExists( $key)
Helper function: check if the given key is present in the updatelog table.
getCoreUpdateList()
Get an array of updates to perform on the database.
populateExternallinksIndex60()
Populates the externallinks.el_index_60 field.
output( $str)
Output some text.
getExtensionUpdates()
Get the list of extension-defined updates.
insertUpdateRow( $key, $val=null)
Helper function: Add a key to the updatelog table.
setAutoExtensionHookContainer(HookContainer $hookContainer)
Set the HookContainer to use for loading extension schema updates.
canUseNewUpdatelog()
Updatelog was changed in 1.17 to have a ul_value column so we can record more information about what ...
patchPath(IDatabase $db, $patch)
Get the full path of a patch file.
copyFile( $filename)
Append an SQL fragment to the open file handle.
tableExists( $tableName)
string[] $postDatabaseUpdateMaintenance
Scripts to run after database update Should be a subclass of LoggedUpdateMaintenance.
HookContainer null $autoExtensionHookContainer
dropExtensionTable( $tableName, $sqlPath=false)
Drop an extension table.
resource null $fileHandle
File handle for SQL output.
renameIndex( $table, $oldIndex, $newIndex, $skipBothIndexExistWarning, $patch, $fullpath=false)
Rename an index from an existing table.
rebuildLocalisationCache()
Rebuilds the localisation cache.
addExtensionField( $tableName, $columnName, $sqlPath)
Add a field to an existing extension table.
IMaintainableDatabase $db
Handle to the database subclass.
ifFieldExists( $table, $field, $func,... $params)
Only run a function if the named field exists.
addTable( $name, $patch, $fullpath=false)
Add a new table to the database.
modifyField( $table, $field, $patch, $fullpath=false)
Modify an existing field.
dropExtensionField( $tableName, $columnName, $sqlPath)
Drop a field from an extension table.
static newForDB(IMaintainableDatabase $db, $shared=false, Maintenance $maintenance=null)
addPostDatabaseUpdateMaintenance( $class)
Add a maintenance script to be run after the database updates are complete.
dropField( $table, $field, $patch, $fullpath=false)
Drop a field from an existing table.
doCollationUpdate()
Update CategoryLinks collation.
renameExtensionIndex( $tableName, $oldIndexName, $newIndexName, $sqlPath, $skipBothIndexExistWarning=false)
Rename an index on an extension table Intended for use in LoadExtensionSchemaUpdates hook handlers.
migrateActors()
Migrate actors to the new 'actor' table.
addExtensionUpdate(array $update)
Add a new update coming from an extension.
array $updatesSkipped
Array of updates that were skipped.
appendLine( $line)
Append a line to the open filehandle.
dropExtensionIndex( $tableName, $indexName, $sqlPath)
Drop an index from an extension table Intended for use in LoadExtensionSchemaUpdates hook handlers.
array $extensionUpdates
List of extension-provided database updates.
checkStats()
Check the site_stats table is not properly populated.
modifyExtensionField( $tableName, $fieldName, $sqlPath)
Modify an existing field in an extension table.
applyPatch( $path, $isFullPath=false, $msg=null)
Applies a SQL patch.
dropIndex( $table, $index, $patch, $fullpath=false)
Drop an index from an existing table.
populateContentTables()
Populates the MCR content tables.
__construct(IMaintainableDatabase &$db, $shared, Maintenance $maintenance=null)
dropTable( $table, $patch=false, $fullpath=false)
If the specified table exists, drop it, or execute the patch if one is provided.
migrateComments()
Migrate comments to the new 'comment' table.
getSchemaVars()
Get appropriate schema variables in the current database connection.
doTable( $name)
Returns whether updates should be executed on the database table $name.
Fake maintenance wrapper, mostly used for the web installer/updater.
static getExistingLocalSettings()
Determine if LocalSettings.php exists.
static getDBTypes()
Get a list of known DB types.
MediaWiki exception.
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...
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
This class generates message blobs for use by ResourceLoader.
static doAllAndCommit( $database, array $options=[])
Do all updates and commit them.
$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.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:39
getType()
Get the RDBMS type of the server (e.g.
setFlag( $flag, $remember=self::REMEMBER_NOTHING)
Set a flag for this connection.
Advanced database interface for IDatabase handles that include maintenance methods.
$line
Definition mcc.php:119
if( $line===false) $args
Definition mcc.php:124
const DBO_DDLMODE
Definition defines.php:16
return true
Definition router.php:92