MediaWiki master
DatabaseInstaller.php
Go to the documentation of this file.
1<?php
2
25namespace MediaWiki\Installer;
26
27use Exception;
29use MWLBFactory;
30use RuntimeException;
31use Wikimedia\AtEase\AtEase;
38
45abstract class DatabaseInstaller {
46
52 public $parent;
53
57 public static $minimumVersion;
58
62 protected static $notMinimumVersionMessage;
63
69 public $db = null;
70
76 protected $internalDefaults = [];
77
83 protected $globalNames = [];
84
92 public static function meetsMinimumRequirement( IDatabase $conn ) {
93 $serverVersion = $conn->getServerVersion();
94 if ( version_compare( $serverVersion, static::$minimumVersion ) < 0 ) {
95 return Status::newFatal(
96 static::$notMinimumVersionMessage, static::$minimumVersion, $serverVersion
97 );
98 }
99
100 return Status::newGood();
101 }
102
106 abstract public function getName();
107
111 abstract public function isCompiled();
112
118 public function checkPrerequisites() {
119 return Status::newGood();
120 }
121
130 abstract public function openConnection();
131
138 abstract public function setupDatabase();
139
149 public function getConnection() {
150 if ( $this->db ) {
151 return new ConnectionStatus( $this->db );
152 }
153
154 $status = $this->openConnection();
155 if ( $status->isOK() ) {
156 $this->db = $status->value;
157 // Enable autocommit
158 $this->db->clearFlag( DBO_TRX );
159 $this->db->commit( __METHOD__ );
160 }
161
162 return $status;
163 }
164
173 private function stepApplySourceFile(
174 $sourceFileMethod,
175 $stepName,
176 $tableThatMustNotExist = false
177 ) {
178 $status = $this->getConnection();
179 if ( !$status->isOK() ) {
180 return $status;
181 }
182 $this->selectDatabase( $this->db, $this->getVar( 'wgDBname' ) );
183
184 if ( $tableThatMustNotExist && $this->db->tableExists( $tableThatMustNotExist, __METHOD__ ) ) {
185 $status->warning( "config-$stepName-tables-exist" );
186 $this->enableLB();
187
188 return $status;
189 }
190
191 $this->db->setFlag( DBO_DDLMODE );
192 $this->db->begin( __METHOD__ );
193
194 $error = $this->db->sourceFile(
195 call_user_func( [ $this, $sourceFileMethod ], $this->db )
196 );
197 if ( $error !== true ) {
198 $this->db->reportQueryError( $error, 0, '', __METHOD__ );
199 $this->db->rollback( __METHOD__ );
200 $status->fatal( "config-$stepName-tables-failed", $error );
201 } else {
202 $this->db->commit( __METHOD__ );
203 }
204 // Resume normal operations
205 if ( $status->isOK() ) {
206 $this->enableLB();
207 }
208
209 return $status;
210 }
211
217 public function createTables() {
218 return $this->stepApplySourceFile( 'getGeneratedSchemaPath', 'install', 'archive' );
219 }
220
226 public function createManualTables() {
227 return $this->stepApplySourceFile( 'getSchemaPath', 'install-manual' );
228 }
229
235 public function insertUpdateKeys() {
236 return $this->stepApplySourceFile( 'getUpdateKeysPath', 'updates', false );
237 }
238
247 private function getSqlFilePath( $db, $filename ) {
248 global $IP;
249
250 $dbmsSpecificFilePath = "$IP/maintenance/" . $db->getType() . "/$filename";
251 if ( file_exists( $dbmsSpecificFilePath ) ) {
252 return $dbmsSpecificFilePath;
253 } else {
254 return "$IP/maintenance/$filename";
255 }
256 }
257
265 public function getSchemaPath( $db ) {
266 return $this->getSqlFilePath( $db, 'tables.sql' );
267 }
268
275 public function getGeneratedSchemaPath( $db ) {
276 return $this->getSqlFilePath( $db, 'tables-generated.sql' );
277 }
278
286 public function getUpdateKeysPath( $db ) {
287 return $this->getSqlFilePath( $db, 'update-keys.sql' );
288 }
289
294 public function createExtensionTables() {
295 $status = $this->getConnection();
296 if ( !$status->isOK() ) {
297 return $status;
298 }
299 $this->enableLB();
300
301 // Now run updates to create tables for old extensions
302 $updater = DatabaseUpdater::newForDB( $this->db );
303 $updater->setAutoExtensionHookContainer( $this->parent->getAutoExtensionHookContainer() );
304 $updater->doUpdates( [ 'extensions' ] );
305
306 return $status;
307 }
308
314 abstract public function getLocalSettings();
315
321 public function getSchemaVars() {
322 return [];
323 }
324
331 public function setupSchemaVars() {
332 $status = $this->getConnection();
333 if ( $status->isOK() ) {
334 $status->getDB()->setSchemaVars( $this->getSchemaVars() );
335 } else {
336 $msg = __METHOD__ . ': unexpected error while establishing'
337 . ' a database connection with message: '
338 . $status->getMessage()->plain();
339 throw new RuntimeException( $msg );
340 }
341 }
342
348 public function enableLB() {
349 $status = $this->getConnection();
350 if ( !$status->isOK() ) {
351 throw new RuntimeException( __METHOD__ . ': unexpected DB connection error' );
352 }
353 $connection = $status->value;
354 $virtualDomains = array_merge(
355 $this->parent->getVirtualDomains(),
356 MWLBFactory::CORE_VIRTUAL_DOMAINS
357 );
358
359 $this->parent->resetMediaWikiServices( null, [
360 'DBLoadBalancerFactory' => static function () use ( $virtualDomains, $connection ) {
361 return LBFactorySingle::newFromConnection(
362 $connection,
363 [ 'virtualDomains' => $virtualDomains ]
364 );
365 }
366 ] );
367 }
368
374 public function doUpgrade() {
375 $this->setupSchemaVars();
376 $this->enableLB();
377
378 $ret = true;
379 ob_start( [ $this, 'outputHandler' ] );
380 $up = DatabaseUpdater::newForDB( $this->db );
381 try {
382 $up->doUpdates();
383 $up->purgeCache();
384 } catch ( Exception $e ) {
385 // TODO: Should this use MWExceptionRenderer?
386 echo "\nAn error occurred:\n";
387 echo $e->getMessage();
388 $ret = false;
389 }
390 ob_end_flush();
391
392 return $ret;
393 }
394
400 public function preInstall() {
401 }
402
406 public function preUpgrade() {
407 }
408
413 public function getGlobalNames() {
414 return $this->globalNames;
415 }
416
422 public function __construct( $parent ) {
423 $this->parent = $parent;
424 }
425
433 protected static function checkExtension( $name ) {
434 return extension_loaded( $name );
435 }
436
441 public function getReadableName() {
442 // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
443 return wfMessage( 'config-type-' . $this->getName() )->text();
444 }
445
451 public function getGlobalDefaults() {
452 $defaults = [];
453 foreach ( $this->getGlobalNames() as $var ) {
454 if ( isset( $GLOBALS[$var] ) ) {
455 $defaults[$var] = $GLOBALS[$var];
456 }
457 }
458 return $defaults;
459 }
460
465 public function getInternalDefaults() {
467 }
468
475 public function getVar( $var, $default = null ) {
476 $defaults = $this->getGlobalDefaults();
477 $internal = $this->getInternalDefaults();
478 if ( isset( $defaults[$var] ) ) {
479 $default = $defaults[$var];
480 } elseif ( isset( $internal[$var] ) ) {
481 $default = $internal[$var];
482 }
483
484 return $this->parent->getVar( $var, $default );
485 }
486
492 public function setVar( $name, $value ) {
493 $this->parent->setVar( $name, $value );
494 }
495
496 abstract public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm;
497
498 abstract public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm;
499
510 public function needsUpgrade() {
511 $status = $this->getConnection();
512 if ( !$status->isOK() ) {
513 return false;
514 }
515
516 try {
517 $this->selectDatabase( $this->db, $this->getVar( 'wgDBname' ) );
518 } catch ( DBConnectionError $e ) {
519 // Don't catch DBConnectionError
520 throw $e;
521 } catch ( DBExpectedError $e ) {
522 return false;
523 }
524
525 return $this->db->tableExists( 'cur', __METHOD__ ) ||
526 $this->db->tableExists( 'revision', __METHOD__ );
527 }
528
534 public function populateInterwikiTable() {
535 $status = $this->getConnection();
536 if ( !$status->isOK() ) {
537 return $status;
538 }
539 $this->selectDatabase( $this->db, $this->getVar( 'wgDBname' ) );
540
541 $row = $this->db->newSelectQueryBuilder()
542 ->select( '1' )
543 ->from( 'interwiki' )
544 ->caller( __METHOD__ )->fetchRow();
545 if ( $row ) {
546 $status->warning( 'config-install-interwiki-exists' );
547
548 return $status;
549 }
550 global $IP;
551 AtEase::suppressWarnings();
552 $rows = file( "$IP/maintenance/interwiki.list",
553 FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
554 AtEase::restoreWarnings();
555 if ( !$rows ) {
556 return Status::newFatal( 'config-install-interwiki-list' );
557 }
558 $insert = $this->db->newInsertQueryBuilder()
559 ->insertInto( 'interwiki' );
560 foreach ( $rows as $row ) {
561 $row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
562 if ( $row == "" ) {
563 continue;
564 }
565 $row .= "|";
566 $insert->row(
567 array_combine(
568 [ 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ],
569 explode( '|', $row )
570 )
571 );
572 }
573 $insert->caller( __METHOD__ )->execute();
574
575 return Status::newGood();
576 }
577
578 public function outputHandler( $string ) {
579 return htmlspecialchars( $string );
580 }
581
588 protected function selectDatabase( Database $conn, string $database ) {
589 $schema = $conn->dbSchema();
590 $prefix = $conn->tablePrefix();
591
592 $conn->selectDomain( new DatabaseDomain(
593 $database,
594 // DatabaseDomain uses null for unspecified schemas
595 ( $schema !== '' ) ? $schema : null,
596 $prefix
597 ) );
598
599 return true;
600 }
601}
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:99
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