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 $row = $this->db->selectRow(
569 'updatelog',
570 # T67813
571 '1 AS X',
572 [ 'ul_key' => $key ],
573 __METHOD__
574 );
575
576 return (bool)$row;
577 }
578
592 public function insertUpdateRow( $key, $val = null ) {
593 $this->db->clearFlag( DBO_DDLMODE );
594 $values = [ 'ul_key' => $key ];
595 if ( $val && $this->canUseNewUpdatelog() ) {
596 $values['ul_value'] = $val;
597 }
598 $this->db->insert( 'updatelog', $values, __METHOD__, [ 'IGNORE' ] );
599 $this->db->setFlag( DBO_DDLMODE );
600 }
601
610 protected function canUseNewUpdatelog() {
611 return $this->db->tableExists( 'updatelog', __METHOD__ ) &&
612 $this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ );
613 }
614
623 protected function doTable( $name ) {
625
626 // Don't bother to check $wgSharedTables if there isn't a shared database
627 // or the user actually also wants to do updates on the shared database.
628 if ( $wgSharedDB === null || $this->shared ) {
629 return true;
630 }
631
632 if ( in_array( $name, $wgSharedTables ) ) {
633 $this->output( "...skipping update to shared table $name.\n" );
634 return false;
635 } else {
636 return true;
637 }
638 }
639
647 abstract protected function getCoreUpdateList();
648
656 protected function copyFile( $filename ) {
657 $this->db->sourceFile(
658 $filename,
659 null,
660 null,
661 __METHOD__,
662 function ( $line ) {
663 return $this->appendLine( $line );
664 }
665 );
666 }
667
680 protected function appendLine( $line ) {
681 $line = rtrim( $line ) . ";\n";
682 if ( fwrite( $this->fileHandle, $line ) === false ) {
683 throw new MWException( "trouble writing file" );
684 }
685
686 return false;
687 }
688
700 protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
701 if ( $msg === null ) {
702 $msg = "Applying $path patch";
703 }
704 if ( $this->skipSchema ) {
705 $this->output( "...skipping schema change ($msg).\n" );
706
707 return false;
708 }
709
710 $this->output( "{$msg}..." );
711
712 if ( !$isFullPath ) {
713 $path = $this->patchPath( $this->db, $path );
714 }
715 if ( $this->fileHandle !== null ) {
716 $this->copyFile( $path );
717 } else {
718 $this->db->sourceFile( $path );
719 }
720 $this->output( "done.\n" );
721
722 return true;
723 }
724
733 public function patchPath( IDatabase $db, $patch ) {
734 $baseDir = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::BaseDirectory );
735
736 $dbType = $db->getType();
737 if ( file_exists( "$baseDir/maintenance/$dbType/archives/$patch" ) ) {
738 return "$baseDir/maintenance/$dbType/archives/$patch";
739 } else {
740 return "$baseDir/maintenance/archives/$patch";
741 }
742 }
743
755 protected function addTable( $name, $patch, $fullpath = false ) {
756 if ( !$this->doTable( $name ) ) {
757 return true;
758 }
759
760 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
761 $this->output( "...$name table already exists.\n" );
762 } else {
763 return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
764 }
765
766 return true;
767 }
768
781 protected function addField( $table, $field, $patch, $fullpath = false ) {
782 if ( !$this->doTable( $table ) ) {
783 return true;
784 }
785
786 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
787 $this->output( "...$table table does not exist, skipping new field patch.\n" );
788 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
789 $this->output( "...have $field field in $table table.\n" );
790 } else {
791 return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
792 }
793
794 return true;
795 }
796
809 protected function addIndex( $table, $index, $patch, $fullpath = false ) {
810 if ( !$this->doTable( $table ) ) {
811 return true;
812 }
813
814 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
815 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
816 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
817 $this->output( "...index $index already set on $table table.\n" );
818 } else {
819 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
820 }
821
822 return true;
823 }
824
837 protected function dropField( $table, $field, $patch, $fullpath = false ) {
838 if ( !$this->doTable( $table ) ) {
839 return true;
840 }
841
842 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
843 return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
844 } else {
845 $this->output( "...$table table does not contain $field field.\n" );
846 }
847
848 return true;
849 }
850
863 protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
864 if ( !$this->doTable( $table ) ) {
865 return true;
866 }
867
868 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
869 return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
870 } else {
871 $this->output( "...$index key doesn't exist.\n" );
872 }
873
874 return true;
875 }
876
893 protected function renameIndex( $table, $oldIndex, $newIndex,
894 $skipBothIndexExistWarning, $patch, $fullpath = false
895 ) {
896 if ( !$this->doTable( $table ) ) {
897 return true;
898 }
899
900 // First requirement: the table must exist
901 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
902 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
903
904 return true;
905 }
906
907 // Second requirement: the new index must be missing
908 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
909 $this->output( "...index $newIndex already set on $table table.\n" );
910 if ( !$skipBothIndexExistWarning &&
911 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
912 ) {
913 $this->output( "...WARNING: $oldIndex still exists, despite it has " .
914 "been renamed into $newIndex (which also exists).\n" .
915 " $oldIndex should be manually removed if not needed anymore.\n" );
916 }
917
918 return true;
919 }
920
921 // Third requirement: the old index must exist
922 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
923 $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
924
925 return true;
926 }
927
928 // Requirements have been satisfied, patch can be applied
929 return $this->applyPatch(
930 $patch,
931 $fullpath,
932 "Renaming index $oldIndex into $newIndex to table $table"
933 );
934 }
935
950 protected function dropTable( $table, $patch = false, $fullpath = false ) {
951 if ( !$this->doTable( $table ) ) {
952 return true;
953 }
954
955 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
956 $msg = "Dropping table $table";
957
958 if ( $patch === false ) {
959 $this->output( "$msg ..." );
960 $this->db->dropTable( $table, __METHOD__ );
961 $this->output( "done.\n" );
962 } else {
963 return $this->applyPatch( $patch, $fullpath, $msg );
964 }
965 } else {
966 $this->output( "...$table doesn't exist.\n" );
967 }
968
969 return true;
970 }
971
986 protected function modifyField( $table, $field, $patch, $fullpath = false ) {
987 if ( !$this->doTable( $table ) ) {
988 return true;
989 }
990
991 $updateKey = "$table-$field-$patch";
992 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
993 $this->output( "...$table table does not exist, skipping modify field patch.\n" );
994 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
995 $this->output( "...$field field does not exist in $table table, " .
996 "skipping modify field patch.\n" );
997 } elseif ( $this->updateRowExists( $updateKey ) ) {
998 $this->output( "...$field in table $table already modified by patch $patch.\n" );
999 } else {
1000 $apply = $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
1001 if ( $apply ) {
1002 $this->insertUpdateRow( $updateKey );
1003 }
1004 return $apply;
1005 }
1006 return true;
1007 }
1008
1023 protected function modifyTable( $table, $patch, $fullpath = false ) {
1024 if ( !$this->doTable( $table ) ) {
1025 return true;
1026 }
1027
1028 $updateKey = "$table-$patch";
1029 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1030 $this->output( "...$table table does not exist, skipping modify table patch.\n" );
1031 } elseif ( $this->updateRowExists( $updateKey ) ) {
1032 $this->output( "...table $table already modified by patch $patch.\n" );
1033 } else {
1034 $apply = $this->applyPatch( $patch, $fullpath, "Modifying table $table" );
1035 if ( $apply ) {
1036 $this->insertUpdateRow( $updateKey );
1037 }
1038 return $apply;
1039 }
1040 return true;
1041 }
1042
1063 protected function runMaintenance( $class, $script ) {
1064 $this->output( "Running $script...\n" );
1065 $task = $this->maintenance->runChild( $class );
1066 $ok = $task->execute();
1067 if ( !$ok ) {
1068 throw new RuntimeException( "Execution of $script did not complete successfully." );
1069 }
1070 $this->output( "done.\n" );
1071 }
1072
1078 public function setFileAccess() {
1079 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1080 $zonePath = $repo->getZonePath( 'temp' );
1081 if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
1082 // If the directory was never made, then it will have the right ACLs when it is made
1083 $status = $repo->getBackend()->secure( [
1084 'dir' => $zonePath,
1085 'noAccess' => true,
1086 'noListing' => true
1087 ] );
1088 if ( $status->isOK() ) {
1089 $this->output( "Set the local repo temp zone container to be private.\n" );
1090 } else {
1091 $this->output( "Failed to set the local repo temp zone container to be private.\n" );
1092 }
1093 }
1094 }
1095
1099 public function purgeCache() {
1101 // We can't guarantee that the user will be able to use TRUNCATE,
1102 // but we know that DELETE is available to us
1103 $this->output( "Purging caches..." );
1104
1105 // ObjectCache
1106 $this->db->delete( 'objectcache', '*', __METHOD__ );
1107
1108 // LocalisationCache
1109 if ( $wgLocalisationCacheConf['manualRecache'] ) {
1110 $this->rebuildLocalisationCache();
1111 }
1112
1113 // ResourceLoader: Message cache
1114 $services = MediaWikiServices::getInstance();
1115 $blobStore = new MessageBlobStore(
1116 $services->getResourceLoader(),
1117 null,
1118 $services->getMainWANObjectCache()
1119 );
1120 $blobStore->clear();
1121
1122 // ResourceLoader: File-dependency cache
1123 $this->db->delete( 'module_deps', '*', __METHOD__ );
1124 $this->output( "done.\n" );
1125 }
1126
1130 protected function checkStats() {
1131 $this->output( "...site_stats is populated..." );
1132 $row = $this->db->selectRow( 'site_stats', '*', [ 'ss_row_id' => 1 ], __METHOD__ );
1133 if ( $row === false ) {
1134 $this->output( "data is missing! rebuilding...\n" );
1135 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1136 $this->output( "missing ss_total_pages, rebuilding...\n" );
1137 } else {
1138 $this->output( "done.\n" );
1139
1140 return;
1141 }
1142 SiteStatsInit::doAllAndCommit( $this->db );
1143 }
1144
1145 # Common updater functions
1146
1150 protected function doCollationUpdate() {
1151 global $wgCategoryCollation;
1152 if ( $this->db->selectField(
1153 'categorylinks',
1154 'COUNT(*)',
1155 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
1156 __METHOD__
1157 ) == 0
1158 ) {
1159 $this->output( "...collations up-to-date.\n" );
1160
1161 return;
1162 }
1163
1164 $this->output( "Updating category collations..." );
1165 $task = $this->maintenance->runChild( UpdateCollation::class );
1166 $task->execute();
1167 $this->output( "...done.\n" );
1168 }
1169
1170 protected function doConvertDjvuMetadata() {
1171 if ( $this->updateRowExists( 'ConvertDjvuMetadata' ) ) {
1172 return;
1173 }
1174 $this->output( "Converting djvu metadata..." );
1175 $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1176 '@phan-var RefreshImageMetadata $task';
1177 $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1178 'force' => true,
1179 'mediatype' => 'OFFICE',
1180 'mime' => 'image/*',
1181 'batch-size' => 1,
1182 'sleep' => 1
1183 ] );
1184 $ok = $task->execute();
1185 if ( $ok !== false ) {
1186 $this->output( "...done.\n" );
1187 $this->insertUpdateRow( 'ConvertDjvuMetadata' );
1188 }
1189 }
1190
1194 protected function rebuildLocalisationCache() {
1198 $cl = $this->maintenance->runChild(
1199 RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
1200 );
1201 '@phan-var RebuildLocalisationCache $cl';
1202 $this->output( "Rebuilding localisation cache...\n" );
1203 $cl->setForce();
1204 $cl->execute();
1205 $this->output( "done.\n" );
1206 }
1207
1208 protected function migrateTemplatelinks() {
1209 if ( $this->updateRowExists( MigrateLinksTable::class . 'templatelinks' ) ) {
1210 $this->output( "...templatelinks table has already been migrated.\n" );
1211 return;
1212 }
1216 $task = $this->maintenance->runChild(
1217 MigrateLinksTable::class, 'migrateLinksTable.php'
1218 );
1219 '@phan-var MigrateLinksTable $task';
1220 $task->loadParamsAndArgs( MigrateLinksTable::class, [
1221 'force' => true,
1222 'table' => 'templatelinks'
1223 ] );
1224 $this->output( "Running migrateLinksTable.php on templatelinks...\n" );
1225 $task->execute();
1226 $this->output( "done.\n" );
1227 }
1228
1233 protected function migrateComments() {
1234 if ( !$this->updateRowExists( 'MigrateComments' ) ) {
1235 $this->output(
1236 "Migrating comments to the 'comments' table, printing progress markers. For large\n" .
1237 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1238 "maintenance/migrateComments.php.\n"
1239 );
1240 $task = $this->maintenance->runChild( MigrateComments::class, 'migrateComments.php' );
1241 $ok = $task->execute();
1242 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1243 }
1244 }
1245
1250 protected function migrateImageCommentTemp() {
1251 if ( $this->tableExists( 'image_comment_temp' ) ) {
1252 $this->output( "Merging image_comment_temp into the image table\n" );
1253 $task = $this->maintenance->runChild(
1254 MigrateImageCommentTemp::class, 'migrateImageCommentTemp.php'
1255 );
1256 // @phan-suppress-next-line PhanUndeclaredMethod
1257 $task->setForce();
1258 $ok = $task->execute();
1259 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1260 if ( $ok ) {
1261 $this->dropTable( 'image_comment_temp' );
1262 }
1263 }
1264 }
1265
1270 protected function migrateActors() {
1271 if ( !$this->updateRowExists( 'MigrateActors' ) ) {
1272 $this->output(
1273 "Migrating actors to the 'actor' table, printing progress markers. For large\n" .
1274 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1275 "maintenance/migrateActors.php.\n"
1276 );
1277 $task = $this->maintenance->runChild( MigrateActors::class, 'migrateActors.php' );
1278 $ok = $task->execute();
1279 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1280 }
1281 }
1282
1287 protected function migrateArchiveText() {
1288 if ( $this->db->fieldExists( 'archive', 'ar_text', __METHOD__ ) ) {
1289 $this->output( "Migrating archive ar_text to modern storage.\n" );
1290 $task = $this->maintenance->runChild( MigrateArchiveText::class, 'migrateArchiveText.php' );
1291 // @phan-suppress-next-line PhanUndeclaredMethod
1292 $task->setForce();
1293 if ( $task->execute() ) {
1294 $this->applyPatch( 'patch-drop-ar_text.sql', false,
1295 'Dropping ar_text and ar_flags columns' );
1296 }
1297 }
1298 }
1299
1304 protected function populateArchiveRevId() {
1305 $info = $this->db->fieldInfo( 'archive', 'ar_rev_id' );
1306 if ( !$info ) {
1307 throw new MWException( 'Missing ar_rev_id field of archive table. Should not happen.' );
1308 }
1309 if ( $info->isNullable() ) {
1310 $this->output( "Populating ar_rev_id.\n" );
1311 $task = $this->maintenance->runChild( PopulateArchiveRevId::class, 'populateArchiveRevId.php' );
1312 if ( $task->execute() ) {
1313 $this->applyPatch( 'patch-ar_rev_id-not-null.sql', false,
1314 'Making ar_rev_id not nullable' );
1315 }
1316 }
1317 }
1318
1323 protected function populateExternallinksIndex60() {
1324 if ( !$this->updateRowExists( 'populate externallinks.el_index_60' ) ) {
1325 $this->output(
1326 "Populating el_index_60 field, printing progress markers. For large\n" .
1327 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1328 "maintenance/populateExternallinksIndex60.php.\n"
1329 );
1330 $task = $this->maintenance->runChild( PopulateExternallinksIndex60::class,
1331 'populateExternallinksIndex60.php' );
1332 $task->execute();
1333 $this->output( "done.\n" );
1334 }
1335 }
1336
1341 protected function populateContentTables() {
1342 if ( !$this->updateRowExists( 'PopulateContentTables' ) ) {
1343 $this->output(
1344 "Migrating revision data to the MCR 'slot' and 'content' tables, printing progress markers.\n" .
1345 "For large databases, you may want to hit Ctrl-C and do this manually with\n" .
1346 "maintenance/populateContentTables.php.\n"
1347 );
1348 $task = $this->maintenance->runChild(
1349 PopulateContentTables::class, 'populateContentTables.php'
1350 );
1351 $ok = $task->execute();
1352 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1353 if ( $ok ) {
1354 $this->insertUpdateRow( 'PopulateContentTables' );
1355 }
1356 }
1357 }
1358
1372 protected function ifTableNotExists( $table, $func, ...$params ) {
1373 // Handle $passSelf from runUpdates().
1374 $passSelf = false;
1375 if ( $table === $this ) {
1376 $passSelf = true;
1377 $table = $func;
1378 $func = array_shift( $params );
1379 }
1380
1381 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1382 return null;
1383 }
1384
1385 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1386 $func = [ $this, $func ];
1387 } elseif ( $passSelf ) {
1388 array_unshift( $params, $this );
1389 }
1390
1391 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1392 return $func( ...$params );
1393 }
1394
1409 protected function ifFieldExists( $table, $field, $func, ...$params ) {
1410 // Handle $passSelf from runUpdates().
1411 $passSelf = false;
1412 if ( $table === $this ) {
1413 $passSelf = true;
1414 $table = $field;
1415 $field = $func;
1416 $func = array_shift( $params );
1417 }
1418
1419 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1420 !$this->db->fieldExists( $table, $field, __METHOD__ )
1421 ) {
1422 return null;
1423 }
1424
1425 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1426 $func = [ $this, $func ];
1427 } elseif ( $passSelf ) {
1428 array_unshift( $params, $this );
1429 }
1430
1431 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1432 return $func( ...$params );
1433 }
1434
1435}
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, $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.
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