MediaWiki REL1_35
DatabaseUpdater.php
Go to the documentation of this file.
1<?php
30
31require_once __DIR__ . '/../../maintenance/Maintenance.php';
32
41abstract class DatabaseUpdater {
42 public const REPLICATION_WAIT_TIMEOUT = 300;
43
49 protected $updates = [];
50
56 protected $updatesSkipped = [];
57
62 protected $extensionUpdates = [];
63
69 protected $db;
70
74 protected $maintenance;
75
76 protected $shared = false;
77
80
86 DeleteDefaultMessages::class,
87 PopulateRevisionLength::class,
88 PopulateRevisionSha1::class,
89 PopulateImageSha1::class,
90 FixExtLinksProtocolRelative::class,
91 PopulateFilearchiveSha1::class,
92 PopulateBacklinkNamespace::class,
93 FixDefaultJsonContentPages::class,
94 CleanupEmptyCategories::class,
95 AddRFCandPMIDInterwiki::class,
96 PopulatePPSortKey::class,
97 PopulateIpChanges::class,
98 RefreshExternallinksIndex::class,
99 ];
100
106 protected $fileHandle = null;
107
113 protected $skipSchema = false;
114
121 protected function __construct(
123 $shared,
125 ) {
126 $this->db = $db;
127 $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
128 $this->shared = $shared;
129 if ( $maintenance ) {
130 $this->maintenance = $maintenance;
131 $this->fileHandle = $maintenance->fileHandle;
132 } else {
133 $this->maintenance = new FakeMaintenance;
134 }
135 $this->maintenance->setDB( $db );
136 }
137
141 private function loadExtensionSchemaUpdates() {
142 $this->initOldGlobals();
143 $hookContainer = $this->loadExtensions();
144 ( new HookRunner( $hookContainer ) )->onLoadExtensionSchemaUpdates( $this );
145 }
146
151 private function initOldGlobals() {
152 global $wgExtNewTables, $wgExtNewFields, $wgExtPGNewFields,
153 $wgExtPGAlteredFields, $wgExtNewIndexes, $wgExtModifiedFields;
154
155 # For extensions only, should be populated via hooks
156 # $wgDBtype should be checked to specify the proper file
157 $wgExtNewTables = []; // table, dir
158 $wgExtNewFields = []; // table, column, dir
159 $wgExtPGNewFields = []; // table, column, column attributes; for PostgreSQL
160 $wgExtPGAlteredFields = []; // table, column, new type, conversion method; for PostgreSQL
161 $wgExtNewIndexes = []; // table, index, dir
162 $wgExtModifiedFields = []; // table, index, dir
163 }
164
171 private function loadExtensions() {
172 if ( $this->autoExtensionHookContainer ) {
173 // Already injected by installer
174 return $this->autoExtensionHookContainer;
175 }
176 if ( defined( 'MW_EXTENSIONS_LOADED' ) ) {
177 throw new Exception( __METHOD__ .
178 ' apparently called from installer but no hook container was injected' );
179 }
180 if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
181 // Running under update.php: just use global locator
182 return MediaWikiServices::getInstance()->getHookContainer();
183 }
185
186 $registry = ExtensionRegistry::getInstance();
187 $queue = $registry->getQueue();
188 // Don't accidentally load extensions in the future
189 $registry->clearQueue();
190
191 // Read extension.json files
192 $data = $registry->readFromQueue( $queue );
193
194 // Merge extension attribute hooks with hooks defined by a .php
195 // registration file included from LocalSettings.php
196 $legacySchemaHooks = $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ?? [];
197 if ( $vars && isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
198 $legacySchemaHooks = array_merge( $legacySchemaHooks, $vars['wgHooks']['LoadExtensionSchemaUpdates'] );
199 }
200
201 // Merge classes from extension.json
202 global $wgAutoloadClasses;
203 if ( $vars && isset( $vars['wgAutoloadClasses'] ) ) {
204 $wgAutoloadClasses += $vars['wgAutoloadClasses'];
205 }
206
207 return new HookContainer(
209 [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
210 $data['attributes']['Hooks'] ?? [],
211 $data['attributes']['DeprecatedHooks'] ?? []
212 ),
213 MediaWikiServices::getInstance()->getObjectFactory()
214 );
215 }
216
225 public static function newForDB(
227 $shared = false,
229 ) {
230 $type = $db->getType();
231 if ( in_array( $type, Installer::getDBTypes() ) ) {
232 $class = ucfirst( $type ) . 'Updater';
233
234 return new $class( $db, $shared, $maintenance );
235 } else {
236 throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' );
237 }
238 }
239
247 public function setAutoExtensionHookContainer( HookContainer $hookContainer ) {
248 $this->autoExtensionHookContainer = $hookContainer;
249 }
250
256 public function getDB() {
257 return $this->db;
258 }
259
266 public function output( $str ) {
267 if ( $this->maintenance->isQuiet() ) {
268 return;
269 }
270 global $wgCommandLineMode;
271 if ( !$wgCommandLineMode ) {
272 $str = htmlspecialchars( $str );
273 }
274 echo $str;
275 flush();
276 }
277
290 public function addExtensionUpdate( array $update ) {
291 $this->extensionUpdates[] = $update;
292 }
293
304 public function addExtensionTable( $tableName, $sqlPath ) {
305 $this->extensionUpdates[] = [ 'addTable', $tableName, $sqlPath, true ];
306 }
307
318 public function addExtensionIndex( $tableName, $indexName, $sqlPath ) {
319 $this->extensionUpdates[] = [ 'addIndex', $tableName, $indexName, $sqlPath, true ];
320 }
321
332 public function addExtensionField( $tableName, $columnName, $sqlPath ) {
333 $this->extensionUpdates[] = [ 'addField', $tableName, $columnName, $sqlPath, true ];
334 }
335
346 public function dropExtensionField( $tableName, $columnName, $sqlPath ) {
347 $this->extensionUpdates[] = [ 'dropField', $tableName, $columnName, $sqlPath, true ];
348 }
349
360 public function dropExtensionIndex( $tableName, $indexName, $sqlPath ) {
361 $this->extensionUpdates[] = [ 'dropIndex', $tableName, $indexName, $sqlPath, true ];
362 }
363
373 public function dropExtensionTable( $tableName, $sqlPath = false ) {
374 $this->extensionUpdates[] = [ 'dropTable', $tableName, $sqlPath, true ];
375 }
376
390 public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName,
391 $sqlPath, $skipBothIndexExistWarning = false
392 ) {
393 $this->extensionUpdates[] = [
394 'renameIndex',
395 $tableName,
396 $oldIndexName,
397 $newIndexName,
398 $skipBothIndexExistWarning,
399 $sqlPath,
400 true
401 ];
402 }
403
414 public function modifyExtensionField( $tableName, $fieldName, $sqlPath ) {
415 $this->extensionUpdates[] = [ 'modifyField', $tableName, $fieldName, $sqlPath, true ];
416 }
417
427 public function modifyExtensionTable( $tableName, $sqlPath ) {
428 $this->extensionUpdates[] = [ 'modifyTable', $tableName, $sqlPath, true ];
429 }
430
437 public function tableExists( $tableName ) {
438 return ( $this->db->tableExists( $tableName, __METHOD__ ) );
439 }
440
450 public function addPostDatabaseUpdateMaintenance( $class ) {
451 $this->postDatabaseUpdateMaintenance[] = $class;
452 }
453
459 protected function getExtensionUpdates() {
460 return $this->extensionUpdates;
461 }
462
469 return $this->postDatabaseUpdateMaintenance;
470 }
471
478 private function writeSchemaUpdateFile( array $schemaUpdate = [] ) {
479 $updates = $this->updatesSkipped;
480 $this->updatesSkipped = [];
481
482 foreach ( $updates as $funcList ) {
483 list( $func, $args, $origParams ) = $funcList;
484 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable
485 $func( ...$args );
486 flush();
487 $this->updatesSkipped[] = $origParams;
488 }
489 }
490
502 public function getSchemaVars() {
503 return []; // DB-type specific
504 }
505
511 public function doUpdates( array $what = [ 'core', 'extensions', 'stats' ] ) {
512 $this->db->setSchemaVars( $this->getSchemaVars() );
513
514 $what = array_flip( $what );
515 $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
516 if ( isset( $what['core'] ) ) {
517 $this->runUpdates( $this->getCoreUpdateList(), false );
518 }
519 if ( isset( $what['extensions'] ) ) {
521 $this->runUpdates( $this->getOldGlobalUpdates(), false );
522 $this->runUpdates( $this->getExtensionUpdates(), true );
523 }
524
525 if ( isset( $what['stats'] ) ) {
526 $this->checkStats();
527 }
528
529 if ( $this->fileHandle ) {
530 $this->skipSchema = false;
531 $this->writeSchemaUpdateFile();
532 }
533 }
534
541 private function runUpdates( array $updates, $passSelf ) {
542 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
543
544 $updatesDone = [];
545 $updatesSkipped = [];
546 foreach ( $updates as $params ) {
547 $origParams = $params;
548 $func = array_shift( $params );
549 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
550 $func = [ $this, $func ];
551 } elseif ( $passSelf ) {
552 array_unshift( $params, $this );
553 }
554 $ret = $func( ...$params );
555 flush();
556 if ( $ret !== false ) {
557 $updatesDone[] = $origParams;
558 $lbFactory->waitForReplication( [ 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
559 } else {
560 $updatesSkipped[] = [ $func, $params, $origParams ];
561 }
562 }
563 $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
564 $this->updates = array_merge( $this->updates, $updatesDone );
565 }
566
574 public function updateRowExists( $key ) {
575 $row = $this->db->selectRow(
576 'updatelog',
577 # T67813
578 '1 AS X',
579 [ 'ul_key' => $key ],
580 __METHOD__
581 );
582
583 return (bool)$row;
584 }
585
599 public function insertUpdateRow( $key, $val = null ) {
600 $this->db->clearFlag( DBO_DDLMODE );
601 $values = [ 'ul_key' => $key ];
602 if ( $val && $this->canUseNewUpdatelog() ) {
603 $values['ul_value'] = $val;
604 }
605 $this->db->insert( 'updatelog', $values, __METHOD__, [ 'IGNORE' ] );
606 $this->db->setFlag( DBO_DDLMODE );
607 }
608
617 protected function canUseNewUpdatelog() {
618 return $this->db->tableExists( 'updatelog', __METHOD__ ) &&
619 $this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ );
620 }
621
630 protected function doTable( $name ) {
632
633 // Don't bother to check $wgSharedTables if there isn't a shared database
634 // or the user actually also wants to do updates on the shared database.
635 if ( $wgSharedDB === null || $this->shared ) {
636 return true;
637 }
638
639 if ( in_array( $name, $wgSharedTables ) ) {
640 $this->output( "...skipping update to shared table $name.\n" );
641 return false;
642 } else {
643 return true;
644 }
645 }
646
656 protected function getOldGlobalUpdates() {
657 global $wgExtNewFields, $wgExtNewTables, $wgExtModifiedFields,
658 $wgExtNewIndexes;
659
660 $updates = [];
661
662 foreach ( $wgExtNewTables as $tableRecord ) {
663 $updates[] = [
664 'addTable', $tableRecord[0], $tableRecord[1], true
665 ];
666 }
667
668 foreach ( $wgExtNewFields as $fieldRecord ) {
669 $updates[] = [
670 'addField', $fieldRecord[0], $fieldRecord[1],
671 $fieldRecord[2], true
672 ];
673 }
674
675 foreach ( $wgExtNewIndexes as $fieldRecord ) {
676 $updates[] = [
677 'addIndex', $fieldRecord[0], $fieldRecord[1],
678 $fieldRecord[2], true
679 ];
680 }
681
682 foreach ( $wgExtModifiedFields as $fieldRecord ) {
683 $updates[] = [
684 'modifyField', $fieldRecord[0], $fieldRecord[1],
685 $fieldRecord[2], true
686 ];
687 }
688
689 return $updates;
690 }
691
700 abstract protected function getCoreUpdateList();
701
709 protected function copyFile( $filename ) {
710 $this->db->sourceFile(
711 $filename,
712 null,
713 null,
714 __METHOD__,
715 function ( $line ) {
716 return $this->appendLine( $line );
717 }
718 );
719 }
720
733 protected function appendLine( $line ) {
734 $line = rtrim( $line ) . ";\n";
735 if ( fwrite( $this->fileHandle, $line ) === false ) {
736 throw new MWException( "trouble writing file" );
737 }
738
739 return false;
740 }
741
753 protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
754 if ( $msg === null ) {
755 $msg = "Applying $path patch";
756 }
757 if ( $this->skipSchema ) {
758 $this->output( "...skipping schema change ($msg).\n" );
759
760 return false;
761 }
762
763 $this->output( "$msg ..." );
764
765 if ( !$isFullPath ) {
766 $path = $this->patchPath( $this->db, $path );
767 }
768 if ( $this->fileHandle !== null ) {
769 $this->copyFile( $path );
770 } else {
771 $this->db->sourceFile( $path );
772 }
773 $this->output( "done.\n" );
774
775 return true;
776 }
777
787 public function patchPath( IDatabase $db, $patch ) {
788 global $IP;
789
790 $dbType = $db->getType();
791 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
792 return "$IP/maintenance/$dbType/archives/$patch";
793 } else {
794 return "$IP/maintenance/archives/$patch";
795 }
796 }
797
809 protected function addTable( $name, $patch, $fullpath = false ) {
810 if ( !$this->doTable( $name ) ) {
811 return true;
812 }
813
814 if ( $this->db->tableExists( $name, __METHOD__ ) ) {
815 $this->output( "...$name table already exists.\n" );
816 } else {
817 return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
818 }
819
820 return true;
821 }
822
835 protected function addField( $table, $field, $patch, $fullpath = false ) {
836 if ( !$this->doTable( $table ) ) {
837 return true;
838 }
839
840 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
841 $this->output( "...$table table does not exist, skipping new field patch.\n" );
842 } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
843 $this->output( "...have $field field in $table table.\n" );
844 } else {
845 return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
846 }
847
848 return true;
849 }
850
863 protected function addIndex( $table, $index, $patch, $fullpath = false ) {
864 if ( !$this->doTable( $table ) ) {
865 return true;
866 }
867
868 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
869 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
870 } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
871 $this->output( "...index $index already set on $table table.\n" );
872 } else {
873 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
874 }
875
876 return true;
877 }
878
889 protected function addIndexIfNoneExist( $table, $indexes, $patch, $fullpath = false ) {
890 if ( !$this->doTable( $table ) ) {
891 return true;
892 }
893
894 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
895 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
896 return true;
897 }
898
899 $newIndex = $indexes[0];
900 foreach ( $indexes as $index ) {
901 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
902 $this->output(
903 "...skipping index $newIndex because index $index already set on $table table.\n"
904 );
905 return true;
906 }
907 }
908
909 return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
910 }
911
924 protected function dropField( $table, $field, $patch, $fullpath = false ) {
925 if ( !$this->doTable( $table ) ) {
926 return true;
927 }
928
929 if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
930 return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
931 } else {
932 $this->output( "...$table table does not contain $field field.\n" );
933 }
934
935 return true;
936 }
937
950 protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
951 if ( !$this->doTable( $table ) ) {
952 return true;
953 }
954
955 if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
956 return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
957 } else {
958 $this->output( "...$index key doesn't exist.\n" );
959 }
960
961 return true;
962 }
963
980 protected function renameIndex( $table, $oldIndex, $newIndex,
981 $skipBothIndexExistWarning, $patch, $fullpath = false
982 ) {
983 if ( !$this->doTable( $table ) ) {
984 return true;
985 }
986
987 // First requirement: the table must exist
988 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
989 $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
990
991 return true;
992 }
993
994 // Second requirement: the new index must be missing
995 if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
996 $this->output( "...index $newIndex already set on $table table.\n" );
997 if ( !$skipBothIndexExistWarning &&
998 $this->db->indexExists( $table, $oldIndex, __METHOD__ )
999 ) {
1000 $this->output( "...WARNING: $oldIndex still exists, despite it has " .
1001 "been renamed into $newIndex (which also exists).\n" .
1002 " $oldIndex should be manually removed if not needed anymore.\n" );
1003 }
1004
1005 return true;
1006 }
1007
1008 // Third requirement: the old index must exist
1009 if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
1010 $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
1011
1012 return true;
1013 }
1014
1015 // Requirements have been satisfied, patch can be applied
1016 return $this->applyPatch(
1017 $patch,
1018 $fullpath,
1019 "Renaming index $oldIndex into $newIndex to table $table"
1020 );
1021 }
1022
1037 protected function dropTable( $table, $patch = false, $fullpath = false ) {
1038 if ( !$this->doTable( $table ) ) {
1039 return true;
1040 }
1041
1042 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1043 $msg = "Dropping table $table";
1044
1045 if ( $patch === false ) {
1046 $this->output( "$msg ..." );
1047 $this->db->dropTable( $table, __METHOD__ );
1048 $this->output( "done.\n" );
1049 } else {
1050 return $this->applyPatch( $patch, $fullpath, $msg );
1051 }
1052 } else {
1053 $this->output( "...$table doesn't exist.\n" );
1054 }
1055
1056 return true;
1057 }
1058
1073 protected function modifyField( $table, $field, $patch, $fullpath = false ) {
1074 if ( !$this->doTable( $table ) ) {
1075 return true;
1076 }
1077
1078 $updateKey = "$table-$field-$patch";
1079 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1080 $this->output( "...$table table does not exist, skipping modify field patch.\n" );
1081 } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
1082 $this->output( "...$field field does not exist in $table table, " .
1083 "skipping modify field patch.\n" );
1084 } elseif ( $this->updateRowExists( $updateKey ) ) {
1085 $this->output( "...$field in table $table already modified by patch $patch.\n" );
1086 } else {
1087 $apply = $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
1088 if ( $apply ) {
1089 $this->insertUpdateRow( $updateKey );
1090 }
1091 return $apply;
1092 }
1093 return true;
1094 }
1095
1110 protected function modifyTable( $table, $patch, $fullpath = false ) {
1111 if ( !$this->doTable( $table ) ) {
1112 return true;
1113 }
1114
1115 $updateKey = "$table-$patch";
1116 if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1117 $this->output( "...$table table does not exist, skipping modify table patch.\n" );
1118 } elseif ( $this->updateRowExists( $updateKey ) ) {
1119 $this->output( "...table $table already modified by patch $patch.\n" );
1120 } else {
1121 $apply = $this->applyPatch( $patch, $fullpath, "Modifying table $table" );
1122 if ( $apply ) {
1123 $this->insertUpdateRow( $updateKey );
1124 }
1125 return $apply;
1126 }
1127 return true;
1128 }
1129
1150 protected function runMaintenance( $class, $script ) {
1151 $this->output( "Running $script...\n" );
1152 $task = $this->maintenance->runChild( $class );
1153 $ok = $task->execute();
1154 if ( !$ok ) {
1155 throw new RuntimeException( "Execution of $script did not complete successfully." );
1156 }
1157 $this->output( "done.\n" );
1158 }
1159
1165 public function setFileAccess() {
1166 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1167 $zonePath = $repo->getZonePath( 'temp' );
1168 if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
1169 // If the directory was never made, then it will have the right ACLs when it is made
1170 $status = $repo->getBackend()->secure( [
1171 'dir' => $zonePath,
1172 'noAccess' => true,
1173 'noListing' => true
1174 ] );
1175 if ( $status->isOK() ) {
1176 $this->output( "Set the local repo temp zone container to be private.\n" );
1177 } else {
1178 $this->output( "Failed to set the local repo temp zone container to be private.\n" );
1179 }
1180 }
1181 }
1182
1186 public function purgeCache() {
1188 // We can't guarantee that the user will be able to use TRUNCATE,
1189 // but we know that DELETE is available to us
1190 $this->output( "Purging caches..." );
1191
1192 // ObjectCache
1193 $this->db->delete( 'objectcache', '*', __METHOD__ );
1194
1195 // LocalisationCache
1196 if ( $wgLocalisationCacheConf['manualRecache'] ) {
1197 $this->rebuildLocalisationCache();
1198 }
1199
1200 // ResourceLoader: Message cache
1201 $services = MediaWikiServices::getInstance();
1202 $blobStore = new MessageBlobStore(
1203 $services->getResourceLoader(),
1204 null,
1205 $services->getMainWANObjectCache()
1206 );
1207 $blobStore->clear();
1208
1209 // ResourceLoader: File-dependency cache
1210 $this->db->delete( 'module_deps', '*', __METHOD__ );
1211 $this->output( "done.\n" );
1212 }
1213
1217 protected function checkStats() {
1218 $this->output( "...site_stats is populated..." );
1219 $row = $this->db->selectRow( 'site_stats', '*', [ 'ss_row_id' => 1 ], __METHOD__ );
1220 if ( $row === false ) {
1221 $this->output( "data is missing! rebuilding...\n" );
1222 } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1223 $this->output( "missing ss_total_pages, rebuilding...\n" );
1224 } else {
1225 $this->output( "done.\n" );
1226
1227 return;
1228 }
1229 SiteStatsInit::doAllAndCommit( $this->db );
1230 }
1231
1232 # Common updater functions
1233
1237 protected function doActiveUsersInit() {
1238 $activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', '', __METHOD__ );
1239 if ( $activeUsers == -1 ) {
1240 $activeUsers = $this->db->selectField( 'recentchanges',
1241 'COUNT( DISTINCT rc_user_text )',
1242 [ 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ], __METHOD__
1243 );
1244 $this->db->update( 'site_stats',
1245 [ 'ss_active_users' => intval( $activeUsers ) ],
1246 [ 'ss_row_id' => 1 ], __METHOD__, [ 'LIMIT' => 1 ]
1247 );
1248 }
1249 $this->output( "...ss_active_users user count set...\n" );
1250 }
1251
1255 protected function doLogUsertextPopulation() {
1256 if ( !$this->updateRowExists( 'populate log_usertext' ) ) {
1257 $this->output(
1258 "Populating log_user_text field, printing progress markers. For large\n" .
1259 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1260 "maintenance/populateLogUsertext.php.\n"
1261 );
1262
1263 $task = $this->maintenance->runChild( PopulateLogUsertext::class );
1264 $task->execute();
1265 $this->output( "done.\n" );
1266 }
1267 }
1268
1272 protected function doLogSearchPopulation() {
1273 if ( !$this->updateRowExists( 'populate log_search' ) ) {
1274 $this->output(
1275 "Populating log_search table, printing progress markers. For large\n" .
1276 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1277 "maintenance/populateLogSearch.php.\n" );
1278
1279 $task = $this->maintenance->runChild( PopulateLogSearch::class );
1280 $task->execute();
1281 $this->output( "done.\n" );
1282 }
1283 }
1284
1288 protected function doCollationUpdate() {
1289 global $wgCategoryCollation;
1290 if ( $this->db->fieldExists( 'categorylinks', 'cl_collation', __METHOD__ ) ) {
1291 if ( $this->db->selectField(
1292 'categorylinks',
1293 'COUNT(*)',
1294 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
1295 __METHOD__
1296 ) == 0
1297 ) {
1298 $this->output( "...collations up-to-date.\n" );
1299
1300 return;
1301 }
1302
1303 $this->output( "Updating category collations..." );
1304 $task = $this->maintenance->runChild( UpdateCollation::class );
1305 $task->execute();
1306 $this->output( "...done.\n" );
1307 }
1308 }
1309
1313 protected function doMigrateUserOptions() {
1314 if ( $this->db->tableExists( 'user_properties', __METHOD__ ) ) {
1315 $cl = $this->maintenance->runChild( ConvertUserOptions::class, 'convertUserOptions.php' );
1316 $cl->execute();
1317 $this->output( "done.\n" );
1318 }
1319 }
1320
1324 protected function rebuildLocalisationCache() {
1328 $cl = $this->maintenance->runChild(
1329 RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
1330 );
1331 '@phan-var RebuildLocalisationCache $cl';
1332 $this->output( "Rebuilding localisation cache...\n" );
1333 $cl->setForce();
1334 $cl->execute();
1335 $this->output( "done.\n" );
1336 }
1337
1342 protected function migrateComments() {
1343 if ( !$this->updateRowExists( 'MigrateComments' ) ) {
1344 $this->output(
1345 "Migrating comments to the 'comments' table, printing progress markers. For large\n" .
1346 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1347 "maintenance/migrateComments.php.\n"
1348 );
1349 $task = $this->maintenance->runChild( MigrateComments::class, 'migrateComments.php' );
1350 $ok = $task->execute();
1351 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1352 }
1353 }
1354
1359 protected function migrateImageCommentTemp() {
1360 if ( $this->tableExists( 'image_comment_temp' ) ) {
1361 $this->output( "Merging image_comment_temp into the image table\n" );
1362 $task = $this->maintenance->runChild(
1363 MigrateImageCommentTemp::class, 'migrateImageCommentTemp.php'
1364 );
1365 // @phan-suppress-next-line PhanUndeclaredMethod
1366 $task->setForce();
1367 $ok = $task->execute();
1368 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1369 if ( $ok ) {
1370 $this->dropTable( 'image_comment_temp' );
1371 }
1372 }
1373 }
1374
1379 protected function migrateActors() {
1380 if ( !$this->updateRowExists( 'MigrateActors' ) ) {
1381 $this->output(
1382 "Migrating actors to the 'actor' table, printing progress markers. For large\n" .
1383 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1384 "maintenance/migrateActors.php.\n"
1385 );
1386 $task = $this->maintenance->runChild( 'MigrateActors', 'migrateActors.php' );
1387 $ok = $task->execute();
1388 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1389 }
1390 }
1391
1396 protected function migrateArchiveText() {
1397 if ( $this->db->fieldExists( 'archive', 'ar_text', __METHOD__ ) ) {
1398 $this->output( "Migrating archive ar_text to modern storage.\n" );
1399 $task = $this->maintenance->runChild( MigrateArchiveText::class, 'migrateArchiveText.php' );
1400 // @phan-suppress-next-line PhanUndeclaredMethod
1401 $task->setForce();
1402 if ( $task->execute() ) {
1403 $this->applyPatch( 'patch-drop-ar_text.sql', false,
1404 'Dropping ar_text and ar_flags columns' );
1405 }
1406 }
1407 }
1408
1413 protected function populateArchiveRevId() {
1414 $info = $this->db->fieldInfo( 'archive', 'ar_rev_id' );
1415 if ( !$info ) {
1416 throw new MWException( 'Missing ar_rev_id field of archive table. Should not happen.' );
1417 }
1418 if ( $info->isNullable() ) {
1419 $this->output( "Populating ar_rev_id.\n" );
1420 $task = $this->maintenance->runChild( 'PopulateArchiveRevId', 'populateArchiveRevId.php' );
1421 if ( $task->execute() ) {
1422 $this->applyPatch( 'patch-ar_rev_id-not-null.sql', false,
1423 'Making ar_rev_id not nullable' );
1424 }
1425 }
1426 }
1427
1432 protected function populateExternallinksIndex60() {
1433 if ( !$this->updateRowExists( 'populate externallinks.el_index_60' ) ) {
1434 $this->output(
1435 "Populating el_index_60 field, printing progress markers. For large\n" .
1436 "databases, you may want to hit Ctrl-C and do this manually with\n" .
1437 "maintenance/populateExternallinksIndex60.php.\n"
1438 );
1439 $task = $this->maintenance->runChild( 'PopulateExternallinksIndex60',
1440 'populateExternallinksIndex60.php' );
1441 $task->execute();
1442 $this->output( "done.\n" );
1443 }
1444 }
1445
1450 protected function populateContentTables() {
1451 if ( !$this->updateRowExists( 'PopulateContentTables' ) ) {
1452 $this->output(
1453 "Migrating revision data to the MCR 'slot' and 'content' tables, printing progress markers.\n" .
1454 "For large databases, you may want to hit Ctrl-C and do this manually with\n" .
1455 "maintenance/populateContentTables.php.\n"
1456 );
1457 $task = $this->maintenance->runChild(
1458 PopulateContentTables::class, 'populateContentTables.php'
1459 );
1460 $ok = $task->execute();
1461 $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
1462 if ( $ok ) {
1463 $this->insertUpdateRow( 'PopulateContentTables' );
1464 }
1465 }
1466 }
1467
1471 protected function ifNoActorTable( $func, ...$params ) {
1472 wfDeprecated( __METHOD__, '1.35' );
1473 return $this->ifTableNotExists( 'actor', $func, ...$params );
1474 }
1475
1489 protected function ifTableNotExists( $table, $func, ...$params ) {
1490 // Handle $passSelf from runUpdates().
1491 $passSelf = false;
1492 if ( $table === $this ) {
1493 $passSelf = true;
1494 $table = $func;
1495 $func = array_shift( $params );
1496 }
1497
1498 if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1499 return null;
1500 }
1501
1502 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1503 $func = [ $this, $func ];
1504 } elseif ( $passSelf ) {
1505 array_unshift( $params, $this );
1506 }
1507
1508 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1509 return $func( ...$params );
1510 }
1511
1526 protected function ifFieldExists( $table, $field, $func, ...$params ) {
1527 // Handle $passSelf from runUpdates().
1528 $passSelf = false;
1529 if ( $table === $this ) {
1530 $passSelf = true;
1531 $table = $field;
1532 $field = $func;
1533 $func = array_shift( $params );
1534 }
1535
1536 if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1537 !$this->db->fieldExists( $table, $field, __METHOD__ )
1538 ) {
1539 return null;
1540 }
1541
1542 if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1543 $func = [ $this, $func ];
1544 } elseif ( $passSelf ) {
1545 array_unshift( $params, $this );
1546 }
1547
1548 // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1549 return $func( ...$params );
1550 }
1551}
$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 $function is deprecated.
$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.
doLogSearchPopulation()
Migrate log params to new table and index for searching.
doLogUsertextPopulation()
Populates the log_user_text field in the logging table.
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.
renameIndex( $table, $oldIndex, $newIndex, $skipBothIndexExistWarning, $patch, $fullpath=false)
Rename an index from an existing table Stable to override.
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.
doActiveUsersInit()
Sets the number of active users in the site_stats table.
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)
getOldGlobalUpdates()
Before 1.17, we used to handle updates via stuff like $wgExtNewTables/Fields/Indexes.
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.
resource $fileHandle
File handle for SQL output.
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.
initOldGlobals()
Initialize all of the old globals.
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)
Stable to call.
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.
doMigrateUserOptions()
Migrates user options from the user table blob to user_properties.
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 type of the DBMS (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