MediaWiki REL1_37
DatabaseUpdater.php
Go to the documentation of this file.
1<?php
30
31require_once __DIR__ . '/../../maintenance/Maintenance.php';
32
40abstract class DatabaseUpdater {
41 public const REPLICATION_WAIT_TIMEOUT = 300;
42
48 protected $updates = [];
49
55 protected $updatesSkipped = [];
56
61 protected $extensionUpdates = [];
62
68 protected $db;
69
73 protected $maintenance;
74
75 protected $shared = false;
76
79
85 DeleteDefaultMessages::class,
86 PopulateRevisionLength::class,
87 PopulateRevisionSha1::class,
88 PopulateImageSha1::class,
89 FixExtLinksProtocolRelative::class,
90 PopulateFilearchiveSha1::class,
91 PopulateBacklinkNamespace::class,
92 FixDefaultJsonContentPages::class,
93 CleanupEmptyCategories::class,
94 AddRFCandPMIDInterwiki::class,
95 PopulatePPSortKey::class,
96 PopulateIpChanges::class,
97 RefreshExternallinksIndex::class,
98 ];
99
105 protected $fileHandle = null;
106
112 protected $skipSchema = false;
113
120 protected function __construct(
122 $shared,
124 ) {
125 $this->db = $db;
126 $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
127 $this->shared = $shared;
128 if ( $maintenance ) {
129 $this->maintenance = $maintenance;
130 $this->fileHandle = $maintenance->fileHandle;
131 } else {
132 $this->maintenance = new FakeMaintenance;
133 }
134 $this->maintenance->setDB( $db );
135 }
136
140 private function loadExtensionSchemaUpdates() {
141 $hookContainer = $this->loadExtensions();
142 ( new HookRunner( $hookContainer ) )->onLoadExtensionSchemaUpdates( $this );
143 }
144
151 private function loadExtensions() {
152 if ( $this->autoExtensionHookContainer ) {
153 // Already injected by installer
154 return $this->autoExtensionHookContainer;
155 }
156 if ( defined( 'MW_EXTENSIONS_LOADED' ) ) {
157 throw new Exception( __METHOD__ .
158 ' apparently called from installer but no hook container was injected' );
159 }
160 if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
161 // Running under update.php: just use global locator
162 return MediaWikiServices::getInstance()->getHookContainer();
163 }
165
166 $registry = ExtensionRegistry::getInstance();
167 $queue = $registry->getQueue();
168 // Don't accidentally load extensions in the future
169 $registry->clearQueue();
170
171 // Read extension.json files
172 $data = $registry->readFromQueue( $queue );
173
174 // Merge extension attribute hooks with hooks defined by a .php
175 // registration file included from LocalSettings.php
176 $legacySchemaHooks = $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ?? [];
177 if ( $vars && isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
178 $legacySchemaHooks = array_merge( $legacySchemaHooks, $vars['wgHooks']['LoadExtensionSchemaUpdates'] );
179 }
180
181 // Merge classes from extension.json
182 global $wgAutoloadClasses;
183 if ( $vars && isset( $vars['wgAutoloadClasses'] ) ) {
184 $wgAutoloadClasses += $vars['wgAutoloadClasses'];
185 }
186
187 return new HookContainer(
189 [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
190 $data['attributes']['Hooks'] ?? [],
191 $data['attributes']['DeprecatedHooks'] ?? []
192 ),
193 MediaWikiServices::getInstance()->getObjectFactory()
194 );
195 }
196
205 public static function newForDB(
207 $shared = false,
209 ) {
210 $type = $db->getType();
211 if ( in_array( $type, Installer::getDBTypes() ) ) {
212 $class = ucfirst( $type ) . 'Updater';
213
214 return new $class( $db, $shared, $maintenance );
215 } else {
216 throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' );
217 }
218 }
219
227 public function setAutoExtensionHookContainer( HookContainer $hookContainer ) {
228 $this->autoExtensionHookContainer = $hookContainer;
229 }
230
236 public function getDB() {
237 return $this->db;
238 }
239
246 public function output( $str ) {
247 if ( $this->maintenance->isQuiet() ) {
248 return;
249 }
250 global $wgCommandLineMode;
251 if ( !$wgCommandLineMode ) {
252 $str = htmlspecialchars( $str );
253 }
254 echo $str;
255 flush();
256 }
257
270 public function addExtensionUpdate( array $update ) {
271 $this->extensionUpdates[] = $update;
272 }
273
284 public function addExtensionTable( $tableName, $sqlPath ) {
285 $this->extensionUpdates[] = [ 'addTable', $tableName, $sqlPath, true ];
286 }
287
298 public function addExtensionIndex( $tableName, $indexName, $sqlPath ) {
299 $this->extensionUpdates[] = [ 'addIndex', $tableName, $indexName, $sqlPath, true ];
300 }
301
312 public function addExtensionField( $tableName, $columnName, $sqlPath ) {
313 $this->extensionUpdates[] = [ 'addField', $tableName, $columnName, $sqlPath, true ];
314 }
315
326 public function dropExtensionField( $tableName, $columnName, $sqlPath ) {
327 $this->extensionUpdates[] = [ 'dropField', $tableName, $columnName, $sqlPath, true ];
328 }
329
340 public function dropExtensionIndex( $tableName, $indexName, $sqlPath ) {
341 $this->extensionUpdates[] = [ 'dropIndex', $tableName, $indexName, $sqlPath, true ];
342 }
343
353 public function dropExtensionTable( $tableName, $sqlPath = false ) {
354 $this->extensionUpdates[] = [ 'dropTable', $tableName, $sqlPath, true ];
355 }
356
370 public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName,
371 $sqlPath, $skipBothIndexExistWarning = false
372 ) {
373 $this->extensionUpdates[] = [
374 'renameIndex',
375 $tableName,
376 $oldIndexName,
377 $newIndexName,
378 $skipBothIndexExistWarning,
379 $sqlPath,
380 true
381 ];
382 }
383
394 public function modifyExtensionField( $tableName, $fieldName, $sqlPath ) {
395 $this->extensionUpdates[] = [ 'modifyField', $tableName, $fieldName, $sqlPath, true ];
396 }
397
407 public function modifyExtensionTable( $tableName, $sqlPath ) {
408 $this->extensionUpdates[] = [ 'modifyTable', $tableName, $sqlPath, true ];
409 }
410
417 public function tableExists( $tableName ) {
418 return ( $this->db->tableExists( $tableName, __METHOD__ ) );
419 }
420
430 public function addPostDatabaseUpdateMaintenance( $class ) {
431 $this->postDatabaseUpdateMaintenance[] = $class;
432 }
433
439 protected function getExtensionUpdates() {
440 return $this->extensionUpdates;
441 }
442
449 return $this->postDatabaseUpdateMaintenance;
450 }
451
458 private function writeSchemaUpdateFile( array $schemaUpdate = [] ) {
459 $updates = $this->updatesSkipped;
460 $this->updatesSkipped = [];
461
462 foreach ( $updates as [ $func, $args, $origParams ] ) {
463 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable
464 $func( ...$args );
465 flush();
466 $this->updatesSkipped[] = $origParams;
467 }
468 }
469
481 public function getSchemaVars() {
482 return []; // DB-type specific
483 }
484
490 public function doUpdates( array $what = [ 'core', 'extensions', 'stats' ] ) {
491 $this->db->setSchemaVars( $this->getSchemaVars() );
492
493 $what = array_fill_keys( $what, true );
494 $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
495 if ( isset( $what['core'] ) ) {
496 $this->doCollationUpdate();
497 $this->runUpdates( $this->getCoreUpdateList(), false );
498 }
499 if ( isset( $what['extensions'] ) ) {
501 $this->runUpdates( $this->getExtensionUpdates(), true );
502 }
503
504 if ( isset( $what['stats'] ) ) {
505 $this->checkStats();
506 }
507
508 if ( $this->fileHandle ) {
509 $this->skipSchema = false;
510 $this->writeSchemaUpdateFile();
511 }
512 }
513
520 private function runUpdates( array $updates, $passSelf ) {
521 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
522
523 $updatesDone = [];
524 $updatesSkipped = [];
525 foreach ( $updates as $params ) {
526 $origParams = $params;
527 $func = array_shift( $params );
528 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
529 $func = [ $this, $func ];
530 } elseif ( $passSelf ) {
531 array_unshift( $params, $this );
532 }
533 $ret = $func( ...$params );
534 flush();
535 if ( $ret !== false ) {
536 $updatesDone[] = $origParams;
537 $lbFactory->waitForReplication( [ 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
538 } else {
539 $updatesSkipped[] = [ $func, $params, $origParams ];
540 }
541 }
542 $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
543 $this->updates = array_merge( $this->updates, $updatesDone );
544 }
545
553 public function updateRowExists( $key ) {
554 $row = $this->db->selectRow(
555 'updatelog',
556 # T67813
557 '1 AS X',
558 [ 'ul_key' => $key ],
559 __METHOD__
560 );
561
562 return (bool)$row;
563 }
564
578 public function insertUpdateRow( $key, $val = null ) {
579 $this->db->clearFlag( DBO_DDLMODE );
580 $values = [ 'ul_key' => $key ];
581 if ( $val && $this->canUseNewUpdatelog() ) {
582 $values['ul_value'] = $val;
583 }
584 $this->db->insert( 'updatelog', $values, __METHOD__, [ 'IGNORE' ] );
585 $this->db->setFlag( DBO_DDLMODE );
586 }
587
596 protected function canUseNewUpdatelog() {
597 return $this->db->tableExists( 'updatelog', __METHOD__ ) &&
598 $this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ );
599 }
600
609 protected function doTable( $name ) {
611
612 // Don't bother to check $wgSharedTables if there isn't a shared database
613 // or the user actually also wants to do updates on the shared database.
614 if ( $wgSharedDB === null || $this->shared ) {
615 return true;
616 }
617
618 if ( in_array( $name, $wgSharedTables ) ) {
619 $this->output( "...skipping update to shared table $name.\n" );
620 return false;
621 } else {
622 return true;
623 }
624 }
625
633 abstract protected function getCoreUpdateList();
634
642 protected function copyFile( $filename ) {
643 $this->db->sourceFile(
644 $filename,
645 null,
646 null,
647 __METHOD__,
648 function ( $line ) {
649 return $this->appendLine( $line );
650 }
651 );
652 }
653
666 protected function appendLine( $line ) {
667 $line = rtrim( $line ) . ";\n";
668 if ( fwrite( $this->fileHandle, $line ) === false ) {
669 throw new MWException( "trouble writing file" );
670 }
671
672 return false;
673 }
674
686 protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
687 if ( $msg === null ) {
688 $msg = "Applying $path patch";
689 }
690 if ( $this->skipSchema ) {
691 $this->output( "...skipping schema change ($msg).\n" );
692
693 return false;
694 }
695
696 $this->output( "$msg ..." );
697
698 if ( !$isFullPath ) {
699 $path = $this->patchPath( $this->db, $path );
700 }
701 if ( $this->fileHandle !== null ) {
702 $this->copyFile( $path );
703 } else {
704 $this->db->sourceFile( $path );
705 }
706 $this->output( "done.\n" );
707
708 return true;
709 }
710
719 public function patchPath( IDatabase $db, $patch ) {
720 global $IP;
721
722 $dbType = $db->getType();
723 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
724 return "$IP/maintenance/$dbType/archives/$patch";
725 } else {
726 return "$IP/maintenance/archives/$patch";
727 }
728 }
729
741 protected function addTable( $name, $patch, $fullpath = false ) {
742 if ( !$this->doTable( $name ) ) {
743 return true;
744 }
745
746 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
747 $this->output( "...$name table already exists.\n" );
748 } else {
749 return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
750 }
751
752 return true;
753 }
754
767 protected function addField( $table, $field, $patch, $fullpath = false ) {
768 if ( !$this->doTable( $table ) ) {
769 return true;
770 }
771
772 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
773 $this->output( "...$table table does not exist, skipping new field patch.\n" );
774 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
775 $this->output( "...have $field field in $table table.\n" );
776 } else {
777 return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
778 }
779
780 return true;
781 }
782
795 protected function addIndex( $table, $index, $patch, $fullpath = false ) {
796 if ( !$this->doTable( $table ) ) {
797 return true;
798 }
799
800 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
801 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
802 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
803 $this->output( "...index $index already set on $table table.\n" );
804 } else {
805 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
806 }
807
808 return true;
809 }
810
821 protected function addIndexIfNoneExist( $table, $indexes, $patch, $fullpath = false ) {
822 if ( !$this->doTable( $table ) ) {
823 return true;
824 }
825
826 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
827 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
828 return true;
829 }
830
831 $newIndex = $indexes[0];
832 foreach ( $indexes as $index ) {
833 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
834 $this->output(
835 "...skipping index $newIndex because index $index already set on $table table.\n"
836 );
837 return true;
838 }
839 }
840
841 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
842 }
843
856 protected function dropField( $table, $field, $patch, $fullpath = false ) {
857 if ( !$this->doTable( $table ) ) {
858 return true;
859 }
860
861 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
862 return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
863 } else {
864 $this->output( "...$table table does not contain $field field.\n" );
865 }
866
867 return true;
868 }
869
882 protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
883 if ( !$this->doTable( $table ) ) {
884 return true;
885 }
886
887 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
888 return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
889 } else {
890 $this->output( "...$index key doesn't exist.\n" );
891 }
892
893 return true;
894 }
895
912 protected function renameIndex( $table, $oldIndex, $newIndex,
913 $skipBothIndexExistWarning, $patch, $fullpath = false
914 ) {
915 if ( !$this->doTable( $table ) ) {
916 return true;
917 }
918
919 // First requirement: the table must exist
920 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
921 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
922
923 return true;
924 }
925
926 // Second requirement: the new index must be missing
927 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
928 $this->output( "...index $newIndex already set on $table table.\n" );
929 if ( !$skipBothIndexExistWarning &&
930 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
931 ) {
932 $this->output( "...WARNING: $oldIndex still exists, despite it has " .
933 "been renamed into $newIndex (which also exists).\n" .
934 " $oldIndex should be manually removed if not needed anymore.\n" );
935 }
936
937 return true;
938 }
939
940 // Third requirement: the old index must exist
941 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
942 $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
943
944 return true;
945 }
946
947 // Requirements have been satisfied, patch can be applied
948 return $this->applyPatch(
949 $patch,
950 $fullpath,
951 "Renaming index $oldIndex into $newIndex to table $table"
952 );
953 }
954
969 protected function dropTable( $table, $patch = false, $fullpath = false ) {
970 if ( !$this->doTable( $table ) ) {
971 return true;
972 }
973
974 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
975 $msg = "Dropping table $table";
976
977 if ( $patch === false ) {
978 $this->output( "$msg ..." );
979 $this->db->dropTable( $table, __METHOD__ );
980 $this->output( "done.\n" );
981 } else {
982 return $this->applyPatch( $patch, $fullpath, $msg );
983 }
984 } else {
985 $this->output( "...$table doesn't exist.\n" );
986 }
987
988 return true;
989 }
990
1005 protected function modifyField( $table, $field, $patch, $fullpath = false ) {
1006 if ( !$this->doTable( $table ) ) {
1007 return true;
1008 }
1009
1010 $updateKey = "$table-$field-$patch";
1011 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1012 $this->output( "...$table table does not exist, skipping modify field patch.\n" );
1013 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1014 $this->output( "...$field field does not exist in $table table, " .
1015 "skipping modify field patch.\n" );
1016 } elseif ( $this->updateRowExists( $updateKey ) ) {
1017 $this->output( "...$field in table $table already modified by patch $patch.\n" );
1018 } else {
1019 $apply = $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
1020 if ( $apply ) {
1021 $this->insertUpdateRow( $updateKey );
1022 }
1023 return $apply;
1024 }
1025 return true;
1026 }
1027
1042 protected function modifyTable( $table, $patch, $fullpath = false ) {
1043 if ( !$this->doTable( $table ) ) {
1044 return true;
1045 }
1046
1047 $updateKey = "$table-$patch";
1048 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1049 $this->output( "...$table table does not exist, skipping modify table patch.\n" );
1050 } elseif ( $this->updateRowExists( $updateKey ) ) {
1051 $this->output( "...table $table already modified by patch $patch.\n" );
1052 } else {
1053 $apply = $this->applyPatch( $patch, $fullpath, "Modifying table $table" );
1054 if ( $apply ) {
1055 $this->insertUpdateRow( $updateKey );
1056 }
1057 return $apply;
1058 }
1059 return true;
1060 }
1061
1082 protected function runMaintenance( $class, $script ) {
1083 $this->output( "Running $script...\n" );
1084 $task = $this->maintenance->runChild( $class );
1085 $ok = $task->execute();
1086 if ( !$ok ) {
1087 throw new RuntimeException( "Execution of $script did not complete successfully." );
1088 }
1089 $this->output( "done.\n" );
1090 }
1091
1097 public function setFileAccess() {
1098 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1099 $zonePath = $repo->getZonePath( 'temp' );
1100 if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
1101 // If the directory was never made, then it will have the right ACLs when it is made
1102 $status = $repo->getBackend()->secure( [
1103 'dir' => $zonePath,
1104 'noAccess' => true,
1105 'noListing' => true
1106 ] );
1107 if ( $status->isOK() ) {
1108 $this->output( "Set the local repo temp zone container to be private.\n" );
1109 } else {
1110 $this->output( "Failed to set the local repo temp zone container to be private.\n" );
1111 }
1112 }
1113 }
1114
1118 public function purgeCache() {
1120 // We can't guarantee that the user will be able to use TRUNCATE,
1121 // but we know that DELETE is available to us
1122 $this->output( "Purging caches..." );
1123
1124 // ObjectCache
1125 $this->db->delete( 'objectcache', '*', __METHOD__ );
1126
1127 // LocalisationCache
1128 if ( $wgLocalisationCacheConf['manualRecache'] ) {
1129 $this->rebuildLocalisationCache();
1130 }
1131
1132 // ResourceLoader: Message cache
1133 $services = MediaWikiServices::getInstance();
1134 $blobStore = new MessageBlobStore(
1135 $services->getResourceLoader(),
1136 null,
1137 $services->getMainWANObjectCache()
1138 );
1139 $blobStore->clear();
1140
1141 // ResourceLoader: File-dependency cache
1142 $this->db->delete( 'module_deps', '*', __METHOD__ );
1143 $this->output( "done.\n" );
1144 }
1145
1149 protected function checkStats() {
1150 $this->output( "...site_stats is populated..." );
1151 $row = $this->db->selectRow( 'site_stats', '*', [ 'ss_row_id' => 1 ], __METHOD__ );
1152 if ( $row === false ) {
1153 $this->output( "data is missing! rebuilding...\n" );
1154 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1155 $this->output( "missing ss_total_pages, rebuilding...\n" );
1156 } else {
1157 $this->output( "done.\n" );
1158
1159 return;
1160 }
1161 SiteStatsInit::doAllAndCommit( $this->db );
1162 }
1163
1164 # Common updater functions
1165
1169 protected function doCollationUpdate() {
1170 global $wgCategoryCollation;
1171 if ( $this->db->selectField(
1172 'categorylinks',
1173 'COUNT(*)',
1174 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
1175 __METHOD__
1176 ) == 0
1177 ) {
1178 $this->output( "...collations up-to-date.\n" );
1179
1180 return;
1181 }
1182
1183 $this->output( "Updating category collations..." );
1184 $task = $this->maintenance->runChild( UpdateCollation::class );
1185 $task->execute();
1186 $this->output( "...done.\n" );
1187 }
1188
1192 protected function rebuildLocalisationCache() {
1196 $cl = $this->maintenance->runChild(
1197 RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
1198 );
1199 '@phan-var RebuildLocalisationCache $cl';
1200 $this->output( "Rebuilding localisation cache...\n" );
1201 $cl->setForce();
1202 $cl->execute();
1203 $this->output( "done.\n" );
1204 }
1205
1210 protected function migrateComments() {
1211 if ( !$this->updateRowExists( 'MigrateComments' ) ) {
1212 $this->output(
1213 "Migrating comments to the 'comments' table, printing progress markers. For large\n" .
1214 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1215 "maintenance/migrateComments.php.\n"
1216 );
1217 $task = $this->maintenance->runChild( MigrateComments::class, 'migrateComments.php' );
1218 $ok = $task->execute();
1219 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1220 }
1221 }
1222
1227 protected function migrateImageCommentTemp() {
1228 if ( $this->tableExists( 'image_comment_temp' ) ) {
1229 $this->output( "Merging image_comment_temp into the image table\n" );
1230 $task = $this->maintenance->runChild(
1231 MigrateImageCommentTemp::class, 'migrateImageCommentTemp.php'
1232 );
1233 // @phan-suppress-next-line PhanUndeclaredMethod
1234 $task->setForce();
1235 $ok = $task->execute();
1236 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1237 if ( $ok ) {
1238 $this->dropTable( 'image_comment_temp' );
1239 }
1240 }
1241 }
1242
1247 protected function migrateActors() {
1248 if ( !$this->updateRowExists( 'MigrateActors' ) ) {
1249 $this->output(
1250 "Migrating actors to the 'actor' table, printing progress markers. For large\n" .
1251 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1252 "maintenance/migrateActors.php.\n"
1253 );
1254 $task = $this->maintenance->runChild( 'MigrateActors', 'migrateActors.php' );
1255 $ok = $task->execute();
1256 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1257 }
1258 }
1259
1264 protected function migrateArchiveText() {
1265 if ( $this->db->fieldExists( 'archive', 'ar_text', __METHOD__ ) ) {
1266 $this->output( "Migrating archive ar_text to modern storage.\n" );
1267 $task = $this->maintenance->runChild( MigrateArchiveText::class, 'migrateArchiveText.php' );
1268 // @phan-suppress-next-line PhanUndeclaredMethod
1269 $task->setForce();
1270 if ( $task->execute() ) {
1271 $this->applyPatch( 'patch-drop-ar_text.sql', false,
1272 'Dropping ar_text and ar_flags columns' );
1273 }
1274 }
1275 }
1276
1281 protected function populateArchiveRevId() {
1282 $info = $this->db->fieldInfo( 'archive', 'ar_rev_id' );
1283 if ( !$info ) {
1284 throw new MWException( 'Missing ar_rev_id field of archive table. Should not happen.' );
1285 }
1286 if ( $info->isNullable() ) {
1287 $this->output( "Populating ar_rev_id.\n" );
1288 $task = $this->maintenance->runChild( 'PopulateArchiveRevId', 'populateArchiveRevId.php' );
1289 if ( $task->execute() ) {
1290 $this->applyPatch( 'patch-ar_rev_id-not-null.sql', false,
1291 'Making ar_rev_id not nullable' );
1292 }
1293 }
1294 }
1295
1300 protected function populateExternallinksIndex60() {
1301 if ( !$this->updateRowExists( 'populate externallinks.el_index_60' ) ) {
1302 $this->output(
1303 "Populating el_index_60 field, printing progress markers. For large\n" .
1304 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1305 "maintenance/populateExternallinksIndex60.php.\n"
1306 );
1307 $task = $this->maintenance->runChild( 'PopulateExternallinksIndex60',
1308 'populateExternallinksIndex60.php' );
1309 $task->execute();
1310 $this->output( "done.\n" );
1311 }
1312 }
1313
1318 protected function populateContentTables() {
1319 if ( !$this->updateRowExists( 'PopulateContentTables' ) ) {
1320 $this->output(
1321 "Migrating revision data to the MCR 'slot' and 'content' tables, printing progress markers.\n" .
1322 "For large databases, you may want to hit Ctrl-C and do this manually with\n" .
1323 "maintenance/populateContentTables.php.\n"
1324 );
1325 $task = $this->maintenance->runChild(
1326 PopulateContentTables::class, 'populateContentTables.php'
1327 );
1328 $ok = $task->execute();
1329 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1330 if ( $ok ) {
1331 $this->insertUpdateRow( 'PopulateContentTables' );
1332 }
1333 }
1334 }
1335
1339 protected function ifNoActorTable( $func, ...$params ) {
1340 wfDeprecated( __METHOD__, '1.35' );
1341 return $this->ifTableNotExists( 'actor', $func, ...$params );
1342 }
1343
1357 protected function ifTableNotExists( $table, $func, ...$params ) {
1358 // Handle $passSelf from runUpdates().
1359 $passSelf = false;
1360 if ( $table === $this ) {
1361 $passSelf = true;
1362 $table = $func;
1363 $func = array_shift( $params );
1364 }
1365
1366 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1367 return null;
1368 }
1369
1370 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1371 $func = [ $this, $func ];
1372 } elseif ( $passSelf ) {
1373 array_unshift( $params, $this );
1374 }
1375
1376 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1377 return $func( ...$params );
1378 }
1379
1394 protected function ifFieldExists( $table, $field, $func, ...$params ) {
1395 // Handle $passSelf from runUpdates().
1396 $passSelf = false;
1397 if ( $table === $this ) {
1398 $passSelf = true;
1399 $table = $field;
1400 $field = $func;
1401 $func = array_shift( $params );
1402 }
1403
1404 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1405 !$this->db->fieldExists( $table, $field, __METHOD__ )
1406 ) {
1407 return null;
1408 }
1409
1410 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1411 $func = [ $this, $func ];
1412 } elseif ( $passSelf ) {
1413 array_unshift( $params, $this );
1414 }
1415
1416 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1417 return $func( ...$params );
1418 }
1419
1420}
$wgSharedTables
$wgAutoloadClasses
Array mapping class names to filenames, for autoloading.
$wgCategoryCollation
Specify how category names should be sorted, when listed on a category page.
$wgSharedDB
Shared database for multiple wikis.
$wgLocalisationCacheConf
Localisation cache configuration.
global $wgCommandLineMode
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
$IP
Definition WebStart.php:49
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 equivilent for storage repos.
migrateImageCommentTemp()
Merge image_comment_temp into the image table.
loadExtensions()
Loads LocalSettings.php, if needed, and initialises everything needed for LoadExtensionSchemaUpdates ...
addIndex( $table, $index, $patch, $fullpath=false)
Add a new index to an existing table.
array $updates
Array of updates to perform on the database.
loadExtensionSchemaUpdates()
Cause extensions to register any updates they need to perform.
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, $script)
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.
runUpdates(array $updates, $passSelf)
Helper function for doUpdates()
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.
writeSchemaUpdateFile(array $schemaUpdate=[])
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.
addIndexIfNoneExist( $table, $indexes, $patch, $fullpath=false)
Add a new index to an existing table if none of the given indexes exist.
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.
ifNoActorTable( $func,... $params)
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...
MediaWikiServices is the service locator for the application scope of MediaWiki.
This class generates message blobs for use by ResourceLoader.
static doAllAndCommit( $database, array $options=[])
Do all updates and commit them.
$maintenance
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
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