MediaWiki master
DatabaseInstaller.php
Go to the documentation of this file.
1<?php
2
25namespace MediaWiki\Installer;
26
27use Exception;
29use MWException;
30use MWLBFactory;
31use RuntimeException;
32use Wikimedia\AtEase\AtEase;
39
46abstract class DatabaseInstaller {
47
53 public $parent;
54
58 public static $minimumVersion;
59
63 protected static $notMinimumVersionMessage;
64
70 public $db = null;
71
77 protected $internalDefaults = [];
78
84 protected $globalNames = [];
85
93 public static function meetsMinimumRequirement( IDatabase $conn ) {
94 $serverVersion = $conn->getServerVersion();
95 if ( version_compare( $serverVersion, static::$minimumVersion ) < 0 ) {
96 return Status::newFatal(
97 static::$notMinimumVersionMessage, static::$minimumVersion, $serverVersion
98 );
99 }
100
101 return Status::newGood();
102 }
103
107 abstract public function getName();
108
112 abstract public function isCompiled();
113
119 public function checkPrerequisites() {
120 return Status::newGood();
121 }
122
131 abstract public function openConnection();
132
139 abstract public function setupDatabase();
140
150 public function getConnection() {
151 if ( $this->db ) {
152 return new ConnectionStatus( $this->db );
153 }
154
155 $status = $this->openConnection();
156 if ( $status->isOK() ) {
157 $this->db = $status->value;
158 // Enable autocommit
159 $this->db->clearFlag( DBO_TRX );
160 $this->db->commit( __METHOD__ );
161 }
162
163 return $status;
164 }
165
174 private function stepApplySourceFile(
175 $sourceFileMethod,
176 $stepName,
177 $tableThatMustNotExist = false
178 ) {
179 $status = $this->getConnection();
180 if ( !$status->isOK() ) {
181 return $status;
182 }
183 $this->selectDatabase( $this->db, $this->getVar( 'wgDBname' ) );
184
185 if ( $tableThatMustNotExist && $this->db->tableExists( $tableThatMustNotExist, __METHOD__ ) ) {
186 $status->warning( "config-$stepName-tables-exist" );
187 $this->enableLB();
188
189 return $status;
190 }
191
192 $this->db->setFlag( DBO_DDLMODE );
193 $this->db->begin( __METHOD__ );
194
195 $error = $this->db->sourceFile(
196 call_user_func( [ $this, $sourceFileMethod ], $this->db )
197 );
198 if ( $error !== true ) {
199 $this->db->reportQueryError( $error, 0, '', __METHOD__ );
200 $this->db->rollback( __METHOD__ );
201 $status->fatal( "config-$stepName-tables-failed", $error );
202 } else {
203 $this->db->commit( __METHOD__ );
204 }
205 // Resume normal operations
206 if ( $status->isOK() ) {
207 $this->enableLB();
208 }
209
210 return $status;
211 }
212
218 public function createTables() {
219 return $this->stepApplySourceFile( 'getGeneratedSchemaPath', 'install', 'archive' );
220 }
221
227 public function createManualTables() {
228 return $this->stepApplySourceFile( 'getSchemaPath', 'install-manual' );
229 }
230
236 public function insertUpdateKeys() {
237 return $this->stepApplySourceFile( 'getUpdateKeysPath', 'updates', false );
238 }
239
248 private function getSqlFilePath( $db, $filename ) {
249 global $IP;
250
251 $dbmsSpecificFilePath = "$IP/maintenance/" . $db->getType() . "/$filename";
252 if ( file_exists( $dbmsSpecificFilePath ) ) {
253 return $dbmsSpecificFilePath;
254 } else {
255 return "$IP/maintenance/$filename";
256 }
257 }
258
266 public function getSchemaPath( $db ) {
267 return $this->getSqlFilePath( $db, 'tables.sql' );
268 }
269
276 public function getGeneratedSchemaPath( $db ) {
277 return $this->getSqlFilePath( $db, 'tables-generated.sql' );
278 }
279
287 public function getUpdateKeysPath( $db ) {
288 return $this->getSqlFilePath( $db, 'update-keys.sql' );
289 }
290
295 public function createExtensionTables() {
296 $status = $this->getConnection();
297 if ( !$status->isOK() ) {
298 return $status;
299 }
300 $this->enableLB();
301
302 // Now run updates to create tables for old extensions
303 $updater = DatabaseUpdater::newForDB( $this->db );
304 $updater->setAutoExtensionHookContainer( $this->parent->getAutoExtensionHookContainer() );
305 $updater->doUpdates( [ 'extensions' ] );
306
307 return $status;
308 }
309
315 abstract public function getLocalSettings();
316
322 public function getSchemaVars() {
323 return [];
324 }
325
332 public function setupSchemaVars() {
333 $status = $this->getConnection();
334 if ( $status->isOK() ) {
335 $status->getDB()->setSchemaVars( $this->getSchemaVars() );
336 } else {
337 $msg = __METHOD__ . ': unexpected error while establishing'
338 . ' a database connection with message: '
339 . $status->getMessage()->plain();
340 throw new RuntimeException( $msg );
341 }
342 }
343
349 public function enableLB() {
350 $status = $this->getConnection();
351 if ( !$status->isOK() ) {
352 throw new RuntimeException( __METHOD__ . ': unexpected DB connection error' );
353 }
354 $connection = $status->value;
355 $virtualDomains = array_merge(
356 $this->parent->getVirtualDomains(),
357 MWLBFactory::CORE_VIRTUAL_DOMAINS
358 );
359
360 $this->parent->resetMediaWikiServices( null, [
361 'DBLoadBalancerFactory' => static function () use ( $virtualDomains, $connection ) {
362 return LBFactorySingle::newFromConnection(
363 $connection,
364 [ 'virtualDomains' => $virtualDomains ]
365 );
366 }
367 ] );
368 }
369
376 public function doUpgrade() {
377 $this->setupSchemaVars();
378 $this->enableLB();
379
380 $ret = true;
381 ob_start( [ $this, 'outputHandler' ] );
382 $up = DatabaseUpdater::newForDB( $this->db );
383 try {
384 $up->doUpdates();
385 $up->purgeCache();
386 } catch ( MWException $e ) {
387 // TODO: Remove special casing in favour of MWExceptionRenderer
388 echo "\nAn error occurred:\n";
389 echo $e->getText();
390 $ret = false;
391 } catch ( Exception $e ) {
392 echo "\nAn error occurred:\n";
393 echo $e->getMessage();
394 $ret = false;
395 }
396 ob_end_flush();
397
398 return $ret;
399 }
400
406 public function preInstall() {
407 }
408
412 public function preUpgrade() {
413 }
414
419 public function getGlobalNames() {
420 return $this->globalNames;
421 }
422
428 public function __construct( $parent ) {
429 $this->parent = $parent;
430 }
431
439 protected static function checkExtension( $name ) {
440 return extension_loaded( $name );
441 }
442
447 public function getReadableName() {
448 // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
449 return wfMessage( 'config-type-' . $this->getName() )->text();
450 }
451
457 public function getGlobalDefaults() {
458 $defaults = [];
459 foreach ( $this->getGlobalNames() as $var ) {
460 if ( isset( $GLOBALS[$var] ) ) {
461 $defaults[$var] = $GLOBALS[$var];
462 }
463 }
464 return $defaults;
465 }
466
471 public function getInternalDefaults() {
473 }
474
481 public function getVar( $var, $default = null ) {
482 $defaults = $this->getGlobalDefaults();
483 $internal = $this->getInternalDefaults();
484 if ( isset( $defaults[$var] ) ) {
485 $default = $defaults[$var];
486 } elseif ( isset( $internal[$var] ) ) {
487 $default = $internal[$var];
488 }
489
490 return $this->parent->getVar( $var, $default );
491 }
492
498 public function setVar( $name, $value ) {
499 $this->parent->setVar( $name, $value );
500 }
501
502 abstract public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm;
503
504 abstract public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm;
505
516 public function needsUpgrade() {
517 $status = $this->getConnection();
518 if ( !$status->isOK() ) {
519 return false;
520 }
521
522 try {
523 $this->selectDatabase( $this->db, $this->getVar( 'wgDBname' ) );
524 } catch ( DBConnectionError $e ) {
525 // Don't catch DBConnectionError
526 throw $e;
527 } catch ( DBExpectedError $e ) {
528 return false;
529 }
530
531 return $this->db->tableExists( 'cur', __METHOD__ ) ||
532 $this->db->tableExists( 'revision', __METHOD__ );
533 }
534
540 public function populateInterwikiTable() {
541 $status = $this->getConnection();
542 if ( !$status->isOK() ) {
543 return $status;
544 }
545 $this->selectDatabase( $this->db, $this->getVar( 'wgDBname' ) );
546
547 $row = $this->db->newSelectQueryBuilder()
548 ->select( '1' )
549 ->from( 'interwiki' )
550 ->caller( __METHOD__ )->fetchRow();
551 if ( $row ) {
552 $status->warning( 'config-install-interwiki-exists' );
553
554 return $status;
555 }
556 global $IP;
557 AtEase::suppressWarnings();
558 $rows = file( "$IP/maintenance/interwiki.list",
559 FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
560 AtEase::restoreWarnings();
561 if ( !$rows ) {
562 return Status::newFatal( 'config-install-interwiki-list' );
563 }
564 $insert = $this->db->newInsertQueryBuilder()
565 ->insertInto( 'interwiki' );
566 foreach ( $rows as $row ) {
567 $row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
568 if ( $row == "" ) {
569 continue;
570 }
571 $row .= "|";
572 $insert->row(
573 array_combine(
574 [ 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ],
575 explode( '|', $row )
576 )
577 );
578 }
579 $insert->caller( __METHOD__ )->execute();
580
581 return Status::newGood();
582 }
583
584 public function outputHandler( $string ) {
585 return htmlspecialchars( $string );
586 }
587
594 protected function selectDatabase( Database $conn, string $database ) {
595 $schema = $conn->dbSchema();
596 $prefix = $conn->tablePrefix();
597
598 $conn->selectDomain( new DatabaseDomain(
599 $database,
600 // DatabaseDomain uses null for unspecified schemas
601 ( $schema !== '' ) ? $schema : null,
602 $prefix
603 ) );
604
605 return true;
606 }
607}
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(!defined( 'MEDIAWIKI')) if(ini_get('mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Definition Setup.php:98
MediaWiki exception.
getText()
Format plain text message for the current exception object.
MediaWiki-specific class for generating database load balancers.
Base class for DBMS-specific installation helper classes.
selectDatabase(Database $conn, string $database)
getConnectForm(WebInstaller $webInstaller)
Database $db
The database connection.
getConnection()
Connect to the database using the administrative user/password currently defined in the session.
getVar( $var, $default=null)
Get a variable, taking local defaults into account.
populateInterwikiTable()
Common function for databases that don't understand the MySQLish syntax of interwiki....
preUpgrade()
Allow DB installers a chance to make checks before upgrade.
insertUpdateKeys()
Insert update keys into table to prevent running unneeded updates.
needsUpgrade()
Determine whether an existing installation of MediaWiki is present in the configured administrative c...
array $internalDefaults
Internal variables for installation.
doUpgrade()
Perform database upgrades.
getSettingsForm(WebInstaller $webInstaller)
static string $notMinimumVersionMessage
Set by subclasses.
getGlobalNames()
Get an array of MW configuration globals that will be configured by this class.
static checkExtension( $name)
Convenience function.
array $globalNames
Array of MW configuration globals this class uses.
__construct( $parent)
Construct and initialise parent.
getSchemaVars()
Override this to provide DBMS-specific schema variables, to be substituted into tables....
getGeneratedSchemaPath( $db)
Return a path to the DBMS-specific automatically generated schema file.
getLocalSettings()
Get the DBMS-specific options for LocalSettings.php generation.
getGlobalDefaults()
Get a name=>value map of MW configuration globals for the default values.
static meetsMinimumRequirement(IDatabase $conn)
Whether the provided version meets the necessary requirements for this type.
static string $minimumVersion
Set by subclasses.
getReadableName()
Get the internationalised name for this DBMS.
createExtensionTables()
Create the tables for each extension the user enabled.
getUpdateKeysPath( $db)
Return a path to the DBMS-specific update key file, otherwise default to update-keys....
getSchemaPath( $db)
Return a path to the DBMS-specific schema file, otherwise default to tables.sql.
preInstall()
Allow DB installers a chance to make last-minute changes before installation occurs.
checkPrerequisites()
Checks for installation prerequisites other than those checked by isCompiled()
enableLB()
Set up LBFactory so that getPrimaryDatabase() etc.
openConnection()
Open a connection to the database using the administrative user/password currently defined in the ses...
setupDatabase()
Create the database and return a Status object indicating success or failure.
setupSchemaVars()
Set appropriate schema variables in the current database connection.
Installer $parent
The Installer object.
getName()
Return the internal name, e.g.
getInternalDefaults()
Get a name=>value map of internal variables used during installation.
createManualTables()
Create database tables from scratch.
createTables()
Create database tables from scratch from the automatically generated file.
setVar( $name, $value)
Convenience alias for $this->parent->setVar()
static newForDB(IMaintainableDatabase $db, $shared=false, Maintenance $maintenance=null)
Base installer class.
Definition Installer.php:90
Class for the core installer web interface.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Base class for the more common types of database errors.
Class to handle database/schema/prefix specifications for IDatabase.
Relational database abstraction object.
Definition Database.php:43
tablePrefix( $prefix=null)
Get/set the table prefix.
Definition Database.php:303
dbSchema( $schema=null)
Get/set the db schema.
Definition Database.php:318
selectDomain( $domain)
Set the current domain (database, schema, and table prefix)
Manage a single hardcoded database connection.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:36
getServerVersion()
A string describing the current software version.
getType()
Get the RDBMS type of the server (e.g.
const DBO_DDLMODE
Definition defines.php:16
const DBO_TRX
Definition defines.php:12