[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/installer/ -> DatabaseUpdater.php (source)

   1  <?php
   2  /**
   3   * DBMS-specific updater helper.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup Deployment
  22   */
  23  
  24  require_once  __DIR__ . '/../../maintenance/Maintenance.php';
  25  
  26  /**
  27   * Class for handling database updates. Roughly based off of updaters.inc, with
  28   * a few improvements :)
  29   *
  30   * @ingroup Deployment
  31   * @since 1.17
  32   */
  33  abstract class DatabaseUpdater {
  34  
  35      /**
  36       * Array of updates to perform on the database
  37       *
  38       * @var array
  39       */
  40      protected $updates = array();
  41  
  42      /**
  43       * Array of updates that were skipped
  44       *
  45       * @var array
  46       */
  47      protected $updatesSkipped = array();
  48  
  49      /**
  50       * List of extension-provided database updates
  51       * @var array
  52       */
  53      protected $extensionUpdates = array();
  54  
  55      /**
  56       * Handle to the database subclass
  57       *
  58       * @var DatabaseBase
  59       */
  60      protected $db;
  61  
  62      protected $shared = false;
  63  
  64      /**
  65       * Scripts to run after database update
  66       * Should be a subclass of LoggedUpdateMaintenance
  67       */
  68      protected $postDatabaseUpdateMaintenance = array(
  69          'DeleteDefaultMessages',
  70          'PopulateRevisionLength',
  71          'PopulateRevisionSha1',
  72          'PopulateImageSha1',
  73          'FixExtLinksProtocolRelative',
  74          'PopulateFilearchiveSha1',
  75          'PopulateBacklinkNamespace'
  76      );
  77  
  78      /**
  79       * File handle for SQL output.
  80       *
  81       * @var resource
  82       */
  83      protected $fileHandle = null;
  84  
  85      /**
  86       * Flag specifying whether or not to skip schema (e.g. SQL-only) updates.
  87       *
  88       * @var bool
  89       */
  90      protected $skipSchema = false;
  91  
  92      /**
  93       * Hold the value of $wgContentHandlerUseDB during the upgrade.
  94       */
  95      protected $holdContentHandlerUseDB = true;
  96  
  97      /**
  98       * Constructor
  99       *
 100       * @param DatabaseBase $db To perform updates on
 101       * @param bool $shared Whether to perform updates on shared tables
 102       * @param Maintenance $maintenance Maintenance object which created us
 103       */
 104  	protected function __construct( DatabaseBase &$db, $shared, Maintenance $maintenance = null ) {
 105          $this->db = $db;
 106          $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
 107          $this->shared = $shared;
 108          if ( $maintenance ) {
 109              $this->maintenance = $maintenance;
 110              $this->fileHandle = $maintenance->fileHandle;
 111          } else {
 112              $this->maintenance = new FakeMaintenance;
 113          }
 114          $this->maintenance->setDB( $db );
 115          $this->initOldGlobals();
 116          $this->loadExtensions();
 117          wfRunHooks( 'LoadExtensionSchemaUpdates', array( $this ) );
 118      }
 119  
 120      /**
 121       * Initialize all of the old globals. One day this should all become
 122       * something much nicer
 123       */
 124  	private function initOldGlobals() {
 125          global $wgExtNewTables, $wgExtNewFields, $wgExtPGNewFields,
 126              $wgExtPGAlteredFields, $wgExtNewIndexes, $wgExtModifiedFields;
 127  
 128          # For extensions only, should be populated via hooks
 129          # $wgDBtype should be checked to specifiy the proper file
 130          $wgExtNewTables = array(); // table, dir
 131          $wgExtNewFields = array(); // table, column, dir
 132          $wgExtPGNewFields = array(); // table, column, column attributes; for PostgreSQL
 133          $wgExtPGAlteredFields = array(); // table, column, new type, conversion method; for PostgreSQL
 134          $wgExtNewIndexes = array(); // table, index, dir
 135          $wgExtModifiedFields = array(); // table, index, dir
 136      }
 137  
 138      /**
 139       * Loads LocalSettings.php, if needed, and initialises everything needed for
 140       * LoadExtensionSchemaUpdates hook.
 141       */
 142  	private function loadExtensions() {
 143          if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
 144              return; // already loaded
 145          }
 146          $vars = Installer::getExistingLocalSettings();
 147          if ( !$vars ) {
 148              return; // no LocalSettings found
 149          }
 150          if ( !isset( $vars['wgHooks'] ) || !isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
 151              return;
 152          }
 153          global $wgHooks, $wgAutoloadClasses;
 154          $wgHooks['LoadExtensionSchemaUpdates'] = $vars['wgHooks']['LoadExtensionSchemaUpdates'];
 155          $wgAutoloadClasses = $wgAutoloadClasses + $vars['wgAutoloadClasses'];
 156      }
 157  
 158      /**
 159       * @param DatabaseBase $db
 160       * @param bool $shared
 161       * @param Maintenance $maintenance
 162       *
 163       * @throws MWException
 164       * @return DatabaseUpdater
 165       */
 166  	public static function newForDB( &$db, $shared = false, $maintenance = null ) {
 167          $type = $db->getType();
 168          if ( in_array( $type, Installer::getDBTypes() ) ) {
 169              $class = ucfirst( $type ) . 'Updater';
 170  
 171              return new $class( $db, $shared, $maintenance );
 172          } else {
 173              throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' );
 174          }
 175      }
 176  
 177      /**
 178       * Get a database connection to run updates
 179       *
 180       * @return DatabaseBase
 181       */
 182  	public function getDB() {
 183          return $this->db;
 184      }
 185  
 186      /**
 187       * Output some text. If we're running from web, escape the text first.
 188       *
 189       * @param string $str Text to output
 190       */
 191  	public function output( $str ) {
 192          if ( $this->maintenance->isQuiet() ) {
 193              return;
 194          }
 195          global $wgCommandLineMode;
 196          if ( !$wgCommandLineMode ) {
 197              $str = htmlspecialchars( $str );
 198          }
 199          echo $str;
 200          flush();
 201      }
 202  
 203      /**
 204       * Add a new update coming from an extension. This should be called by
 205       * extensions while executing the LoadExtensionSchemaUpdates hook.
 206       *
 207       * @since 1.17
 208       *
 209       * @param array $update The update to run. Format is the following:
 210       *                first item is the callback function, it also can be a
 211       *                simple string with the name of a function in this class,
 212       *                following elements are parameters to the function.
 213       *                Note that callback functions will receive this object as
 214       *                first parameter.
 215       */
 216  	public function addExtensionUpdate( array $update ) {
 217          $this->extensionUpdates[] = $update;
 218      }
 219  
 220      /**
 221       * Convenience wrapper for addExtensionUpdate() when adding a new table (which
 222       * is the most common usage of updaters in an extension)
 223       *
 224       * @since 1.18
 225       *
 226       * @param string $tableName Name of table to create
 227       * @param string $sqlPath Full path to the schema file
 228       */
 229  	public function addExtensionTable( $tableName, $sqlPath ) {
 230          $this->extensionUpdates[] = array( 'addTable', $tableName, $sqlPath, true );
 231      }
 232  
 233      /**
 234       * @since 1.19
 235       *
 236       * @param string $tableName
 237       * @param string $indexName
 238       * @param string $sqlPath
 239       */
 240  	public function addExtensionIndex( $tableName, $indexName, $sqlPath ) {
 241          $this->extensionUpdates[] = array( 'addIndex', $tableName, $indexName, $sqlPath, true );
 242      }
 243  
 244      /**
 245       *
 246       * @since 1.19
 247       *
 248       * @param string $tableName
 249       * @param string $columnName
 250       * @param string $sqlPath
 251       */
 252  	public function addExtensionField( $tableName, $columnName, $sqlPath ) {
 253          $this->extensionUpdates[] = array( 'addField', $tableName, $columnName, $sqlPath, true );
 254      }
 255  
 256      /**
 257       *
 258       * @since 1.20
 259       *
 260       * @param string $tableName
 261       * @param string $columnName
 262       * @param string $sqlPath
 263       */
 264  	public function dropExtensionField( $tableName, $columnName, $sqlPath ) {
 265          $this->extensionUpdates[] = array( 'dropField', $tableName, $columnName, $sqlPath, true );
 266      }
 267  
 268      /**
 269       * Drop an index from an extension table
 270       *
 271       * @since 1.21
 272       *
 273       * @param string $tableName The table name
 274       * @param string $indexName The index name
 275       * @param string $sqlPath The path to the SQL change path
 276       */
 277  	public function dropExtensionIndex( $tableName, $indexName, $sqlPath ) {
 278          $this->extensionUpdates[] = array( 'dropIndex', $tableName, $indexName, $sqlPath, true );
 279      }
 280  
 281      /**
 282       *
 283       * @since 1.20
 284       *
 285       * @param string $tableName
 286       * @param string $sqlPath
 287       */
 288  	public function dropExtensionTable( $tableName, $sqlPath ) {
 289          $this->extensionUpdates[] = array( 'dropTable', $tableName, $sqlPath, true );
 290      }
 291  
 292      /**
 293       * Rename an index on an extension table
 294       *
 295       * @since 1.21
 296       *
 297       * @param string $tableName The table name
 298       * @param string $oldIndexName The old index name
 299       * @param string $newIndexName The new index name
 300       * @param string $sqlPath The path to the SQL change path
 301       * @param bool $skipBothIndexExistWarning Whether to warn if both the old
 302       * and the new indexes exist. [facultative; by default, false]
 303       */
 304  	public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName,
 305          $sqlPath, $skipBothIndexExistWarning = false
 306      ) {
 307          $this->extensionUpdates[] = array(
 308              'renameIndex',
 309              $tableName,
 310              $oldIndexName,
 311              $newIndexName,
 312              $skipBothIndexExistWarning,
 313              $sqlPath,
 314              true
 315          );
 316      }
 317  
 318      /**
 319       * @since 1.21
 320       *
 321       * @param string $tableName The table name
 322       * @param string $fieldName The field to be modified
 323       * @param string $sqlPath The path to the SQL change path
 324       */
 325  	public function modifyExtensionField( $tableName, $fieldName, $sqlPath ) {
 326          $this->extensionUpdates[] = array( 'modifyField', $tableName, $fieldName, $sqlPath, true );
 327      }
 328  
 329      /**
 330       *
 331       * @since 1.20
 332       *
 333       * @param string $tableName
 334       * @return bool
 335       */
 336  	public function tableExists( $tableName ) {
 337          return ( $this->db->tableExists( $tableName, __METHOD__ ) );
 338      }
 339  
 340      /**
 341       * Add a maintenance script to be run after the database updates are complete.
 342       *
 343       * Script should subclass LoggedUpdateMaintenance
 344       *
 345       * @since 1.19
 346       *
 347       * @param string $class Name of a Maintenance subclass
 348       */
 349  	public function addPostDatabaseUpdateMaintenance( $class ) {
 350          $this->postDatabaseUpdateMaintenance[] = $class;
 351      }
 352  
 353      /**
 354       * Get the list of extension-defined updates
 355       *
 356       * @return array
 357       */
 358  	protected function getExtensionUpdates() {
 359          return $this->extensionUpdates;
 360      }
 361  
 362      /**
 363       * @since 1.17
 364       *
 365       * @return array
 366       */
 367  	public function getPostDatabaseUpdateMaintenance() {
 368          return $this->postDatabaseUpdateMaintenance;
 369      }
 370  
 371      /**
 372       * @since 1.21
 373       *
 374       * Writes the schema updates desired to a file for the DB Admin to run.
 375       * @param array $schemaUpdate
 376       */
 377  	private function writeSchemaUpdateFile( $schemaUpdate = array() ) {
 378          $updates = $this->updatesSkipped;
 379          $this->updatesSkipped = array();
 380  
 381          foreach ( $updates as $funcList ) {
 382              $func = $funcList[0];
 383              $arg = $funcList[1];
 384              $origParams = $funcList[2];
 385              call_user_func_array( $func, $arg );
 386              flush();
 387              $this->updatesSkipped[] = $origParams;
 388          }
 389      }
 390  
 391      /**
 392       * Do all the updates
 393       *
 394       * @param array $what What updates to perform
 395       */
 396  	public function doUpdates( $what = array( 'core', 'extensions', 'stats' ) ) {
 397          global $wgVersion;
 398  
 399          $this->db->begin( __METHOD__ );
 400          $what = array_flip( $what );
 401          $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
 402          if ( isset( $what['core'] ) ) {
 403              $this->runUpdates( $this->getCoreUpdateList(), false );
 404          }
 405          if ( isset( $what['extensions'] ) ) {
 406              $this->runUpdates( $this->getOldGlobalUpdates(), false );
 407              $this->runUpdates( $this->getExtensionUpdates(), true );
 408          }
 409  
 410          if ( isset( $what['stats'] ) ) {
 411              $this->checkStats();
 412          }
 413  
 414          $this->setAppliedUpdates( $wgVersion, $this->updates );
 415  
 416          if ( $this->fileHandle ) {
 417              $this->skipSchema = false;
 418              $this->writeSchemaUpdateFile();
 419              $this->setAppliedUpdates( "$wgVersion-schema", $this->updatesSkipped );
 420          }
 421  
 422          $this->db->commit( __METHOD__ );
 423      }
 424  
 425      /**
 426       * Helper function for doUpdates()
 427       *
 428       * @param array $updates Array of updates to run
 429       * @param bool $passSelf Whether to pass this object we calling external functions
 430       */
 431  	private function runUpdates( array $updates, $passSelf ) {
 432          $updatesDone = array();
 433          $updatesSkipped = array();
 434          foreach ( $updates as $params ) {
 435              $origParams = $params;
 436              $func = array_shift( $params );
 437              if ( !is_array( $func ) && method_exists( $this, $func ) ) {
 438                  $func = array( $this, $func );
 439              } elseif ( $passSelf ) {
 440                  array_unshift( $params, $this );
 441              }
 442              $ret = call_user_func_array( $func, $params );
 443              flush();
 444              if ( $ret !== false ) {
 445                  $updatesDone[] = $origParams;
 446              } else {
 447                  $updatesSkipped[] = array( $func, $params, $origParams );
 448              }
 449          }
 450          $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
 451          $this->updates = array_merge( $this->updates, $updatesDone );
 452      }
 453  
 454      /**
 455       * @param string $version
 456       * @param array $updates
 457       */
 458  	protected function setAppliedUpdates( $version, $updates = array() ) {
 459          $this->db->clearFlag( DBO_DDLMODE );
 460          if ( !$this->canUseNewUpdatelog() ) {
 461              return;
 462          }
 463          $key = "updatelist-$version-" . time();
 464          $this->db->insert( 'updatelog',
 465              array( 'ul_key' => $key, 'ul_value' => serialize( $updates ) ),
 466              __METHOD__ );
 467          $this->db->setFlag( DBO_DDLMODE );
 468      }
 469  
 470      /**
 471       * Helper function: check if the given key is present in the updatelog table.
 472       * Obviously, only use this for updates that occur after the updatelog table was
 473       * created!
 474       * @param string $key Name of the key to check for
 475       * @return bool
 476       */
 477  	public function updateRowExists( $key ) {
 478          $row = $this->db->selectRow(
 479              'updatelog',
 480              # Bug 65813
 481              '1 AS X',
 482              array( 'ul_key' => $key ),
 483              __METHOD__
 484          );
 485  
 486          return (bool)$row;
 487      }
 488  
 489      /**
 490       * Helper function: Add a key to the updatelog table
 491       * Obviously, only use this for updates that occur after the updatelog table was
 492       * created!
 493       * @param string $key Name of key to insert
 494       * @param string $val [optional] Value to insert along with the key
 495       */
 496  	public function insertUpdateRow( $key, $val = null ) {
 497          $this->db->clearFlag( DBO_DDLMODE );
 498          $values = array( 'ul_key' => $key );
 499          if ( $val && $this->canUseNewUpdatelog() ) {
 500              $values['ul_value'] = $val;
 501          }
 502          $this->db->insert( 'updatelog', $values, __METHOD__, 'IGNORE' );
 503          $this->db->setFlag( DBO_DDLMODE );
 504      }
 505  
 506      /**
 507       * Updatelog was changed in 1.17 to have a ul_value column so we can record
 508       * more information about what kind of updates we've done (that's what this
 509       * class does). Pre-1.17 wikis won't have this column, and really old wikis
 510       * might not even have updatelog at all
 511       *
 512       * @return bool
 513       */
 514  	protected function canUseNewUpdatelog() {
 515          return $this->db->tableExists( 'updatelog', __METHOD__ ) &&
 516              $this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ );
 517      }
 518  
 519      /**
 520       * Returns whether updates should be executed on the database table $name.
 521       * Updates will be prevented if the table is a shared table and it is not
 522       * specified to run updates on shared tables.
 523       *
 524       * @param string $name Table name
 525       * @return bool
 526       */
 527  	protected function doTable( $name ) {
 528          global $wgSharedDB, $wgSharedTables;
 529  
 530          // Don't bother to check $wgSharedTables if there isn't a shared database
 531          // or the user actually also wants to do updates on the shared database.
 532          if ( $wgSharedDB === null || $this->shared ) {
 533              return true;
 534          }
 535  
 536          if ( in_array( $name, $wgSharedTables ) ) {
 537              $this->output( "...skipping update to shared table $name.\n" );
 538              return false;
 539          } else {
 540              return true;
 541          }
 542      }
 543  
 544      /**
 545       * Before 1.17, we used to handle updates via stuff like
 546       * $wgExtNewTables/Fields/Indexes. This is nasty :) We refactored a lot
 547       * of this in 1.17 but we want to remain back-compatible for a while. So
 548       * load up these old global-based things into our update list.
 549       *
 550       * @return array
 551       */
 552  	protected function getOldGlobalUpdates() {
 553          global $wgExtNewFields, $wgExtNewTables, $wgExtModifiedFields,
 554              $wgExtNewIndexes;
 555  
 556          $updates = array();
 557  
 558          foreach ( $wgExtNewTables as $tableRecord ) {
 559              $updates[] = array(
 560                  'addTable', $tableRecord[0], $tableRecord[1], true
 561              );
 562          }
 563  
 564          foreach ( $wgExtNewFields as $fieldRecord ) {
 565              $updates[] = array(
 566                  'addField', $fieldRecord[0], $fieldRecord[1],
 567                  $fieldRecord[2], true
 568              );
 569          }
 570  
 571          foreach ( $wgExtNewIndexes as $fieldRecord ) {
 572              $updates[] = array(
 573                  'addIndex', $fieldRecord[0], $fieldRecord[1],
 574                  $fieldRecord[2], true
 575              );
 576          }
 577  
 578          foreach ( $wgExtModifiedFields as $fieldRecord ) {
 579              $updates[] = array(
 580                  'modifyField', $fieldRecord[0], $fieldRecord[1],
 581                  $fieldRecord[2], true
 582              );
 583          }
 584  
 585          return $updates;
 586      }
 587  
 588      /**
 589       * Get an array of updates to perform on the database. Should return a
 590       * multi-dimensional array. The main key is the MediaWiki version (1.12,
 591       * 1.13...) with the values being arrays of updates, identical to how
 592       * updaters.inc did it (for now)
 593       *
 594       * @return array
 595       */
 596      abstract protected function getCoreUpdateList();
 597  
 598      /**
 599       * Append an SQL fragment to the open file handle.
 600       *
 601       * @param string $filename File name to open
 602       */
 603  	public function copyFile( $filename ) {
 604          $this->db->sourceFile( $filename, false, false, false,
 605              array( $this, 'appendLine' )
 606          );
 607      }
 608  
 609      /**
 610       * Append a line to the open filehandle.  The line is assumed to
 611       * be a complete SQL statement.
 612       *
 613       * This is used as a callback for for sourceLine().
 614       *
 615       * @param string $line Text to append to the file
 616       * @return bool False to skip actually executing the file
 617       * @throws MWException
 618       */
 619  	public function appendLine( $line ) {
 620          $line = rtrim( $line ) . ";\n";
 621          if ( fwrite( $this->fileHandle, $line ) === false ) {
 622              throw new MWException( "trouble writing file" );
 623          }
 624  
 625          return false;
 626      }
 627  
 628      /**
 629       * Applies a SQL patch
 630       *
 631       * @param string $path Path to the patch file
 632       * @param bool $isFullPath Whether to treat $path as a relative or not
 633       * @param string $msg Description of the patch
 634       * @return bool False if patch is skipped.
 635       */
 636  	protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
 637          if ( $msg === null ) {
 638              $msg = "Applying $path patch";
 639          }
 640          if ( $this->skipSchema ) {
 641              $this->output( "...skipping schema change ($msg).\n" );
 642  
 643              return false;
 644          }
 645  
 646          $this->output( "$msg ..." );
 647  
 648          if ( !$isFullPath ) {
 649              $path = $this->db->patchPath( $path );
 650          }
 651          if ( $this->fileHandle !== null ) {
 652              $this->copyFile( $path );
 653          } else {
 654              $this->db->sourceFile( $path );
 655          }
 656          $this->output( "done.\n" );
 657  
 658          return true;
 659      }
 660  
 661      /**
 662       * Add a new table to the database
 663       *
 664       * @param string $name Name of the new table
 665       * @param string $patch Path to the patch file
 666       * @param bool $fullpath Whether to treat $patch path as a relative or not
 667       * @return bool False if this was skipped because schema changes are skipped
 668       */
 669  	protected function addTable( $name, $patch, $fullpath = false ) {
 670          if ( !$this->doTable( $name ) ) {
 671              return true;
 672          }
 673  
 674          if ( $this->db->tableExists( $name, __METHOD__ ) ) {
 675              $this->output( "...$name table already exists.\n" );
 676          } else {
 677              return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
 678          }
 679  
 680          return true;
 681      }
 682  
 683      /**
 684       * Add a new field to an existing table
 685       *
 686       * @param string $table Name of the table to modify
 687       * @param string $field Name of the new field
 688       * @param string $patch Path to the patch file
 689       * @param bool $fullpath Whether to treat $patch path as a relative or not
 690       * @return bool False if this was skipped because schema changes are skipped
 691       */
 692  	protected function addField( $table, $field, $patch, $fullpath = false ) {
 693          if ( !$this->doTable( $table ) ) {
 694              return true;
 695          }
 696  
 697          if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
 698              $this->output( "...$table table does not exist, skipping new field patch.\n" );
 699          } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
 700              $this->output( "...have $field field in $table table.\n" );
 701          } else {
 702              return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
 703          }
 704  
 705          return true;
 706      }
 707  
 708      /**
 709       * Add a new index to an existing table
 710       *
 711       * @param string $table Name of the table to modify
 712       * @param string $index Name of the new index
 713       * @param string $patch Path to the patch file
 714       * @param bool $fullpath Whether to treat $patch path as a relative or not
 715       * @return bool False if this was skipped because schema changes are skipped
 716       */
 717  	protected function addIndex( $table, $index, $patch, $fullpath = false ) {
 718          if ( !$this->doTable( $table ) ) {
 719              return true;
 720          }
 721  
 722          if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
 723              $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
 724          } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
 725              $this->output( "...index $index already set on $table table.\n" );
 726          } else {
 727              return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
 728          }
 729  
 730          return true;
 731      }
 732  
 733      /**
 734       * Drop a field from an existing table
 735       *
 736       * @param string $table Name of the table to modify
 737       * @param string $field Name of the old field
 738       * @param string $patch Path to the patch file
 739       * @param bool $fullpath Whether to treat $patch path as a relative or not
 740       * @return bool False if this was skipped because schema changes are skipped
 741       */
 742  	protected function dropField( $table, $field, $patch, $fullpath = false ) {
 743          if ( !$this->doTable( $table ) ) {
 744              return true;
 745          }
 746  
 747          if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
 748              return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
 749          } else {
 750              $this->output( "...$table table does not contain $field field.\n" );
 751          }
 752  
 753          return true;
 754      }
 755  
 756      /**
 757       * Drop an index from an existing table
 758       *
 759       * @param string $table Name of the table to modify
 760       * @param string $index Name of the index
 761       * @param string $patch Path to the patch file
 762       * @param bool $fullpath Whether to treat $patch path as a relative or not
 763       * @return bool False if this was skipped because schema changes are skipped
 764       */
 765  	protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
 766          if ( !$this->doTable( $table ) ) {
 767              return true;
 768          }
 769  
 770          if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
 771              return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
 772          } else {
 773              $this->output( "...$index key doesn't exist.\n" );
 774          }
 775  
 776          return true;
 777      }
 778  
 779      /**
 780       * Rename an index from an existing table
 781       *
 782       * @param string $table Name of the table to modify
 783       * @param string $oldIndex Old name of the index
 784       * @param string $newIndex New name of the index
 785       * @param bool $skipBothIndexExistWarning Whether to warn if both the
 786       * old and the new indexes exist.
 787       * @param string $patch Path to the patch file
 788       * @param bool $fullpath Whether to treat $patch path as a relative or not
 789       * @return bool False if this was skipped because schema changes are skipped
 790       */
 791  	protected function renameIndex( $table, $oldIndex, $newIndex,
 792          $skipBothIndexExistWarning, $patch, $fullpath = false
 793      ) {
 794          if ( !$this->doTable( $table ) ) {
 795              return true;
 796          }
 797  
 798          // First requirement: the table must exist
 799          if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
 800              $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
 801  
 802              return true;
 803          }
 804  
 805          // Second requirement: the new index must be missing
 806          if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
 807              $this->output( "...index $newIndex already set on $table table.\n" );
 808              if ( !$skipBothIndexExistWarning &&
 809                  $this->db->indexExists( $table, $oldIndex, __METHOD__ )
 810              ) {
 811                  $this->output( "...WARNING: $oldIndex still exists, despite it has " .
 812                      "been renamed into $newIndex (which also exists).\n" .
 813                      "            $oldIndex should be manually removed if not needed anymore.\n" );
 814              }
 815  
 816              return true;
 817          }
 818  
 819          // Third requirement: the old index must exist
 820          if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
 821              $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
 822  
 823              return true;
 824          }
 825  
 826          // Requirements have been satisfied, patch can be applied
 827          return $this->applyPatch(
 828              $patch,
 829              $fullpath,
 830              "Renaming index $oldIndex into $newIndex to table $table"
 831          );
 832      }
 833  
 834      /**
 835       * If the specified table exists, drop it, or execute the
 836       * patch if one is provided.
 837       *
 838       * Public @since 1.20
 839       *
 840       * @param string $table Table to drop.
 841       * @param string|bool $patch String of patch file that will drop the table. Default: false.
 842       * @param bool $fullpath Whether $patch is a full path. Default: false.
 843       * @return bool False if this was skipped because schema changes are skipped
 844       */
 845  	public function dropTable( $table, $patch = false, $fullpath = false ) {
 846          if ( !$this->doTable( $table ) ) {
 847              return true;
 848          }
 849  
 850          if ( $this->db->tableExists( $table, __METHOD__ ) ) {
 851              $msg = "Dropping table $table";
 852  
 853              if ( $patch === false ) {
 854                  $this->output( "$msg ..." );
 855                  $this->db->dropTable( $table, __METHOD__ );
 856                  $this->output( "done.\n" );
 857              } else {
 858                  return $this->applyPatch( $patch, $fullpath, $msg );
 859              }
 860          } else {
 861              $this->output( "...$table doesn't exist.\n" );
 862          }
 863  
 864          return true;
 865      }
 866  
 867      /**
 868       * Modify an existing field
 869       *
 870       * @param string $table Name of the table to which the field belongs
 871       * @param string $field Name of the field to modify
 872       * @param string $patch Path to the patch file
 873       * @param bool $fullpath Whether to treat $patch path as a relative or not
 874       * @return bool False if this was skipped because schema changes are skipped
 875       */
 876  	public function modifyField( $table, $field, $patch, $fullpath = false ) {
 877          if ( !$this->doTable( $table ) ) {
 878              return true;
 879          }
 880  
 881          $updateKey = "$table-$field-$patch";
 882          if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
 883              $this->output( "...$table table does not exist, skipping modify field patch.\n" );
 884          } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
 885              $this->output( "...$field field does not exist in $table table, " .
 886                  "skipping modify field patch.\n" );
 887          } elseif ( $this->updateRowExists( $updateKey ) ) {
 888              $this->output( "...$field in table $table already modified by patch $patch.\n" );
 889          } else {
 890              $this->insertUpdateRow( $updateKey );
 891  
 892              return $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
 893          }
 894  
 895          return true;
 896      }
 897  
 898      /**
 899       * Purge the objectcache table
 900       */
 901  	public function purgeCache() {
 902          global $wgLocalisationCacheConf;
 903          # We can't guarantee that the user will be able to use TRUNCATE,
 904          # but we know that DELETE is available to us
 905          $this->output( "Purging caches..." );
 906          $this->db->delete( 'objectcache', '*', __METHOD__ );
 907          if ( $wgLocalisationCacheConf['manualRecache'] ) {
 908              $this->rebuildLocalisationCache();
 909          }
 910          MessageBlobStore::getInstance()->clear();
 911          $this->output( "done.\n" );
 912      }
 913  
 914      /**
 915       * Check the site_stats table is not properly populated.
 916       */
 917  	protected function checkStats() {
 918          $this->output( "...site_stats is populated..." );
 919          $row = $this->db->selectRow( 'site_stats', '*', array( 'ss_row_id' => 1 ), __METHOD__ );
 920          if ( $row === false ) {
 921              $this->output( "data is missing! rebuilding...\n" );
 922          } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
 923              $this->output( "missing ss_total_pages, rebuilding...\n" );
 924          } else {
 925              $this->output( "done.\n" );
 926  
 927              return;
 928          }
 929          SiteStatsInit::doAllAndCommit( $this->db );
 930      }
 931  
 932      # Common updater functions
 933  
 934      /**
 935       * Sets the number of active users in the site_stats table
 936       */
 937  	protected function doActiveUsersInit() {
 938          $activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', false, __METHOD__ );
 939          if ( $activeUsers == -1 ) {
 940              $activeUsers = $this->db->selectField( 'recentchanges',
 941                  'COUNT( DISTINCT rc_user_text )',
 942                  array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ), __METHOD__
 943              );
 944              $this->db->update( 'site_stats',
 945                  array( 'ss_active_users' => intval( $activeUsers ) ),
 946                  array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 )
 947              );
 948          }
 949          $this->output( "...ss_active_users user count set...\n" );
 950      }
 951  
 952      /**
 953       * Populates the log_user_text field in the logging table
 954       */
 955  	protected function doLogUsertextPopulation() {
 956          if ( !$this->updateRowExists( 'populate log_usertext' ) ) {
 957              $this->output(
 958                  "Populating log_user_text field, printing progress markers. For large\n" .
 959                  "databases, you may want to hit Ctrl-C and do this manually with\n" .
 960                  "maintenance/populateLogUsertext.php.\n"
 961              );
 962  
 963              $task = $this->maintenance->runChild( 'PopulateLogUsertext' );
 964              $task->execute();
 965              $this->output( "done.\n" );
 966          }
 967      }
 968  
 969      /**
 970       * Migrate log params to new table and index for searching
 971       */
 972  	protected function doLogSearchPopulation() {
 973          if ( !$this->updateRowExists( 'populate log_search' ) ) {
 974              $this->output(
 975                  "Populating log_search table, printing progress markers. For large\n" .
 976                  "databases, you may want to hit Ctrl-C and do this manually with\n" .
 977                  "maintenance/populateLogSearch.php.\n" );
 978  
 979              $task = $this->maintenance->runChild( 'PopulateLogSearch' );
 980              $task->execute();
 981              $this->output( "done.\n" );
 982          }
 983      }
 984  
 985      /**
 986       * Updates the timestamps in the transcache table
 987       * @return bool
 988       */
 989  	protected function doUpdateTranscacheField() {
 990          if ( $this->updateRowExists( 'convert transcache field' ) ) {
 991              $this->output( "...transcache tc_time already converted.\n" );
 992  
 993              return true;
 994          }
 995  
 996          return $this->applyPatch( 'patch-tc-timestamp.sql', false,
 997              "Converting tc_time from UNIX epoch to MediaWiki timestamp" );
 998      }
 999  
