Add a new type of database to the installer from extension

Decouple Installer services
Implement injection class Autoloader and i18n messages from extension.json
Implement extension selector by type
Add i18n message key `version-database`
Extensions for testing:
- https://github.com/MWStake/PerconaDB - real Percona extension
- https://github.com/killev/mediawiki-dbext2 - fake extension for test

Bug: T226857, T255151
Change-Id: I9ec8a18ad19283f6be67ac000110ac370afc0815
This commit is contained in:
ArtBaltai 2020-06-02 11:06:48 +03:00 committed by Peter Ovchyn
parent 52f1bfa61b
commit 46eabe275c
17 changed files with 629 additions and 87 deletions

View file

@ -237,6 +237,8 @@ For notes on 1.34.x and older releases, see HISTORY.
Doing so using that hook is now soft deprecated.
* A new BlockPermissionChecker service was introduced for checking
block-related permissions.
* The support of 'database' type of extensions has been added to allow 3d party
databases like Percona be used as storage. See T226857, T253248.
* …
=== External library changes in 1.35 ===
@ -1243,6 +1245,8 @@ because of Phabricator reports.
instead.
* Using Skin::addToBodyAttributes() method to add body attributes has been
deprecated. Use OutputPageBodyAttributes hook instead.
* Installer::getDBTypes has been hard deprecated in favor of
InstallerDBSupport::getDatabases
* …
=== Other changes in 1.35 ===

View file

