MediaWiki  master
DatabaseUpdater.php
Go to the documentation of this file.
1 <?php
32 
33 require_once __DIR__ . '/../../maintenance/Maintenance.php';
34 
42 abstract 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 
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,
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
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(
202  new StaticHookRegistry(
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,
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() {
455  }
456 
464  }
465 
472  private function writeSchemaUpdateFile( array $schemaUpdate = [] ) {
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  $msg ??= "Applying $path patch";
702  if ( $this->skipSchema ) {
703  $this->output( "...skipping schema change ($msg).\n" );
704 
705  return false;
706  }
707 
708  $this->output( "{$msg}..." );
709 
710  if ( !$isFullPath ) {
711  $path = $this->patchPath( $this->db, $path );
712  }
713  if ( $this->fileHandle !== null ) {
714  $this->copyFile( $path );
715  } else {
716  $this->db->sourceFile( $path );
717  }
718  $this->output( "done.\n" );
719 
720  return true;
721  }
722 
731  public function patchPath( IDatabase $db, $patch ) {
732  $baseDir = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::BaseDirectory );
733 
734  $dbType = $db->getType();
735  if ( file_exists( "$baseDir/maintenance/$dbType/archives/$patch" ) ) {
736  return "$baseDir/maintenance/$dbType/archives/$patch";
737  } else {
738  return "$baseDir/maintenance/archives/$patch";
739  }
740  }
741 
753  protected function addTable( $name, $patch, $fullpath = false ) {
754  if ( !$this->doTable( $name ) ) {
755  return true;
756  }
757 
758  if ( $this->db->tableExists( $name, __METHOD__ ) ) {
759  $this->output( "...$name table already exists.\n" );
760  } else {
761  return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
762  }
763 
764  return true;
765  }
766 
779  protected function addField( $table, $field, $patch, $fullpath = false ) {
780  if ( !$this->doTable( $table ) ) {
781  return true;
782  }
783 
784  if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
785  $this->output( "...$table table does not exist, skipping new field patch.\n" );
786  } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
787  $this->output( "...have $field field in $table table.\n" );
788  } else {
789  return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
790  }
791 
792  return true;
793  }
794 
807  protected function addIndex( $table, $index, $patch, $fullpath = false ) {
808  if ( !$this->doTable( $table ) ) {
809  return true;
810  }
811 
812  if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
813  $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
814  } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
815  $this->output( "...index $index already set on $table table.\n" );
816  } else {
817  return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
818  }
819 
820  return true;
821  }
822 
835  protected function dropField( $table, $field, $patch, $fullpath = false ) {
836  if ( !$this->doTable( $table ) ) {
837  return true;
838  }
839 
840  if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
841  return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
842  } else {
843  $this->output( "...$table table does not contain $field field.\n" );
844  }
845 
846  return true;
847  }
848 
861  protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
862  if ( !$this->doTable( $table ) ) {
863  return true;
864  }
865 
866  if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
867  return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
868  } else {
869  $this->output( "...$index key doesn't exist.\n" );
870  }
871 
872  return true;
873  }
874 
891  protected function renameIndex( $table, $oldIndex, $newIndex,
892  $skipBothIndexExistWarning, $patch, $fullpath = false
893  ) {
894  if ( !$this->doTable( $table ) ) {
895  return true;
896  }
897 
898  // First requirement: the table must exist
899  if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
900  $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
901 
902  return true;
903  }
904 
905  // Second requirement: the new index must be missing
906  if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
907  $this->output( "...index $newIndex already set on $table table.\n" );
908  if ( !$skipBothIndexExistWarning &&
909  $this->db->indexExists( $table, $oldIndex, __METHOD__ )
910  ) {
911  $this->output( "...WARNING: $oldIndex still exists, despite it has " .
912  "been renamed into $newIndex (which also exists).\n" .
913  " $oldIndex should be manually removed if not needed anymore.\n" );
914  }
915 
916  return true;
917  }
918 
919  // Third requirement: the old index must exist
920  if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
921  $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
922 
923  return true;
924  }
925 
926  // Requirements have been satisfied, patch can be applied
927  return $this->applyPatch(
928  $patch,
929  $fullpath,
930  "Renaming index $oldIndex into $newIndex to table $table"
931  );
932  }
933 
948  protected function dropTable( $table, $patch = false, $fullpath = false ) {
949  if ( !$this->doTable( $table ) ) {
950  return true;
951  }
952 
953  if ( $this->db->tableExists( $table, __METHOD__ ) ) {
954  $msg = "Dropping table $table";
955 
956  if ( $patch === false ) {
957  $this->output( "$msg ..." );
958  $this->db->dropTable( $table, __METHOD__ );
959  $this->output( "done.\n" );
960  } else {
961  return $this->applyPatch( $patch, $fullpath, $msg );
962  }
963  } else {
964  $this->output( "...$table doesn't exist.\n" );
965  }
966 
967  return true;
968  }
969 
984  protected function modifyField( $table, $field, $patch, $fullpath = false ) {
985  if ( !$this->doTable( $table ) ) {
986  return true;
987  }
988 
989  $updateKey = "$table-$field-$patch";
990  if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
991  $this->output( "...$table table does not exist, skipping modify field patch.\n" );
992  } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
993  $this->output( "...$field field does not exist in $table table, " .
994  "skipping modify field patch.\n" );
995  } elseif ( $this->updateRowExists( $updateKey ) ) {
996  $this->output( "...$field in table $table already modified by patch $patch.\n" );
997  } else {
998  $apply = $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
999  if ( $apply ) {
1000  $this->insertUpdateRow( $updateKey );
1001  }
1002  return $apply;
1003  }
1004  return true;
1005  }
1006 
1021  protected function modifyTable( $table, $patch, $fullpath = false ) {
1022  if ( !$this->doTable( $table ) ) {
1023  return true;
1024  }
1025 
1026  $updateKey = "$table-$patch";
1027  if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
1028  $this->output( "...$table table does not exist, skipping modify table patch.\n" );
1029  } elseif ( $this->updateRowExists( $updateKey ) ) {
1030  $this->output( "...table $table already modified by patch $patch.\n" );
1031  } else {
1032  $apply = $this->applyPatch( $patch, $fullpath, "Modifying table $table" );
1033  if ( $apply ) {
1034  $this->insertUpdateRow( $updateKey );
1035  }
1036  return $apply;
1037  }
1038  return true;
1039  }
1040 
1061  protected function runMaintenance( $class, $script ) {
1062  $this->output( "Running $script...\n" );
1063  $task = $this->maintenance->runChild( $class );
1064  $ok = $task->execute();
1065  if ( !$ok ) {
1066  throw new RuntimeException( "Execution of $script did not complete successfully." );
1067  }
1068  $this->output( "done.\n" );
1069  }
1070 
1076  public function setFileAccess() {
1077  $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1078  $zonePath = $repo->getZonePath( 'temp' );
1079  if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
1080  // If the directory was never made, then it will have the right ACLs when it is made
1081  $status = $repo->getBackend()->secure( [
1082  'dir' => $zonePath,
1083  'noAccess' => true,
1084  'noListing' => true
1085  ] );
1086  if ( $status->isOK() ) {
1087  $this->output( "Set the local repo temp zone container to be private.\n" );
1088  } else {
1089  $this->output( "Failed to set the local repo temp zone container to be private.\n" );
1090  }
1091  }
1092  }
1093 
1097  public function purgeCache() {
1098  global $wgLocalisationCacheConf;
1099  // We can't guarantee that the user will be able to use TRUNCATE,
1100  // but we know that DELETE is available to us
1101  $this->output( "Purging caches..." );
1102 
1103  // ObjectCache
1104  $this->db->delete( 'objectcache', '*', __METHOD__ );
1105 
1106  // LocalisationCache
1107  if ( $wgLocalisationCacheConf['manualRecache'] ) {
1108  $this->rebuildLocalisationCache();
1109  }
1110 
1111  // ResourceLoader: Message cache
1112  $services = MediaWikiServices::getInstance();
1113  MessageBlobStore::clearGlobalCacheEntry(
1114  $services->getMainWANObjectCache()
1115  );
1116 
1117  // ResourceLoader: File-dependency cache
1118  $this->db->delete( 'module_deps', '*', __METHOD__ );
1119  $this->output( "done.\n" );
1120  }
1121 
1125  protected function checkStats() {
1126  $this->output( "...site_stats is populated..." );
1127  $row = $this->db->selectRow( 'site_stats', '*', [ 'ss_row_id' => 1 ], __METHOD__ );
1128  if ( $row === false ) {
1129  $this->output( "data is missing! rebuilding...\n" );
1130  } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
1131  $this->output( "missing ss_total_pages, rebuilding...\n" );
1132  } else {
1133  $this->output( "done.\n" );
1134 
1135  return;
1136  }
1137  SiteStatsInit::doAllAndCommit( $this->db );
1138  }
1139 
1140  # Common updater functions
1141 
1145  protected function doCollationUpdate() {
1146  global $wgCategoryCollation;
1147  if ( $this->db->selectField(
1148  'categorylinks',
1149  'COUNT(*)',
1150  'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
1151  __METHOD__
1152  ) == 0
1153  ) {
1154  $this->output( "...collations up-to-date.\n" );
1155 
1156  return;
1157  }
1158 
1159  $this->output( "Updating category collations..." );
1160  $task = $this->maintenance->runChild( UpdateCollation::class );
1161  $task->execute();
1162  $this->output( "...done.\n" );
1163  }
1164 
1165  protected function doConvertDjvuMetadata() {
1166  if ( $this->updateRowExists( 'ConvertDjvuMetadata' ) ) {
1167  return;
1168  }
1169  $this->output( "Converting djvu metadata..." );
1170  $task = $this->maintenance->runChild( RefreshImageMetadata::class );
1171  '@phan-var RefreshImageMetadata $task';
1172  $task->loadParamsAndArgs( RefreshImageMetadata::class, [
1173  'force' => true,
1174  'mediatype' => 'OFFICE',
1175  'mime' => 'image/*',
1176  'batch-size' => 1,
1177  'sleep' => 1
1178  ] );
1179  $ok = $task->execute();
1180  if ( $ok !== false ) {
1181  $this->output( "...done.\n" );
1182  $this->insertUpdateRow( 'ConvertDjvuMetadata' );
1183  }
1184  }
1185 
1189  protected function rebuildLocalisationCache() {
1193  $cl = $this->maintenance->runChild(
1194  RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
1195  );
1196  '@phan-var RebuildLocalisationCache $cl';
1197  $this->output( "Rebuilding localisation cache...\n" );
1198  $cl->setForce();
1199  $cl->execute();
1200  $this->output( "done.\n" );
1201  }
1202 
1203  protected function migrateTemplatelinks() {
1204  if ( $this->updateRowExists( MigrateLinksTable::class . 'templatelinks' ) ) {
1205  $this->output( "...templatelinks table has already been migrated.\n" );
1206  return;
1207  }
1211  $task = $this->maintenance->runChild(
1212  MigrateLinksTable::class, 'migrateLinksTable.php'
1213  );
1214  '@phan-var MigrateLinksTable $task';
1215  $task->loadParamsAndArgs( MigrateLinksTable::class, [
1216  'force' => true,
1217  'table' => 'templatelinks'
1218  ] );
1219  $this->output( "Running migrateLinksTable.php on templatelinks...\n" );
1220  $task->execute();
1221  $this->output( "done.\n" );
1222  }
1223 
1237  protected function ifTableNotExists( $table, $func, ...$params ) {
1238  // Handle $passSelf from runUpdates().
1239  $passSelf = false;
1240  if ( $table === $this ) {
1241  $passSelf = true;
1242  $table = $func;
1243  $func = array_shift( $params );
1244  }
1245 
1246  if ( $this->db->tableExists( $table, __METHOD__ ) ) {
1247  return null;
1248  }
1249 
1250  if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1251  $func = [ $this, $func ];
1252  } elseif ( $passSelf ) {
1253  array_unshift( $params, $this );
1254  }
1255 
1256  // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1257  return $func( ...$params );
1258  }
1259 
1274  protected function ifFieldExists( $table, $field, $func, ...$params ) {
1275  // Handle $passSelf from runUpdates().
1276  $passSelf = false;
1277  if ( $table === $this ) {
1278  $passSelf = true;
1279  $table = $field;
1280  $field = $func;
1281  $func = array_shift( $params );
1282  }
1283 
1284  if ( !$this->db->tableExists( $table, __METHOD__ ) ||
1285  !$this->db->fieldExists( $table, $field, __METHOD__ )
1286  ) {
1287  return null;
1288  }
1289 
1290  if ( !is_array( $func ) && method_exists( $this, $func ) ) {
1291  $func = [ $this, $func ];
1292  } elseif ( $passSelf ) {
1293  array_unshift( $params, $this );
1294  }
1295 
1296  // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
1297  return $func( ...$params );
1298  }
1299 
1300 }
global $wgCommandLineMode
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.
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.
runMaintenance( $class, $script)
Run a maintenance script.
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.
const REPLICATION_WAIT_TIMEOUT
getCoreUpdateList()
Get an array of updates to perform on the database.
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.
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.
__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.
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.
Definition: Installer.php:654
static getDBTypes()
Get a list of known DB types.
Definition: Installer.php:535
MediaWiki exception.
Definition: MWException.php:30
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:66
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...
Definition: HookRunner.php:561
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.
setFlag( $flag, $remember=self::REMEMBER_NOTHING)
Set a flag for this connection.
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
getType()
Get the RDBMS type of the server (e.g.
Advanced database interface for IDatabase handles that include maintenance methods.
const DBO_DDLMODE
Definition: defines.php:16
return true
Definition: router.php:90