1000      /**
1001       * Update CategoryLinks collation
1002       */
1003  	protected function doCollationUpdate() {
1004          global $wgCategoryCollation;
1005          if ( $this->db->fieldExists( 'categorylinks', 'cl_collation', __METHOD__ ) ) {
1006              if ( $this->db->selectField(
1007                  'categorylinks',
1008                  'COUNT(*)',
1009                  'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
1010                  __METHOD__
1011                  ) == 0
1012              ) {
1013                  $this->output( "...collations up-to-date.\n" );
1014  
1015                  return;
1016              }
1017  
1018              $this->output( "Updating category collations..." );
1019              $task = $this->maintenance->runChild( 'UpdateCollation' );
1020              $task->execute();
1021              $this->output( "...done.\n" );
1022          }
1023      }
1024  
1025      /**
1026       * Migrates user options from the user table blob to user_properties
1027       */
1028  	protected function doMigrateUserOptions() {
1029          if ( $this->db->tableExists( 'user_properties' ) ) {
1030              $cl = $this->maintenance->runChild( 'ConvertUserOptions', 'convertUserOptions.php' );
1031              $cl->execute();
1032              $this->output( "done.\n" );
1033          }
1034      }
1035  
1036      /**
1037       * Rebuilds the localisation cache
1038       */
1039  	protected function rebuildLocalisationCache() {
1040          /**
1041           * @var $cl RebuildLocalisationCache
1042           */
1043          $cl = $this->maintenance->runChild( 'RebuildLocalisationCache', 'rebuildLocalisationCache.php' );
1044          $this->output( "Rebuilding localisation cache...\n" );
1045          $cl->setForce();
1046          $cl->execute();
1047          $this->output( "done.\n" );
1048      }
1049  
1050      /**
1051       * Turns off content handler fields during parts of the upgrade
1052       * where they aren't available.
1053       */
1054  	protected function disableContentHandlerUseDB() {
1055          global $wgContentHandlerUseDB;
1056  
1057          if ( $wgContentHandlerUseDB ) {
1058              $this->output( "Turning off Content Handler DB fields for this part of upgrade.\n" );
1059              $this->holdContentHandlerUseDB = $wgContentHandlerUseDB;
1060              $wgContentHandlerUseDB = false;
1061          }
1062      }
1063  
1064      /**
1065       * Turns content handler fields back on.
1066       */
1067  	protected function enableContentHandlerUseDB() {
1068          global $wgContentHandlerUseDB;
1069  
1070          if ( $this->holdContentHandlerUseDB ) {
1071              $this->output( "Content Handler DB fields should be usable now.\n" );
1072              $wgContentHandlerUseDB = $this->holdContentHandlerUseDB;
1073          }
1074      }
1075  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1