@ -820,6 +820,29 @@ class LocalisationCache {
return $used;
}
/**
* @var string[]
*/
private $messagesDirs = [];
/**
* @var string[]
*/
private $additionalMessagesDirs = [];
/**
* Add additional path for looking for i18n localization messages
*
* @internal for Installer with DbType extensions
* @since 1.35
*
* @param string $name
* @param string $path
*/
public function addMessagesDir( string $name, string $path ) : void {
$this->additionalMessagesDirs[$name] = $path;
}
/**
* Gets the combined list of messages dirs from
* core and extensions
@ -837,7 +860,7 @@ class LocalisationCache {
'rest' => "$IP/includes/Rest/i18n",
'oojs-ui' => "$IP/resources/lib/ooui/i18n",
'paramvalidator' => "$IP/includes/libs/ParamValidator/i18n",
] + $this->options->get( 'MessagesDirs' );
] + $this->options->get( 'MessagesDirs' ) + $this->additionalMessagesDirs;
}
/**

View file

@ -20,6 +20,7 @@
* @file
* @ingroup Installer
*/
use MediaWiki\Installer\Services\InstallerDBSupport;
use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IMaintainableDatabase;
@ -195,8 +196,8 @@ abstract class DatabaseUpdater {
Maintenance $maintenance = null
) {
$type = $db->getType();
if ( in_array( $type, Installer::getDBTypes() ) ) {
$class = ucfirst( $type ) . 'Updater';
if ( InstallerDBSupport::getInstance()->hasDatabase( $type ) ) {
$class = InstallerDBSupport::getInstance()->getDBUpdaterClass( $type );
return new $class( $db, $shared, $maintenance );
} else {

View file

@ -24,6 +24,7 @@
* @ingroup Installer
*/
use MediaWiki\Installer\Services\InstallerDBSupport;
use MediaWiki\Interwiki\NullInterwikiLookup;
use MediaWiki\MediaWikiServices;
use MediaWiki\Shell\Shell;
@ -100,19 +101,9 @@ abstract class Installer {
protected $parserOptions;
/**
* Known database types. These correspond to the class names <type>Installer,
* and are also MediaWiki database types valid for $wgDBtype.
*
* To add a new type, create a <type>Installer class and a Database<type>
* class, and add a config-type-<type> message to MessagesEn.php.
*
* @var array
* @var InstallerDBSupport
*/
protected static $dbTypes = [
'mysql',
'postgres',
'sqlite',
];
protected $installerDbSupport;
/**
* A list of environment check methods called by doEnvironmentChecks().
@ -406,7 +397,6 @@ abstract class Installer {
*/
public function __construct() {
global $wgMemc, $wgUser, $wgObjectCaches;
$defaultConfig = new GlobalVarConfig(); // all the stuff from DefaultSettings.php
$installerConfig = self::getInstallerConfig( $defaultConfig );
@ -458,7 +448,9 @@ abstract class Installer {
$this->doEnvironmentPreps();
$this->compiledDBs = [];
foreach ( self::getDBTypes() as $type ) {
$this->installerDbSupport = InstallerDBSupport::getInstance();
foreach ( $this->installerDbSupport->getDatabases() as $type ) {
$installer = $this->getDBInstaller( $type );
if ( !$installer->isCompiled() ) {
@ -475,11 +467,13 @@ abstract class Installer {
/**
* Get a list of known DB types.
* @deprecated since 1.35 use InstallerDBSupport::getDatabases instead
*
* @return array
*/
public static function getDBTypes() {
return self::$dbTypes;
wfDeprecated( __METHOD__, '1.35' );
return InstallerDBSupport::getInstance()->getDatabases();
}
/**
@ -561,13 +555,16 @@ abstract class Installer {
/**
* Get the DatabaseInstaller class name for this type
* @deprecated since 1.35 use InstallerDBSupport instead
*
* @param string $type database type ($wgDBtype)
* @return string Class name
* @since 1.30
*/
public static function getDBInstallerClass( $type ) {
return ucfirst( $type ) . 'Installer';
wfDeprecated( __METHOD__, '1.35' );
return InstallerDBSupport::getInstance()->getDBInstallerClass( $type );
}
/**
@ -582,10 +579,8 @@ abstract class Installer {
$type = $this->getVar( 'wgDBtype' );
}
$type = strtolower( $type );
if ( !isset( $this->dbInstallers[$type] ) ) {
$class = self::getDBInstallerClass( $type );
$class = $this->installerDbSupport->getDBInstallerClass( $type );
$this->dbInstallers[$type] = new $class( $this );
}
@ -769,7 +764,7 @@ abstract class Installer {
$allNames = [];
// Messages: config-type-mysql, config-type-postgres, config-type-sqlite
foreach ( self::getDBTypes() as $name ) {
foreach ( $this->installerDbSupport->getDatabases() as $name ) {
$allNames[] = wfMessage( "config-type-$name" )->text();
}

View file

@ -0,0 +1,336 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Installer
*
* @author Art Baltai
*/
declare( strict_types = 1 );
namespace MediaWiki\Installer\Services;
use DatabaseInstaller;
use DatabaseUpdater;
use InvalidArgumentException;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MysqlInstaller;
use MysqlUpdater;
use PostgresInstaller;
use PostgresUpdater;
use Psr\Log\LoggerInterface;
use SqliteInstaller;
use SqliteUpdater;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\DatabaseMysqli;
use Wikimedia\Rdbms\DatabasePostgres;
use Wikimedia\Rdbms\DatabaseSqlite;
/**
* @since 1.35
*/
class InstallerDBSupport {
public const EXTENSION_TYPE_DATABASE = 'database';
/**
* @var InstallerDBSupport $instance
*/
private static $instance;
/**
* Known database types. These correspond to the class names <type>Installer,
* and are also MediaWiki database types valid for $wgDBtype.
*
* To add a new type, create a <type>Installer class and a Database<type>
* class, and add a config-type-<type> message to MessagesEn.php.
*
* @var array<string, array>
*/
private $databaseInfo = [
'mysql' => [
'installer' => MysqlInstaller::class,
'updater' => MysqlUpdater::class,
'driver' => DatabaseMysqli::class,
'extension' => 'core'
],
'postgres' => [
'installer' => PostgresInstaller::class,
'updater' => PostgresUpdater::class,
'driver' => DatabasePostgres::class,
'extension' => 'core'
],
'sqlite' => [
'installer' => SqliteInstaller::class,
'updater' => SqliteUpdater::class,
'driver' => DatabaseSqlite::class,
'extension' => 'core'
]
];
/**
* @var InstallerExtensionRegistration
*/
private $extensionRegistration;
/**
* @var LoggerInterface
*/
private $logger;
public static function getInstance(): self {
global $IP, $wgExtensionDirectory;
if ( !isset( self::$instance ) ) {
$extensionDir = $wgExtensionDirectory ?: "$IP/extensions";
$installerRegistration = new InstallerExtensionRegistration(
$extensionDir,
MediaWikiServices::getInstance()->getLocalisationCache()
);
self::$instance = new InstallerDBSupport(
$installerRegistration,
LoggerFactory::getInstance( 'Installer' )
);
self::$instance->registerDbExtensions(
( new InstallerExtensionSelector( $extensionDir ) )
->getExtOptionsByType( self::EXTENSION_TYPE_DATABASE )
);
}
return self::$instance;
}
private function __construct(
InstallerExtensionRegistration $extensionRegistration,
LoggerInterface $logger
) {
$this->extensionRegistration = $extensionRegistration;
$this->logger = $logger;
}
private function registerDbExtensions( iterable $extDbOptionsByType ): bool {
$registered = false;
foreach ( $extDbOptionsByType as $extensionName => $extDbOptions ) {
$result = $this->registerDbExtension(
$extensionName,
$extDbOptions
);
$registered = $result || $registered;
}
return $registered;
}
private function registerDbExtension(
string $extensionName,
array $extJsonOptions
): bool {
if ( !isset( $extJsonOptions['Providers']['Databases'] ) ) {
return false;
}
$newDatabases = $extJsonOptions['Providers']['Databases'];
$isRegistred = false;
foreach ( $newDatabases as $database => $options ) {
if ( is_numeric( $database ) ) {
continue;
}
if ( $isRegistred === false ) {
$this->extensionRegistration->register(
$extensionName,
$extJsonOptions
);
$isRegistred = true;
}
$this->registerDatabase(
$database,
$options['Installer'] ?? $this->getInstallerClassAuto( $database ),
$options['Updater'] ?? $this->getUpdaterClassAuto( $database ),
$options['Driver'] ?? $this->getDriverClassAuto( $database ),
$extensionName
);
}
return $isRegistred;
}
private function isDatabaseInfoValid(
string $dbInstaller,
string $dbUpdater,
string $dbDriver
): bool {
$isValid = true;
if ( !is_subclass_of( $dbInstaller, DatabaseInstaller::class ) ) {
$this->logger->warning( 'Database `Installer` should be a subclass of '
. DatabaseInstaller::class );
$isValid = false;
}
if ( !is_subclass_of( $dbUpdater, DatabaseUpdater::class ) ) {
$this->logger->warning( 'Database `Updater` should be a subclass of '
. DatabaseUpdater::class );
$isValid = false;
}
if ( !is_subclass_of( $dbDriver, Database::class ) ) {
$this->logger->warning( 'Database `Driver` should be a subclass of ' . Database::class );
$isValid = false;
}
return $isValid;
}
/**
* @param string $database
* @param string $dbInstaller
* @param string $dbUpdater
* @param string $dbDriver
* @param string $extensionName
*/
private function registerDatabase(
string $database,
string $dbInstaller,
string $dbUpdater,
string $dbDriver,
string $extensionName
): void {
if ( !$this->isDatabaseInfoValid( $dbInstaller, $dbUpdater, $dbDriver ) ) {
return;
}
$this->databaseInfo[ strtolower( $database ) ] = [
'installer' => $dbInstaller,
'updater' => $dbUpdater,
'driver' => $dbDriver,
'extension' => $extensionName,
];
}
/**
* Get a list of known DB types.
*
* @return string[]
*/
public function getDatabases(): array {
return array_keys( $this->databaseInfo );
}
/**
* Checks wheather given database type is registered
* @param string $database
*
* @return bool
*/
public function hasDatabase( string $database ): bool {
return array_key_exists( $database, $this->databaseInfo );
}
/**
* Get the database installer class for given database type, throws an
* InvalidArgumentException if no given database registerred
*
* @param string $database
* @return string Class name
* @throws InvalidArgumentException
*/
public function getDBInstallerClass( string $database ): string {
if ( !isset( $this->databaseInfo[strtolower( $database )] ) ) {
throw new InvalidArgumentException( __METHOD__ .
" no registered database found for type '$database'" );
}
return $this->databaseInfo[strtolower( $database )]['installer'];
}
/**
* Get the database updater class for given database type, throws an
* InvalidArgumentException if no given database registerred
*
* @param string $database
* @return string class name
* @throws InvalidArgumentException
*/
public function getDBUpdaterClass( string $database ): string {
if ( !isset( $this->databaseInfo[strtolower( $database )] ) ) {
throw new InvalidArgumentException( __METHOD__ .
" no registered database found for type '$database'" );
}
return $this->databaseInfo[strtolower( $database )]['updater'];
}
/**
* Get the database driver class for given database type, throws an
* InvalidArgumentException if no given database registerred
*
* @param string $database
* @return string class name
* @throws InvalidArgumentException
*/
public function getDBDriverClass( string $database ): string {
if ( !isset( $this->databaseInfo[strtolower( $database )] ) ) {
throw new InvalidArgumentException( __METHOD__ .
" no registered database found for type '$database'" );
}
return $this->databaseInfo[strtolower( $database )]['driver'];
}
/**
* Gets extension name that implements classes for given database type, throws an
* InvalidArgumentException if no given database registerred
*
* @param string $database
* @return string|null
* @throws InvalidArgumentException
*/
public function getExtensionNameForDatabase(
string $database
): string {
if ( !isset( $this->databaseInfo[strtolower( $database )] ) ) {
throw new InvalidArgumentException( __METHOD__ .
" no registered database found for type '$database'" );
}
return $this->databaseInfo[strtolower( $database )]['extension'];
}
/**
* Generate default name for database Installer
*
* @param string $type database type ($wgDBtype)
* @return string Class name
*/
private function getInstallerClassAuto( string $type ): string {
return ucfirst( $type ) . 'Installer';
}
/**
* Generate default name for database Updater
*
* @param string $type database type ($wgDBtype)
* @return string Class name
*/
private function getUpdaterClassAuto( string $type ): string {
return ucfirst( $type ) . 'Updater';
}
/**
* Generate default name for database Driver
*
* @param string $type database type ($wgDBtype)
* @return string Class name
*/
private function getDriverClassAuto( string $type ): string {
return 'Database' . ucfirst( $type );
}
}

View file

@ -0,0 +1,110 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Installer
*
* @author Art Baltai
*/
declare( strict_types = 1 );
namespace MediaWiki\Installer\Services;
use ExtensionRegistry;
use LocalisationCache;
/**
* @since 1.35
*/
class InstallerExtensionRegistration {
/** @var string */
private $extensionDir;
/** @var LocalisationCache */
private $localisationCache;
public function __construct( string $extensionDir, LocalisationCache $localisationCache ) {
$this->extensionDir = $extensionDir;
$this->localisationCache = $localisationCache;
}
/**
* Register class autoloader, i18n messages
* @param string $extensionName
* @param array $extJsonOptions
*/
public function register(
string $extensionName,
array $extJsonOptions
): void {
$this->registerClassAutoloader( $extensionName, $extJsonOptions );
$this->registerMessagesDirs( $extensionName, $extJsonOptions );
}
public function registerClassAutoloader(
string $extensionName,
array $extJsonOptions
): void {
global $wgAutoloadLocalClasses;
$extPath = $this->getExtensionPath( $extensionName );
ExtensionRegistry::exportAutoloadClassesAndNamespaces(
$extPath,
$extJsonOptions
);
// important for upgrade (mw-config/?page=ExistingWiki) with existing LocalSettings.php
if (
!empty( $extJsonOptions['AutoloadClasses'] )
&& is_array( $extJsonOptions['AutoloadClasses'] )
) {
foreach ( $extJsonOptions['AutoloadClasses'] as $extensionName => $path ) {
$wgAutoloadLocalClasses[$extensionName] = "$extPath/$path";
}
}
}
public function registerMessagesDirs(
string $extensionName,
array $extJsonOptions
): void {
if ( !is_array( $extJsonOptions['MessagesDirs'] ?? null ) ) {
return;
}
foreach ( $extJsonOptions['MessagesDirs'] as $messageType => $messagesDirs ) {
$messagesDirs = (array)$messagesDirs;
foreach ( $messagesDirs as $key => $messageDir ) {
$i18nDir = $this->getExtensionPath( $extensionName ) . "/$messageDir";
if ( !is_dir( $i18nDir ) ) {
continue;
}
$this->localisationCache->addMessagesDir(
count( $messagesDirs ) === 1
? $messageType
: "{$messageType}_{$key}",
$i18nDir
);
}
}
}
private function getExtensionPath( string $extensionName ) : string {
return "{$this->extensionDir}/{$extensionName}";
}
}

View file

@ -0,0 +1,104 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Installer
*
* @author Art Baltai
*/
declare( strict_types = 1 );
namespace MediaWiki\Installer\Services;
use Generator;
/**
* @since 1.35
*/
class InstallerExtensionSelector {
/**
* @var string
*/
private $extensionDir;
/**
* @var string[]|null
*/
private $extensionJsonPath;
public function __construct( string $extensionDir ) {
$this->extensionDir = $extensionDir;
}
private function getExtensionJsonPaths() : array {
if ( $this->extensionJsonPath === null ) {
$this->extensionJsonPath = [];
if ( is_dir( $this->extensionDir ) ) {
$dh = opendir( $this->extensionDir );
while ( ( $extension = readdir( $dh ) ) !== false ) {
if ( mb_substr( $extension, 0, 1 ) === '.' ) {
continue;
}
$jsonPath = "{$this->extensionDir}/{$extension}/extension.json";
if ( is_file( $jsonPath ) && is_readable( $jsonPath ) ) {
$this->extensionJsonPath[$extension] = $jsonPath;
}
}
closedir( $dh );
uksort( $this->extensionJsonPath, 'strnatcasecmp' );
} // @todo else:warning?
}
return $this->extensionJsonPath;
}
/**
* Returns a Generator for interating through all exntension.json files converterd
* into array. Key is string of exntension name and value is decoded exntension.json.
*
* @return Generator
*/
private function getExtensionJsons() : Generator {
foreach ( $this->getExtensionJsonPaths() as $extension => $jsonPath ) {
$json = file_get_contents( $jsonPath );
$options = json_decode( $json, true );
if ( !is_array( $options ) ) {
continue;
}
yield $extension => $options;
}
}
/**
* Returns a Generator for interating through extension's options
* filtered by certain type.
*
* @param string $type
* @return Generator
*/
public function getExtOptionsByType( string $type ) : Generator {
foreach ( $this->getExtensionJsons() as $extensionName => $options ) {
if ( $type === ( $options['type'] ?? null ) ) {
yield $extensionName => $options;
}
}
}
}

View file

@ -18,6 +18,7 @@
* @file
* @ingroup Installer
*/
use MediaWiki\Installer\Services\InstallerDBSupport;
class WebInstallerDBConnect extends WebInstallerPage {
@ -50,7 +51,7 @@ class WebInstallerDBConnect extends WebInstallerPage {
// Messages: config-dbsupport-mysql, config-dbsupport-postgres, config-dbsupport-sqlite
$dbSupport = '';
foreach ( Installer::getDBTypes() as $type ) {
foreach ( InstallerDBSupport::getInstance()->getDatabases() as $type ) {
$dbSupport .= wfMessage( "config-dbsupport-$type" )->plain() . "\n";
}
$this->addHTML( $this->parent->getInfoBox(

View file

@ -19,6 +19,8 @@
* @ingroup Installer
*/
use MediaWiki\Installer\Services\InstallerDBSupport;
class WebInstallerExistingWiki extends WebInstallerPage {
/**
@ -140,7 +142,7 @@ class WebInstallerExistingWiki extends WebInstallerPage {
protected function handleExistingUpgrade( $vars ) {
// Check $wgDBtype
if ( !isset( $vars['wgDBtype'] ) ||
!in_array( $vars['wgDBtype'], Installer::getDBTypes() )
!InstallerDBSupport::getInstance()->hasDatabase( $vars['wgDBtype'] )
) {
return Status::newFatal( 'config-localsettings-connection-error', '' );
}

View file

@ -19,6 +19,7 @@
* @ingroup Installer
*/
use MediaWiki\Installer\Services\InstallerDBSupport;
use Wikimedia\IPUtils;
class WebInstallerOptions extends WebInstallerPage {
@ -147,7 +148,14 @@ class WebInstallerOptions extends WebInstallerPage {
$this->getFieldsetEnd();
$this->addHTML( $skinHtml );
$extensions = $this->parent->findExtensions()->value;
$extensions = array_filter(
$this->parent->findExtensions()->value,
function ( $extensionInfo ) {
return ( $extensionInfo['type'] ?? null )
!== InstallerDBSupport::EXTENSION_TYPE_DATABASE;
}
);
'@phan-var array[] $extensions';
$dependencyMap = [];
@ -182,7 +190,7 @@ class WebInstallerOptions extends WebInstallerPage {
'class' => 'config-ext-input'
];
$labelAttribs = [];
$fullDepList = [];
if ( isset( $info['requires']['extensions'] ) ) {
$dependencyMap[$ext]['extensions'] = $info['requires']['extensions'];
$labelAttribs['class'] = 'mw-ext-with-dependencies';

View file

@ -30,6 +30,7 @@ use Exception;
use HashBagOStuff;
use InvalidArgumentException;
use LogicException;
use MediaWiki\Installer\Services\InstallerDBSupport;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
@ -392,7 +393,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
* @since 1.18
*/
final public static function factory( $type, $params = [], $connect = self::NEW_CONNECTED ) {
$class = self::getClass( $type, $params['driver'] ?? null );
$class = InstallerDBSupport::getInstance()->getDBDriverClass( $type );
if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) {
$params += [
@ -453,64 +454,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
self::ATTR_SCHEMAS_AS_TABLE_GROUPS => false
];
$class = self::getClass( $dbType, $driver );
$class = InstallerDBSupport::getInstance()->getDBDriverClass( $dbType );
return call_user_func( [ $class, 'getAttributes' ] ) + $defaults;
}
/**
* @param string $dbType A possible DB type (sqlite, mysql, postgres,...)
* @param string|null $driver Optional name of a specific DB client driver
* @return string Database subclass name to use
* @throws InvalidArgumentException
*/
private static function getClass( $dbType, $driver = null ) {
// For database types with built-in support, the below maps type to IDatabase
// implementations. For types with multiple driver implementations (PHP extensions),
// an array can be used, keyed by extension name. In case of an array, the
// optional 'driver' parameter can be used to force a specific driver. Otherwise,
// we auto-detect the first available driver. For types without built-in support,
// an class named "Database<Type>" us used, eg. DatabaseFoo for type 'foo'.
static $builtinTypes = [
'mysql' => [ 'mysqli' => DatabaseMysqli::class ],
'sqlite' => DatabaseSqlite::class,
'postgres' => DatabasePostgres::class,
];
$dbType = strtolower( $dbType );
$class = false;
if ( isset( $builtinTypes[$dbType] ) ) {
$possibleDrivers = $builtinTypes[$dbType];
if ( is_string( $possibleDrivers ) ) {
$class = $possibleDrivers;
} elseif ( (string)$driver !== '' ) {
if ( !isset( $possibleDrivers[$driver] ) ) {
throw new InvalidArgumentException( __METHOD__ .
" type '$dbType' does not support driver '{$driver}'" );
}
$class = $possibleDrivers[$driver];
} else {
foreach ( $possibleDrivers as $posDriver => $possibleClass ) {
if ( extension_loaded( $posDriver ) ) {
$class = $possibleClass;
break;
}
}
}
} else {
$class = 'Database' . ucfirst( $dbType );
}
if ( $class === false ) {
throw new InvalidArgumentException( __METHOD__ .
" no viable database extension found for type '$dbType'" );
}
return $class;
}
/**
* @return array Map of (Database::ATTR_* constant => value)
* @since 1.31

View file

@ -24,6 +24,7 @@
*/
use MediaWiki\ExtensionInfo;
use MediaWiki\Installer\Services\InstallerDBSupport;
use MediaWiki\MediaWikiServices;
/**
@ -416,6 +417,8 @@ class SpecialVersion extends SpecialPage {
'antispam' => wfMessage( 'version-antispam' )->text(),
'skin' => wfMessage( 'version-skins' )->text(),
'api' => wfMessage( 'version-api' )->text(),
InstallerDBSupport::EXTENSION_TYPE_DATABASE =>
wfMessage( 'version-database' )->text(),
'other' => wfMessage( 'version-other' )->text(),
];

View file

@ -3561,6 +3561,7 @@
"version-variables": "Variables",
"version-editors": "Editors",
"version-antispam": "Spam prevention",
"version-database": "Custom database support",
"version-api": "API",
"version-other": "Other",
"version-mediahandlers": "Media handlers",

View file

@ -3778,6 +3778,7 @@
"version-antispam": "Part of [[Special:Version]].\nThis message is followed by the list of SPAM prevention extensions.",
"version-api": "{{optional}}",
"version-other": "{{Identical|Other}}",
"version-database": "Database type",
"version-mediahandlers": "Used in [[Special:Version]]. It is the title of a section for media handler extensions (e.g. [[mw:Extension:OggHandler]]).\nThere are no such extensions here, so look at [[wikipedia:Special:Version]] for an example.",
"version-hooks": "Shown in [[Special:Version]]\n{{Identical|Hook}}",
"version-parser-extensiontags": "Part of [[Special:Version]].\nThis message is followed by the list of parser extension tags like <code><nowiki><charinsert></nowiki></code>, <code><nowiki><coordinates></nowiki></code>, etc.",

View file

@ -27,6 +27,7 @@
require_once __DIR__ . '/Maintenance.php';
use MediaWiki\Installer\Services\InstallerDBSupport;
use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\DatabaseSqlite;
@ -141,8 +142,9 @@ class UpdateMediaWiki extends Maintenance {
$db = $this->getDB( DB_MASTER );
# Check to see whether the database server meets the minimum requirements
/** @var DatabaseInstaller $dbInstallerClass */
$dbInstallerClass = Installer::getDBInstallerClass( $db->getType() );
/** @var string|DatabaseInstaller $dbInstallerClass */
$dbInstallerClass = InstallerDBSupport::getInstance()
->getDBInstallerClass( $db->getType() );
$status = $dbInstallerClass::meetsMinimumRequirement( $db->getServerVersion() );
if ( !$status->isOK() ) {
// This might output some wikitext like <strong> but it should be comprehensible

View file

@ -30,9 +30,9 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
$m = Database::NEW_UNCONNECTED; // no-connect mode
$p = [ 'host' => 'localhost', 'user' => 'me', 'password' => 'myself', 'dbname' => 'i' ];
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'mysqli', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySqli', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySQLi', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'mysql', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySql', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySQL', $p, $m ) );
$this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'postgres', $p, $m ) );
$this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'Postgres', $p, $m ) );

View file

@ -21,6 +21,9 @@
<testsuite name="languages">
<directory>languages</directory>
</testsuite>
<testsuite name="rdbms">
<directory>includes/libs/rdbms</directory>
</testsuite>
<testsuite name="parsertests">
<file>suites/CoreParserTestSuite.php</file>
<file>suites/ExtensionsParserTestSuite.php</file>