Merge "rdbms: Move Database::factory() to DatabaseFactory service"
This commit is contained in:
commit
c48a61b247
18 changed files with 308 additions and 155 deletions
|
|
@ -1804,6 +1804,7 @@ $wgAutoloadLocalClasses = [
|
|||
'Wikimedia\\Rdbms\\DBUnexpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBUnexpectedError.php',
|
||||
'Wikimedia\\Rdbms\\Database' => __DIR__ . '/includes/libs/rdbms/database/Database.php',
|
||||
'Wikimedia\\Rdbms\\DatabaseDomain' => __DIR__ . '/includes/libs/rdbms/database/domain/DatabaseDomain.php',
|
||||
'Wikimedia\\Rdbms\\DatabaseFactory' => __DIR__ . '/includes/libs/rdbms/database/DatabaseFactory.php',
|
||||
'Wikimedia\\Rdbms\\DatabaseMysqlBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqlBase.php',
|
||||
'Wikimedia\\Rdbms\\DatabaseMysqli' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqli.php',
|
||||
'Wikimedia\\Rdbms\\DatabasePostgres' => __DIR__ . '/includes/libs/rdbms/database/DatabasePostgres.php',
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ use Wikimedia\NonSerializable\NonSerializableTrait;
|
|||
use Wikimedia\ObjectFactory\ObjectFactory;
|
||||
use Wikimedia\Parsoid\Config\DataAccess;
|
||||
use Wikimedia\Parsoid\Config\SiteConfig;
|
||||
use Wikimedia\Rdbms\DatabaseFactory;
|
||||
use Wikimedia\Rdbms\ILoadBalancer;
|
||||
use Wikimedia\Rdbms\LBFactory;
|
||||
use Wikimedia\RequestTimeout\CriticalSectionProvider;
|
||||
|
|
@ -918,6 +919,14 @@ class MediaWikiServices extends ServiceContainer {
|
|||
return $this->getService( 'DatabaseBlockStore' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.39
|
||||
* @return DatabaseFactory
|
||||
*/
|
||||
public function getDatabaseFactory(): DatabaseFactory {
|
||||
return $this->getService( 'DatabaseFactory' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.33
|
||||
* @return DateFormatterFactory
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ use Wikimedia\Parsoid\Config\Api\SiteConfig as ApiSiteConfig;
|
|||
use Wikimedia\Parsoid\Config\DataAccess;
|
||||
use Wikimedia\Parsoid\Config\SiteConfig;
|
||||
use Wikimedia\Parsoid\Parsoid;
|
||||
use Wikimedia\Rdbms\DatabaseFactory;
|
||||
use Wikimedia\RequestTimeout\CriticalSectionProvider;
|
||||
use Wikimedia\RequestTimeout\RequestTimeout;
|
||||
use Wikimedia\Services\RecursiveServiceDependencyException;
|
||||
|
|
@ -525,6 +526,10 @@ return [
|
|||
);
|
||||
},
|
||||
|
||||
'DatabaseFactory' => static function ( MediaWikiServices $services ): DatabaseFactory {
|
||||
return new DatabaseFactory();
|
||||
},
|
||||
|
||||
'DateFormatterFactory' => static function ( MediaWikiServices $services ): DateFormatterFactory {
|
||||
return new DateFormatterFactory();
|
||||
},
|
||||
|
|
@ -591,7 +596,8 @@ return [
|
|||
$srvCache,
|
||||
$wanCache,
|
||||
$services->getCriticalSectionProvider(),
|
||||
$services->getStatsdDataFactory()
|
||||
$services->getStatsdDataFactory(),
|
||||
$services->getDatabaseFactory()
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ use MediaWiki\Logger\LoggerFactory;
|
|||
use MediaWiki\MainConfigNames;
|
||||
use Wikimedia\Rdbms\ChronologyProtector;
|
||||
use Wikimedia\Rdbms\DatabaseDomain;
|
||||
use Wikimedia\Rdbms\DatabaseFactory;
|
||||
use Wikimedia\Rdbms\IDatabase;
|
||||
use Wikimedia\Rdbms\ILBFactory;
|
||||
use Wikimedia\Rdbms\LBFactory;
|
||||
|
|
@ -95,6 +96,10 @@ class MWLBFactory {
|
|||
* @var StatsdDataFactoryInterface
|
||||
*/
|
||||
private $statsdDataFactory;
|
||||
/**
|
||||
* @var DatabaseFactory
|
||||
*/
|
||||
private $databaseFactory;
|
||||
|
||||
/**
|
||||
* @param ServiceOptions $options
|
||||
|
|
@ -104,6 +109,7 @@ class MWLBFactory {
|
|||
* @param WANObjectCache $wanCache
|
||||
* @param CriticalSectionProvider $csProvider
|
||||
* @param StatsdDataFactoryInterface $statsdDataFactory
|
||||
* @param DatabaseFactory $databaseFactory
|
||||
*/
|
||||
public function __construct(
|
||||
ServiceOptions $options,
|
||||
|
|
@ -112,7 +118,8 @@ class MWLBFactory {
|
|||
BagOStuff $srvCache,
|
||||
WANObjectCache $wanCache,
|
||||
CriticalSectionProvider $csProvider,
|
||||
StatsdDataFactoryInterface $statsdDataFactory
|
||||
StatsdDataFactoryInterface $statsdDataFactory,
|
||||
DatabaseFactory $databaseFactory
|
||||
) {
|
||||
$this->options = $options;
|
||||
$this->readOnlyMode = $readOnlyMode;
|
||||
|
|
@ -121,6 +128,7 @@ class MWLBFactory {
|
|||
$this->wanCache = $wanCache;
|
||||
$this->csProvider = $csProvider;
|
||||
$this->statsdDataFactory = $statsdDataFactory;
|
||||
$this->databaseFactory = $databaseFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -212,6 +220,7 @@ class MWLBFactory {
|
|||
$lbConf['cpStash'] = $this->cpStash;
|
||||
$lbConf['srvCache'] = $this->srvCache;
|
||||
$lbConf['wanCache'] = $this->wanCache;
|
||||
$lbConf['databaseFactory'] = $this->databaseFactory;
|
||||
|
||||
return $lbConf;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
* @ingroup Installer
|
||||
*/
|
||||
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use Wikimedia\Rdbms\Database;
|
||||
use Wikimedia\Rdbms\DBConnectionError;
|
||||
use Wikimedia\Rdbms\DBQueryError;
|
||||
|
|
@ -397,7 +398,7 @@ class MysqlInstaller extends DatabaseInstaller {
|
|||
if ( !$create ) {
|
||||
// Test the web account
|
||||
try {
|
||||
Database::factory( 'mysql', [
|
||||
MediaWikiServices::getInstance()->getDatabaseFactory()->create( 'mysql', [
|
||||
'host' => $this->getVar( 'wgDBserver' ),
|
||||
'user' => $this->getVar( 'wgDBuser' ),
|
||||
'password' => $this->getVar( 'wgDBpassword' ),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
* @ingroup Installer
|
||||
*/
|
||||
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use Wikimedia\Rdbms\Database;
|
||||
use Wikimedia\Rdbms\DatabasePostgres;
|
||||
use Wikimedia\Rdbms\DBConnectionError;
|
||||
|
|
@ -161,7 +162,7 @@ class PostgresInstaller extends DatabaseInstaller {
|
|||
protected function openConnectionWithParams( $user, $password, $dbName, $schema ) {
|
||||
$status = Status::newGood();
|
||||
try {
|
||||
$db = Database::factory( 'postgres', [
|
||||
$db = MediaWikiServices::getInstance()->getDatabaseFactory()->create( 'postgres', [
|
||||
'host' => $this->getVar( 'wgDBserver' ),
|
||||
'port' => $this->getVar( 'wgDBport' ),
|
||||
'user' => $user,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
* @ingroup Installer
|
||||
*/
|
||||
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use Wikimedia\AtEase\AtEase;
|
||||
use Wikimedia\Rdbms\Database;
|
||||
use Wikimedia\Rdbms\DatabaseSqlite;
|
||||
|
|
@ -194,8 +195,9 @@ class SqliteInstaller extends DatabaseInstaller {
|
|||
$dir = $this->getVar( 'wgSQLiteDataDir' );
|
||||
$dbName = $this->getVar( 'wgDBname' );
|
||||
try {
|
||||
# @todo FIXME: Need more sensible constructor parameters, e.g. single associative array
|
||||
$db = Database::factory( 'sqlite', [ 'dbname' => $dbName, 'dbDirectory' => $dir ] );
|
||||
$db = MediaWikiServices::getInstance()->getDatabaseFactory()->create(
|
||||
'sqlite', [ 'dbname' => $dbName, 'dbDirectory' => $dir ]
|
||||
);
|
||||
$status->value = $db;
|
||||
} catch ( DBConnectionError $e ) {
|
||||
$status->fatal( 'config-sqlite-connection-error', $e->getMessage() );
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
namespace Wikimedia\Rdbms;
|
||||
|
||||
use BagOStuff;
|
||||
use HashBagOStuff;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
|
|
@ -354,7 +353,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
* The "master" and "replica" fields are used to flag the replication role of this
|
||||
* database server and whether methods like getLag() should actually issue queries.
|
||||
* - topologicalPrimaryConnRef: lazy-connecting IDatabase handle to the most authoritative
|
||||
* primary database server for the cluster that this database belongs to. This hande is
|
||||
* primary database server for the cluster that this database belongs to. This handle is
|
||||
* used for replication status purposes. This is generally managed by LoadBalancer.
|
||||
* - connLogger: Optional PSR-3 logger interface instance.
|
||||
* - queryLogger: Optional PSR-3 logger interface instance.
|
||||
|
|
@ -373,53 +372,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
* @param int $connect One of the class constants (NEW_CONNECTED, NEW_UNCONNECTED) [optional]
|
||||
* @return Database|null If the database driver or extension cannot be found
|
||||
* @throws InvalidArgumentException If the database driver or extension cannot be found
|
||||
* @deprecated since 1.39, use DatabaseFactory::create instead
|
||||
* @since 1.18
|
||||
*/
|
||||
final public static function factory( $type, $params = [], $connect = self::NEW_CONNECTED ) {
|
||||
$class = self::getClass( $type, $params['driver'] ?? null );
|
||||
|
||||
if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) {
|
||||
$params += [
|
||||
// Default configuration
|
||||
'host' => null,
|
||||
'user' => null,
|
||||
'password' => null,
|
||||
'dbname' => null,
|
||||
'schema' => null,
|
||||
'tablePrefix' => '',
|
||||
'flags' => 0,
|
||||
'variables' => [],
|
||||
'lbInfo' => [],
|
||||
'cliMode' => ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ),
|
||||
'agent' => '',
|
||||
'serverName' => null,
|
||||
'topologyRole' => null,
|
||||
// Objects and callbacks
|
||||
'topologicalPrimaryConnRef' => $params['topologicalPrimaryConnRef'] ?? null,
|
||||
'srvCache' => $params['srvCache'] ?? new HashBagOStuff(),
|
||||
'profiler' => $params['profiler'] ?? null,
|
||||
'trxProfiler' => $params['trxProfiler'] ?? new TransactionProfiler(),
|
||||
'connLogger' => $params['connLogger'] ?? new NullLogger(),
|
||||
'queryLogger' => $params['queryLogger'] ?? new NullLogger(),
|
||||
'replLogger' => $params['replLogger'] ?? new NullLogger(),
|
||||
'errorLogger' => $params['errorLogger'] ?? static function ( Throwable $e ) {
|
||||
trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
|
||||
},
|
||||
'deprecationLogger' => $params['deprecationLogger'] ?? static function ( $msg ) {
|
||||
trigger_error( $msg, E_USER_DEPRECATED );
|
||||
}
|
||||
];
|
||||
|
||||
/** @var Database $conn */
|
||||
$conn = new $class( $params );
|
||||
if ( $connect === self::NEW_CONNECTED ) {
|
||||
$conn->initConnection();
|
||||
}
|
||||
} else {
|
||||
$conn = null;
|
||||
}
|
||||
|
||||
return $conn;
|
||||
return ( new DatabaseFactory() )->create( $type, $params, $connect );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -427,75 +384,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
* @param string|null $driver Optional name of a specific DB client driver
|
||||
* @return array Map of (Database::ATTR_* constant => value) for all such constants
|
||||
* @throws DBUnexpectedError
|
||||
* @deprecated since 1.39, use DatabaseFactory::attributesFromType instead
|
||||
* @since 1.31
|
||||
*/
|
||||
final public static function attributesFromType( $dbType, $driver = null ) {
|
||||
static $defaults = [
|
||||
self::ATTR_DB_IS_FILE => false,
|
||||
self::ATTR_DB_LEVEL_LOCKING => false,
|
||||
self::ATTR_SCHEMAS_AS_TABLE_GROUPS => false
|
||||
];
|
||||
|
||||
$class = self::getClass( $dbType, $driver );
|
||||
if ( class_exists( $class ) ) {
|
||||
return call_user_func( [ $class, 'getAttributes' ] ) + $defaults;
|
||||
} else {
|
||||
throw new DBUnexpectedError( null, "$dbType is not a supported database type." );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 );
|
||||
|
||||
if ( !isset( $builtinTypes[$dbType] ) ) {
|
||||
// Not a built in type, assume standard naming scheme
|
||||
return 'Database' . ucfirst( $dbType );
|
||||
}
|
||||
|
||||
$class = false;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $class === false ) {
|
||||
throw new InvalidArgumentException( __METHOD__ .
|
||||
" no viable database extension found for type '$dbType'" );
|
||||
}
|
||||
|
||||
return $class;
|
||||
return ( new DatabaseFactory() )->attributesFromType( $dbType, $driver );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -503,7 +396,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
|
|||
* @return array Map of (Database::ATTR_* constant => value)
|
||||
* @since 1.31
|
||||
*/
|
||||
protected static function getAttributes() {
|
||||
public static function getAttributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
|||
208
includes/libs/rdbms/database/DatabaseFactory.php
Normal file
208
includes/libs/rdbms/database/DatabaseFactory.php
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
<?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 Database
|
||||
*/
|
||||
namespace Wikimedia\Rdbms;
|
||||
|
||||
use HashBagOStuff;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Log\NullLogger;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Constructs Database objects
|
||||
*
|
||||
* @since 1.39
|
||||
*/
|
||||
class DatabaseFactory {
|
||||
|
||||
/**
|
||||
* Construct a Database subclass instance given a database type and parameters
|
||||
*
|
||||
* This also connects to the database immediately upon object construction
|
||||
*
|
||||
* @param string $type A possible DB type (sqlite, mysql, postgres,...)
|
||||
* @param array $params Parameter map with keys:
|
||||
* - host : The hostname of the DB server
|
||||
* - user : The name of the database user the client operates under
|
||||
* - password : The password for the database user
|
||||
* - dbname : The name of the database to use where queries do not specify one.
|
||||
* The database must exist or an error might be thrown. Setting this to the empty string
|
||||
* will avoid any such errors and make the handle have no implicit database scope. This is
|
||||
* useful for queries like SHOW STATUS, CREATE DATABASE, or DROP DATABASE. Note that a
|
||||
* "database" in Postgres is rougly equivalent to an entire MySQL server. This the domain
|
||||
* in which user names and such are defined, e.g. users are database-specific in Postgres.
|
||||
* - schema : The database schema to use (if supported). A "schema" in Postgres is roughly
|
||||
* equivalent to a "database" in MySQL. Note that MySQL and SQLite do not use schemas.
|
||||
* - tablePrefix : Optional table prefix that is implicitly added on to all table names
|
||||
* recognized in queries. This can be used in place of schemas for handle site farms.
|
||||
* - flags : Optional bit field of DBO_* constants that define connection, protocol,
|
||||
* buffering, and transaction behavior. It is STRONGLY adviced to leave the DBO_DEFAULT
|
||||
* flag in place UNLESS this this database simply acts as a key/value store.
|
||||
* - driver: Optional name of a specific DB client driver. For MySQL, there is only the
|
||||
* 'mysqli' driver; the old one 'mysql' has been removed.
|
||||
* - variables: Optional map of session variables to set after connecting. This can be
|
||||
* used to adjust lock timeouts or encoding modes and the like.
|
||||
* - topologyRole: Optional IDatabase::ROLE_* constant for the server.
|
||||
* - lbInfo: Optional map of field/values for the managing load balancer instance.
|
||||
* The "master" and "replica" fields are used to flag the replication role of this
|
||||
* database server and whether methods like getLag() should actually issue queries.
|
||||
* - topologicalPrimaryConnRef: lazy-connecting IDatabase handle to the most authoritative
|
||||
* primary database server for the cluster that this database belongs to. This handle is
|
||||
* used for replication status purposes. This is generally managed by LoadBalancer.
|
||||
* - connLogger: Optional PSR-3 logger interface instance.
|
||||
* - queryLogger: Optional PSR-3 logger interface instance.
|
||||
* - profiler : Optional callback that takes a section name argument and returns
|
||||
* a ScopedCallback instance that ends the profile section in its destructor.
|
||||
* These will be called in query(), using a simplified version of the SQL that
|
||||
* also includes the agent as a SQL comment.
|
||||
* - trxProfiler: Optional TransactionProfiler instance.
|
||||
* - errorLogger: Optional callback that takes an Exception and logs it.
|
||||
* - deprecationLogger: Optional callback that takes a string and logs it.
|
||||
* - cliMode: Whether to consider the execution context that of a CLI script.
|
||||
* - agent: Optional name used to identify the end-user in query profiling/logging.
|
||||
* - serverName: Optional human-readable server name
|
||||
* - srvCache: Optional BagOStuff instance to an APC-style cache.
|
||||
* - nonNativeInsertSelectBatchSize: Optional batch size for non-native INSERT SELECT.
|
||||
* @param int $connect One of the class constants (NEW_CONNECTED, NEW_UNCONNECTED) [optional]
|
||||
* @return Database|null If the database driver or extension cannot be found
|
||||
* @throws InvalidArgumentException If the database driver or extension cannot be found
|
||||
*/
|
||||
public function create( $type, $params = [], $connect = Database::NEW_CONNECTED ) {
|
||||
$class = $this->getClass( $type, $params['driver'] ?? null );
|
||||
|
||||
if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) {
|
||||
$params += [
|
||||
// Default configuration
|
||||
'host' => null,
|
||||
'user' => null,
|
||||
'password' => null,
|
||||
'dbname' => null,
|
||||
'schema' => null,
|
||||
'tablePrefix' => '',
|
||||
'flags' => 0,
|
||||
'variables' => [],
|
||||
'lbInfo' => [],
|
||||
'cliMode' => ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ),
|
||||
'agent' => '',
|
||||
'serverName' => null,
|
||||
'topologyRole' => null,
|
||||
// Objects and callbacks
|
||||
'topologicalPrimaryConnRef' => $params['topologicalPrimaryConnRef'] ?? null,
|
||||
'srvCache' => $params['srvCache'] ?? new HashBagOStuff(),
|
||||
'profiler' => $params['profiler'] ?? null,
|
||||
'trxProfiler' => $params['trxProfiler'] ?? new TransactionProfiler(),
|
||||
'connLogger' => $params['connLogger'] ?? new NullLogger(),
|
||||
'queryLogger' => $params['queryLogger'] ?? new NullLogger(),
|
||||
'replLogger' => $params['replLogger'] ?? new NullLogger(),
|
||||
'errorLogger' => $params['errorLogger'] ?? static function ( Throwable $e ) {
|
||||
trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
|
||||
},
|
||||
'deprecationLogger' => $params['deprecationLogger'] ?? static function ( $msg ) {
|
||||
trigger_error( $msg, E_USER_DEPRECATED );
|
||||
}
|
||||
];
|
||||
|
||||
/** @var Database $conn */
|
||||
$conn = new $class( $params );
|
||||
if ( $connect === Database::NEW_CONNECTED ) {
|
||||
$conn->initConnection();
|
||||
}
|
||||
} else {
|
||||
$conn = null;
|
||||
}
|
||||
|
||||
return $conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dbType A possible DB type (sqlite, mysql, postgres,...)
|
||||
* @param string|null $driver Optional name of a specific DB client driver
|
||||
* @return array Map of (Database::ATTR_* constant => value) for all such constants
|
||||
* @throws DBUnexpectedError
|
||||
*/
|
||||
public function attributesFromType( $dbType, $driver = null ) {
|
||||
static $defaults = [
|
||||
Database::ATTR_DB_IS_FILE => false,
|
||||
Database::ATTR_DB_LEVEL_LOCKING => false,
|
||||
Database::ATTR_SCHEMAS_AS_TABLE_GROUPS => false
|
||||
];
|
||||
|
||||
$class = $this->getClass( $dbType, $driver );
|
||||
if ( class_exists( $class ) ) {
|
||||
return call_user_func( [ $class, 'getAttributes' ] ) + $defaults;
|
||||
} else {
|
||||
throw new DBUnexpectedError( null, "$dbType is not a supported database type." );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
protected 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 );
|
||||
|
||||
if ( !isset( $builtinTypes[$dbType] ) ) {
|
||||
// Not a built in type, assume standard naming scheme
|
||||
return 'Database' . ucfirst( $dbType );
|
||||
}
|
||||
|
||||
$class = false;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $class === false ) {
|
||||
throw new InvalidArgumentException( __METHOD__ .
|
||||
" no viable database extension found for type '$dbType'" );
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
|
|
@ -1182,7 +1182,7 @@ SQL;
|
|||
return $row ? ( strtolower( $row->default_transaction_read_only ) === 'on' ) : false;
|
||||
}
|
||||
|
||||
protected static function getAttributes() {
|
||||
public static function getAttributes() {
|
||||
return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class DatabaseSqlite extends Database {
|
|||
);
|
||||
}
|
||||
|
||||
protected static function getAttributes() {
|
||||
public static function getAttributes() {
|
||||
return [
|
||||
self::ATTR_DB_IS_FILE => true,
|
||||
self::ATTR_DB_LEVEL_LOCKING => true
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ interface ILBFactory {
|
|||
* - cpStash: BagOStuff instance for ChronologyProtector store [optional]
|
||||
* See [ChronologyProtector requirements](@ref ChronologyProtector-storage-requirements).
|
||||
* - wanCache: WANObjectCache instance [optional]
|
||||
* - databaseFactory: DatabaseFactory instance [optional]
|
||||
* - cliMode: Whether the execution context is a CLI script. [optional]
|
||||
* - maxLag: Try to avoid DB replicas with lag above this many seconds [optional]
|
||||
* - profiler: Callback that takes a section name argument and returns
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ abstract class LBFactory implements ILBFactory {
|
|||
protected $srvCache;
|
||||
/** @var WANObjectCache */
|
||||
protected $wanCache;
|
||||
/** @var DatabaseFactory */
|
||||
protected $databaseFactory;
|
||||
/** @var DatabaseDomain Local domain */
|
||||
protected $localDomain;
|
||||
|
||||
|
|
@ -169,6 +171,8 @@ abstract class LBFactory implements ILBFactory {
|
|||
$this->srvCache = $conf['srvCache'] ?? new EmptyBagOStuff();
|
||||
$this->wanCache = $conf['wanCache'] ?? WANObjectCache::newEmpty();
|
||||
|
||||
$this->databaseFactory = $conf['databaseFactory'] ?? new DatabaseFactory();
|
||||
|
||||
foreach ( self::$loggerFields as $key ) {
|
||||
$this->$key = $conf[ $key ] ?? new NullLogger();
|
||||
}
|
||||
|
|
@ -737,6 +741,7 @@ abstract class LBFactory implements ILBFactory {
|
|||
'readOnlyReason' => $this->readOnlyReason,
|
||||
'srvCache' => $this->srvCache,
|
||||
'wanCache' => $this->wanCache,
|
||||
'databaseFactory' => $this->databaseFactory,
|
||||
'profiler' => $this->profiler,
|
||||
'trxProfiler' => $this->trxProfiler,
|
||||
'queryLogger' => $this->queryLogger,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ interface ILoadBalancerForOwner extends ILoadBalancer {
|
|||
* - maxLag: Try to avoid DB replicas with lag above this many seconds [optional]
|
||||
* - srvCache : BagOStuff object for server cache [optional]
|
||||
* - wanCache : WANObjectCache object [optional]
|
||||
* - databaseFactory: DatabaseFactory object [optional]
|
||||
* - chronologyCallback: Callback to run before the first connection attempt [optional]
|
||||
* - defaultGroup: Default query group; the generic group if not specified [optional]
|
||||
* - hostname : The name of the current server [optional]
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ class LoadBalancer implements ILoadBalancerForOwner {
|
|||
private $srvCache;
|
||||
/** @var WANObjectCache */
|
||||
private $wanCache;
|
||||
/** @var DatabaseFactory */
|
||||
private $databaseFactory;
|
||||
/**
|
||||
* @var callable|null An optional callback that returns a ScopedCallback instance,
|
||||
* meant to profile the actual query execution in {@see Database::doQuery}
|
||||
|
|
@ -234,6 +236,7 @@ class LoadBalancer implements ILoadBalancerForOwner {
|
|||
|
||||
$this->srvCache = $params['srvCache'] ?? new EmptyBagOStuff();
|
||||
$this->wanCache = $params['wanCache'] ?? WANObjectCache::newEmpty();
|
||||
$this->databaseFactory = $params['databaseFactory'] ?? new DatabaseFactory();
|
||||
$this->errorLogger = $params['errorLogger'] ?? static function ( Throwable $e ) {
|
||||
trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
|
||||
};
|
||||
|
|
@ -1288,7 +1291,7 @@ class LoadBalancer implements ILoadBalancerForOwner {
|
|||
}
|
||||
|
||||
public function getServerAttributes( $i ) {
|
||||
return Database::attributesFromType(
|
||||
return $this->databaseFactory->attributesFromType(
|
||||
$this->getServerType( $i ),
|
||||
$this->servers[$i]['driver'] ?? null
|
||||
);
|
||||
|
|
@ -1323,7 +1326,7 @@ class LoadBalancer implements ILoadBalancerForOwner {
|
|||
|
||||
$server = $this->getServerInfoStrict( $i );
|
||||
|
||||
$conn = Database::factory(
|
||||
$conn = $this->databaseFactory->create(
|
||||
$server['type'],
|
||||
array_merge( $server, [
|
||||
// Basic replication role information
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
use Wikimedia\Rdbms\Database;
|
||||
use Wikimedia\Rdbms\DatabaseFactory;
|
||||
use Wikimedia\Rdbms\DatabaseMysqli;
|
||||
use Wikimedia\Rdbms\DatabasePostgres;
|
||||
use Wikimedia\Rdbms\DatabaseSqlite;
|
||||
|
||||
/**
|
||||
* @covers Wikimedia\Rdbms\DatabaseFactory
|
||||
*/
|
||||
class DatabaseFactoryTest extends PHPUnit\Framework\TestCase {
|
||||
|
||||
use MediaWikiCoversValidator;
|
||||
|
||||
public function testFactory() {
|
||||
$factory = new DatabaseFactory();
|
||||
$m = Database::NEW_UNCONNECTED; // no-connect mode
|
||||
$p = [
|
||||
'host' => 'localhost',
|
||||
'serverName' => 'localdb',
|
||||
'user' => 'me',
|
||||
'password' => 'myself',
|
||||
'dbname' => 'i'
|
||||
];
|
||||
|
||||
$this->assertInstanceOf( DatabaseMysqli::class, $factory->create( 'mysqli', $p, $m ) );
|
||||
$this->assertInstanceOf( DatabaseMysqli::class, $factory->create( 'MySqli', $p, $m ) );
|
||||
$this->assertInstanceOf( DatabaseMysqli::class, $factory->create( 'MySQLi', $p, $m ) );
|
||||
$this->assertInstanceOf( DatabasePostgres::class, $factory->create( 'postgres', $p, $m ) );
|
||||
$this->assertInstanceOf( DatabasePostgres::class, $factory->create( 'Postgres', $p, $m ) );
|
||||
|
||||
$x = $p + [ 'dbFilePath' => 'some/file.sqlite' ];
|
||||
$this->assertInstanceOf( DatabaseSqlite::class, $factory->create( 'sqlite', $x, $m ) );
|
||||
$x = $p + [ 'dbDirectory' => 'some/file' ];
|
||||
$this->assertInstanceOf( DatabaseSqlite::class, $factory->create( 'sqlite', $x, $m ) );
|
||||
|
||||
$conn = $factory->create( 'sqlite', $p, $m );
|
||||
$this->assertEquals( 'localhost', $conn->getServer() );
|
||||
$this->assertEquals( 'localdb', $conn->getServerName() );
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use Wikimedia\Rdbms\DatabaseDomain;
|
||||
use Wikimedia\Rdbms\DatabaseFactory;
|
||||
use Wikimedia\Rdbms\LBFactorySimple;
|
||||
use Wikimedia\RequestTimeout\CriticalSectionProvider;
|
||||
use Wikimedia\RequestTimeout\RequestTimeout;
|
||||
|
|
@ -39,7 +40,8 @@ class MWLBFactoryTest extends MediaWikiUnitTestCase {
|
|||
new EmptyBagOStuff(),
|
||||
new WANObjectCache( [ 'cache' => new EmptyBagOStuff() ] ),
|
||||
new CriticalSectionProvider( RequestTimeout::singleton(), 1, null, null ),
|
||||
new NullStatsdDataFactory()
|
||||
new NullStatsdDataFactory(),
|
||||
new DatabaseFactory()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ use MediaWiki\Tests\Unit\Libs\Rdbms\SQLPlatformTestHelper;
|
|||
use Wikimedia\Rdbms\Database;
|
||||
use Wikimedia\Rdbms\DatabaseDomain;
|
||||
use Wikimedia\Rdbms\DatabaseMysqli;
|
||||
use Wikimedia\Rdbms\DatabasePostgres;
|
||||
use Wikimedia\Rdbms\DatabaseSqlite;
|
||||
use Wikimedia\Rdbms\DBReadOnlyRoleError;
|
||||
use Wikimedia\Rdbms\DBTransactionStateError;
|
||||
use Wikimedia\Rdbms\DBUnexpectedError;
|
||||
|
|
@ -30,35 +28,6 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
|
|||
$this->db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Wikimedia\Rdbms\Database::factory
|
||||
*/
|
||||
public function testFactory() {
|
||||
$m = Database::NEW_UNCONNECTED; // no-connect mode
|
||||
$p = [
|
||||
'host' => 'localhost',
|
||||
'serverName' => 'localdb',
|
||||
'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( DatabasePostgres::class, Database::factory( 'postgres', $p, $m ) );
|
||||
$this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'Postgres', $p, $m ) );
|
||||
|
||||
$x = $p + [ 'dbFilePath' => 'some/file.sqlite' ];
|
||||
$this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
|
||||
$x = $p + [ 'dbDirectory' => 'some/file' ];
|
||||
$this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
|
||||
|
||||
$conn = Database::factory( 'sqlite', $p, $m );
|
||||
$this->assertEquals( 'localhost', $conn->getServer() );
|
||||
$this->assertEquals( 'localdb', $conn->getServerName() );
|
||||
}
|
||||
|
||||
public static function provideAddQuotes() {
|
||||
return [
|
||||
[ null, 'NULL' ],
|
||||
|
|
|
|||
Loading…
Reference in a new issue