Issue #3120096 by alexpott, daffie, effulgentsia, Neslee Canil Pinto, xjm, mondrake, catch, ravi.shankar: Support contrib database driver directories in a fixed location in a module

(cherry picked from commit ff6279aa78)
(cherry picked from commit 5ca020f139)
merge-requests/64/head
effulgentsia 2020-04-07 13:00:31 -07:00
parent efa1e2aee7
commit 90f5f95851
45 changed files with 1002 additions and 63 deletions

View File

@ -105,6 +105,16 @@ $databases = [];
* webserver. For most other drivers, you must specify a
* username, password, host, and database name.
*
* Drupal core implements drivers for mysql, pgsql, and sqlite. Other drivers
* can be provided by contributed or custom modules. To use a contributed or
* custom driver, the "namespace" property must be set to the namespace of the
* driver. The code in this namespace must be autoloadable prior to connecting
* to the database, and therefore, prior to when module root namespaces are
* added to the autoloader. To add the driver's namespace to the autoloader,
* set the "autoload" property to the PSR-4 base directory of the driver's
* namespace. This is optional for projects managed with Composer if the
* driver's namespace is in Composer's autoloader.
*
* Transaction support is enabled by default for all drivers that support it,
* including MySQL. To explicitly disable it, set the 'transactions' key to
* FALSE.
@ -224,6 +234,20 @@ $databases = [];
* 'database' => '/path/to/databasefilename',
* ];
* @endcode
*
* Sample Database configuration format for a driver in a contributed module:
* @code
* $databases['default']['default'] = [
* 'driver' => 'mydriver',
* 'namespace' => 'Drupal\mymodule\Driver\Database\mydriver',
* 'autoload' => 'modules/mymodule/src/Driver/Database/mydriver/',
* 'database' => 'databasename',
* 'username' => 'sqlusername',
* 'password' => 'sqlpassword',
* 'host' => 'localhost',
* 'prefix' => '',
* ];
* @endcode
*/
/**

View File

@ -375,6 +375,11 @@ function install_begin_request($class_loader, &$install_state) {
->addArgument(Settings::getInstance())
->addArgument((new LoggerChannelFactory())->get('file'));
// Register the class loader so contrib and custom database drivers can be
// autoloaded.
// @see drupal_get_database_types()
$container->set('class_loader', $class_loader);
\Drupal::setContainer($container);
// Determine whether base system services are ready to operate.
@ -1207,7 +1212,7 @@ function install_database_errors($database, $settings_file) {
// calling function.
Database::addConnectionInfo('default', 'default', $database);
$errors = db_installer_object($driver)->runTasks();
$errors = db_installer_object($driver, $database['namespace'] ?? NULL)->runTasks();
}
return $errors;
}

View File

@ -171,6 +171,8 @@ function drupal_get_database_types() {
// The internal database driver name is any valid PHP identifier.
$mask = ExtensionDiscovery::PHP_FUNCTION_PATTERN;
// Find drivers in the Drupal\Core and Drupal\Driver namespaces.
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$files = $file_system->scanDirectory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, ['recurse' => FALSE]);
@ -179,11 +181,43 @@ function drupal_get_database_types() {
}
foreach ($files as $file) {
if (file_exists($file->uri . '/Install/Tasks.php')) {
$drivers[$file->filename] = $file->uri;
// The namespace doesn't need to be added here, because
// db_installer_object() will find it.
$drivers[$file->filename] = NULL;
}
}
foreach ($drivers as $driver => $file) {
$installer = db_installer_object($driver);
// Find drivers in Drupal module namespaces.
/** @var \Composer\Autoload\ClassLoader $class_loader */
$class_loader = \Drupal::service('class_loader');
// We cannot use the file cache because it does not always exist.
$extension_discovery = new ExtensionDiscovery(DRUPAL_ROOT, FALSE, []);
$modules = $extension_discovery->scan('module');
foreach ($modules as $module) {
$module_driver_path = DRUPAL_ROOT . '/' . $module->getPath() . '/src/Driver/Database';
if (is_dir($module_driver_path)) {
$driver_files = $file_system->scanDirectory($module_driver_path, $mask, ['recurse' => FALSE]);
foreach ($driver_files as $driver_file) {
$tasks_file = $module_driver_path . '/' . $driver_file->filename . '/Install/Tasks.php';
if (file_exists($tasks_file)) {
$namespace = 'Drupal\\' . $module->getName() . '\\Driver\\Database\\' . $driver_file->filename;
// The namespace needs to be added for db_installer_object() to find
// it.
$drivers[$driver_file->filename] = $namespace;
// The directory needs to be added to the autoloader, because this is
// early in the installation process: the module hasn't been enabled
// yet and the database connection info array (including its 'autoload'
// key) hasn't been created yet.
$class_loader->addPsr4($namespace . '\\', $module->getPath() . '/src/Driver/Database/' . $driver_file->filename);
}
}
}
}
foreach ($drivers as $driver => $namespace) {
$installer = db_installer_object($driver, $namespace);
if ($installer->installable()) {
$databases[$driver] = $installer;
}
@ -1169,20 +1203,35 @@ function install_profile_info($profile, $langcode = 'en') {
/**
* Returns a database installer object.
*
* Before calling this function it is important the database installer object
* is autoloadable. Database drivers provided by contributed modules are added
* to the autoloader in drupal_get_database_types() and Settings::initialize().
*
* @param $driver
* The name of the driver.
* @param string $namespace
* (optional) The database driver namespace.
*
* @return \Drupal\Core\Database\Install\Tasks
* A class defining the requirements and tasks for installing the database.
*
* @see drupal_get_database_types()
* @see \Drupal\Core\Site\Settings::initialize()
*/
function db_installer_object($driver) {
function db_installer_object($driver, $namespace = NULL) {
// We cannot use Database::getConnection->getDriverClass() here, because
// the connection object is not yet functional.
if ($namespace) {
$task_class = $namespace . "\\Install\\Tasks";
return new $task_class();
}
// Old Drupal 8 style contrib namespace.
$task_class = "Drupal\\Driver\\Database\\{$driver}\\Install\\Tasks";
if (class_exists($task_class)) {
return new $task_class();
}
else {
// Core provided driver.
$task_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Install\\Tasks";
return new $task_class();
}

View File

@ -1378,7 +1378,7 @@ abstract class Connection {
* Returns the type of database driver.
*
* This is not necessarily the same as the type of the database itself. For
* instance, there could be two MySQL drivers, mysql and mysql_mock. This
* instance, there could be two MySQL drivers, mysql and mysqlMock. This
* function would return different values for each, but both would return
* "mysql" for databaseType().
*
@ -1572,10 +1572,6 @@ abstract class Connection {
/**
* Creates an array of database connection options from a URL.
*
* @internal
* This method should not be called. Use
* \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo() instead.
*
* @param string $url
* The URL.
* @param string $root
@ -1589,6 +1585,10 @@ abstract class Connection {
* Exception thrown when the provided URL does not meet the minimum
* requirements.
*
* @internal
* This method should only be called from
* \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo().
*
* @see \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo()
*/
public static function createConnectionOptionsFromUrl($url, $root) {
@ -1634,12 +1634,10 @@ abstract class Connection {
/**
* Creates a URL from an array of database connection options.
*
* @internal
* This method should not be called. Use
* \Drupal\Core\Database\Database::getConnectionInfoAsUrl() instead.
*
* @param array $connection_options
* The array of connection options for a database connection.
* The array of connection options for a database connection. An additional
* key of 'module' is added by Database::getConnectionInfoAsUrl() for
* drivers provided my contributed or custom modules for convenience.
*
* @return string
* The connection info as a URL.
@ -1648,6 +1646,10 @@ abstract class Connection {
* Exception thrown when the provided array of connection options does not
* meet the minimum requirements.
*
* @internal
* This method should only be called from
* \Drupal\Core\Database\Database::getConnectionInfoAsUrl().
*
* @see \Drupal\Core\Database\Database::getConnectionInfoAsUrl()
*/
public static function createUrlFromConnectionOptions(array $connection_options) {
@ -1674,6 +1676,11 @@ abstract class Connection {
$db_url .= '/' . $connection_options['database'];
// Add the module when the driver is provided by a module.
if (isset($connection_options['module'])) {
$db_url .= '?module=' . $connection_options['module'];
}
if (isset($connection_options['prefix']['default']) && $connection_options['prefix']['default'] !== '') {
$db_url .= '#' . $connection_options['prefix']['default'];
}

View File

@ -2,6 +2,9 @@
namespace Drupal\Core\Database;
use Composer\Autoload\ClassLoader;
use Drupal\Core\Extension\ExtensionDiscovery;
/**
* Primary front-controller for the database system.
*
@ -448,6 +451,8 @@ abstract class Database {
* @throws \InvalidArgumentException
* Exception thrown when the provided URL does not meet the minimum
* requirements.
* @throws \RuntimeException
* Exception thrown when a module provided database driver does not exist.
*/
public static function convertDbUrlToConnectionInfo($url, $root) {
// Check that the URL is well formed, starting with 'scheme://', where
@ -457,18 +462,130 @@ abstract class Database {
}
$driver = $matches[1];
// Discover if the URL has a valid driver scheme. Try with custom drivers
// first, since those can override/extend the core ones.
$connection_class = $custom_connection_class = "Drupal\\Driver\\Database\\{$driver}\\Connection";
if (!class_exists($connection_class)) {
// If the URL is not relative to a custom driver, try with core ones.
$connection_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
if (!class_exists($connection_class)) {
throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$custom_connection_class' does not exist");
// Determine if the database driver is provided by a module.
$module = NULL;
$connection_class = NULL;
$url_components = parse_url($url);
if (isset($url_components['query'])) {
parse_str($url_components['query'], $query);
if ($query['module']) {
$module = $query['module'];
// Set up an additional autoloader. We don't use the main autoloader as
// this method can be called before Drupal is installed and is never
// called during regular runtime.
$namespace = "Drupal\\$module\\Driver\\Database\\$driver";
$psr4_base_directory = Database::findDriverAutoloadDirectory($namespace, $root, TRUE);
$additional_class_loader = new ClassLoader();
$additional_class_loader->addPsr4($namespace . '\\', $psr4_base_directory);
$additional_class_loader->register(TRUE);
$connection_class = $custom_connection_class = $namespace . '\\Connection';
}
}
return $connection_class::createConnectionOptionsFromUrl($url, $root);
if (!$module) {
// Determine the connection class to use. Discover if the URL has a valid
// driver scheme. Try with Drupal 8 style custom drivers first, since
// those can override/extend the core ones.
$connection_class = $custom_connection_class = "Drupal\\Driver\\Database\\{$driver}\\Connection";
if (!class_exists($connection_class)) {
// If the URL is not relative to a custom driver, try with core ones.
$connection_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
}
}
if (!class_exists($connection_class)) {
throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$custom_connection_class' does not exist");
}
$options = $connection_class::createConnectionOptionsFromUrl($url, $root);
// If the driver is provided by a module add the necessary information to
// autoload the code.
// @see \Drupal\Core\Site\Settings::initialize()
if (isset($psr4_base_directory)) {
$options['autoload'] = $psr4_base_directory;
}
return $options;
}
/**
* Finds the directory to add to the autoloader for the driver's namespace.
*
* For Drupal sites that manage their codebase with Composer, the package
* that provides the database driver should add the driver's namespace to
* Composer's autoloader. However, to support sites that add Drupal modules
* without Composer, and because the database connection must be established
* before Drupal adds the module's entire namespace to the autoloader, the
* database connection info array can include an "autoload" key containing
* the autoload directory for the driver's namespace. For requests that
* connect to the database via a connection info array, the value of the
* "autoload" key is automatically added to the autoloader.
*
* This method can be called to find the default value of that key when the
* database connection info array isn't available. This includes:
* - Console commands and test runners that connect to a database specified
* by a database URL rather than a connection info array.
* - During installation, prior to the connection info array being written to
* settings.php.
*
* This method returns the directory that must be added to the autoloader for
* the given namespace.
* - If the namespace is a sub-namespace of a Drupal module, then this method
* returns the autoload directory for that namespace, allowing Drupal
* modules containing database drivers to be added to a Drupal website
* without Composer.
* - If the namespace is a sub-namespace of Drupal\Core or Drupal\Driver,
* then this method returns FALSE, because Drupal core's autoloader already
* includes these namespaces, so no additional autoload directory is
* required for any code within them.
* - If the namespace is anything else, then this method returns FALSE,
* because neither drupal_get_database_types() nor
* static::convertDbUrlToConnectionInfo() support that anyway. One can
* manually edit the connection info array in settings.php to reference
* any arbitrary namespace, but requests using that would use the
* corresponding 'autoload' key in that connection info rather than calling
* this method.
*
* @param string $namespace
* The database driver's namespace.
* @param string $root
* The root directory of the Drupal installation.
*
* @return string|false
* The PSR-4 directory to add to the autoloader for the namespace if the
* namespace is a sub-namespace of a Drupal module. FALSE otherwise, as
* explained above.
*
* @throws \RuntimeException
* Exception thrown when a module provided database driver does not exist.
*/
public static function findDriverAutoloadDirectory($namespace, $root) {
// As explained by this method's documentation, return FALSE if the
// namespace is not a sub-namespace of a Drupal module.
if (!static::isWithinModuleNamespace($namespace)) {
return FALSE;
}
// Extract the module information from the namespace.
[, $module, $module_relative_namespace] = explode('\\', $namespace, 3);
// The namespace is within a Drupal module. Find the directory where the
// module is located.
$extension_discovery = new ExtensionDiscovery($root, FALSE, []);
$modules = $extension_discovery->scan('module');
if (!isset($modules[$module])) {
throw new \RuntimeException(sprintf("Cannot find the module '%s' for the database driver namespace '%s'", $module, $namespace));
}
$module_directory = $modules[$module]->getPath();
// All code within the Drupal\MODULE namespace is expected to follow a
// PSR-4 layout within the module's "src" directory.
$driver_directory = $module_directory . '/src/' . str_replace('\\', '/', $module_relative_namespace) . '/';
if (!is_dir($root . '/' . $driver_directory)) {
throw new \RuntimeException(sprintf("Cannot find the database driver namespace '%s' in module '%s'", $namespace, $module));
}
return $driver_directory;
}
/**
@ -488,7 +605,16 @@ abstract class Database {
if (empty($db_info) || empty($db_info['default'])) {
throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings");
}
$connection_class = static::getDatabaseDriverNamespace($db_info['default']) . '\\Connection';
$namespace = static::getDatabaseDriverNamespace($db_info['default']);
// If the driver namespace is within a Drupal module, add the module name
// to the connection options to make it easy for the connection class's
// createUrlFromConnectionOptions() method to add it to the URL.
if (static::isWithinModuleNamespace($namespace)) {
$db_info['default']['module'] = explode('\\', $namespace)[1];
}
$connection_class = $namespace . '\\Connection';
return $connection_class::createUrlFromConnectionOptions($db_info['default']);
}
@ -511,4 +637,32 @@ abstract class Database {
return 'Drupal\\Core\\Database\\Driver\\' . $connection_info['driver'];
}
/**
* Checks whether a namespace is within the namespace of a Drupal module.
*
* This can be used to determine if a database driver's namespace is provided
* by a Drupal module.
*
* @param string $namespace
* The namespace (for example, of a database driver) to check.
*
* @return bool
* TRUE if the passed in namespace is a sub-namespace of a Drupal module's
* namespace.
*
* @todo https://www.drupal.org/project/drupal/issues/3125476 Remove if we
* add this to the extension API or if
* \Drupal\Core\Database\Database::getConnectionInfoAsUrl() is removed.
*/
private static function isWithinModuleNamespace(string $namespace) {
[$first, $second] = explode('\\', $namespace, 3);
// The namespace for Drupal modules is Drupal\MODULE_NAME, and the module
// name must be all lowercase. Second-level namespaces containing uppercase
// letters (e.g., "Core", "Component", "Driver") are not modules.
// @see \Drupal\Core\DrupalKernel::getModuleNamespacesPsr4()
// @see https://www.drupal.org/docs/8/creating-custom-modules/naming-and-placing-your-drupal-8-module#s-name-your-module
return ($first === 'Drupal' && strtolower($second) === $second);
}
}

View File

@ -202,6 +202,13 @@ abstract class Tasks {
* The options form array.
*/
public function getFormOptions(array $database) {
// Use reflection to determine the driver name.
// @todo https:///www.drupal.org/node/3123240 Provide a better way to get
// the driver name.
$reflection = new \ReflectionClass($this);
$dir_parts = explode(DIRECTORY_SEPARATOR, dirname(dirname($reflection->getFileName())));
$driver = array_pop($dir_parts);
$form['database'] = [
'#type' => 'textfield',
'#title' => t('Database name'),
@ -210,7 +217,7 @@ abstract class Tasks {
'#required' => TRUE,
'#states' => [
'required' => [
':input[name=driver]' => ['value' => $this->pdoDriver],
':input[name=driver]' => ['value' => $driver],
],
],
];
@ -223,7 +230,7 @@ abstract class Tasks {
'#required' => TRUE,
'#states' => [
'required' => [
':input[name=driver]' => ['value' => $this->pdoDriver],
':input[name=driver]' => ['value' => $driver],
],
],
];

View File

@ -160,6 +160,10 @@ class SiteSettingsForm extends FormBase {
// Cut the trailing \Install from namespace.
$database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\'));
$database['driver'] = $driver;
// See default.settings.php for an explanation of the 'autoload' key.
if ($autoload = Database::findDriverAutoloadDirectory($database['namespace'], DRUPAL_ROOT)) {
$database['autoload'] = $autoload;
}
$form_state->set('database', $database);
foreach ($this->getDatabaseErrors($database, $form_state->getValue('settings_file')) as $name => $message) {

View File

@ -125,8 +125,21 @@ final class Settings {
require $app_root . '/' . $site_path . '/settings.php';
}
// Initialize Database.
Database::setMultipleConnectionInfo($databases);
// Initialize databases.
foreach ($databases as $key => $targets) {
foreach ($targets as $target => $info) {
Database::addConnectionInfo($key, $target, $info);
// If the database driver is provided by a module, then its code may
// need to be instantiated prior to when the module's root namespace
// is added to the autoloader, because that happens during service
// container initialization but the container definition is likely in
// the database. Therefore, allow the connection info to specify an
// autoload directory for the driver.
if (isset($info['autoload'])) {
$class_loader->addPsr4($info['namespace'] . '\\', $info['autoload']);
}
}
}
// For BC ensure the $config_directories global is set both in the global
// and settings.

View File

@ -0,0 +1,8 @@
<?php
namespace Drupal\database_statement_monitoring_test\mysql\Install;
use Drupal\Core\Database\Driver\mysql\Install\Tasks as BaseTasks;
class Tasks extends BaseTasks {
}

View File

@ -0,0 +1,8 @@
<?php
namespace Drupal\database_statement_monitoring_test\pgsql\Install;
use Drupal\Core\Database\Driver\pgsql\Install\Tasks as BaseTasks;
class Tasks extends BaseTasks {
}

View File

@ -0,0 +1,8 @@
<?php
namespace Drupal\database_statement_monitoring_test\sqlite\Install;
use Drupal\Core\Database\Driver\sqlite\Install\Tasks as BaseTasks;
class Tasks extends BaseTasks {
}

View File

@ -0,0 +1,5 @@
name: 'Contrib database driver test'
type: module
description: 'Support database contrib driver testing.'
package: Testing
version: VERSION

View File

@ -0,0 +1,19 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Connection as CoreConnection;
/**
* MySQL test implementation of \Drupal\Core\Database\Connection.
*/
class Connection extends CoreConnection {
/**
* {@inheritdoc}
*/
public function driver() {
return 'DrivertestMysql';
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Delete as CoreDelete;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Delete.
*/
class Delete extends CoreDelete {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Insert as CoreInsert;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Insert.
*/
class Insert extends CoreInsert {}

View File

@ -0,0 +1,19 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql\Install;
use Drupal\Core\Database\Driver\mysql\Install\Tasks as CoreTasks;
/**
* Specifies installation tasks for MySQL test databases.
*/
class Tasks extends CoreTasks {
/**
* {@inheritdoc}
*/
public function name() {
return t('MySQL by the driver_test module');
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Merge as CoreMerge;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Merge.
*/
class Merge extends CoreMerge {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Schema as CoreSchema;
/**
* MySQL test implementation of \Drupal\Core\Database\Schema.
*/
class Schema extends CoreSchema {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Select as CoreSelect;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Select.
*/
class Select extends CoreSelect {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Transaction as CoreTransaction;
/**
* MySQL test implementation of \Drupal\Core\Database\Transaction.
*/
class Transaction extends CoreTransaction {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Truncate as CoreTruncate;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Truncate.
*/
class Truncate extends CoreTruncate {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Update as CoreUpdate;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Update.
*/
class Update extends CoreUpdate {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\Core\Database\Driver\mysql\Upsert as CoreUpsert;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Upsert.
*/
class Upsert extends CoreUpsert {}

View File

@ -0,0 +1,19 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Connection as CoreConnection;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Connection.
*/
class Connection extends CoreConnection {
/**
* {@inheritdoc}
*/
public function driver() {
return 'DrivertestPgsql';
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Delete as CoreDelete;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Query\Delete.
*/
class Delete extends CoreDelete {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Insert as CoreInsert;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Query\Insert.
*/
class Insert extends CoreInsert {}

View File

@ -0,0 +1,19 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql\Install;
use Drupal\Core\Database\Driver\pgsql\Install\Tasks as CoreTasks;
/**
* Specifies installation tasks for PostgreSQL databases.
*/
class Tasks extends CoreTasks {
/**
* {@inheritdoc}
*/
public function name() {
return t('PostgreSQL by the driver_test module');
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Merge as CoreMerge;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Query\Merge.
*/
class Merge extends CoreMerge {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\NativeUpsert as CoreNativeUpsert;
/**
* PostgreSQL implementation of native \Drupal\Core\Database\Query\Upsert.
*/
class NativeUpsert extends CoreNativeUpsert {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Schema as CoreSchema;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Schema.
*/
class Schema extends CoreSchema {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Select as CoreSelect;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Query\Select.
*/
class Select extends CoreSelect {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Transaction as CoreTransaction;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Transaction.
*/
class Transaction extends CoreTransaction {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Truncate as CoreTruncate;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Query\Truncate.
*/
class Truncate extends CoreTruncate {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Update as CoreUpdate;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Query\Update.
*/
class Update extends CoreUpdate {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
use Drupal\Core\Database\Driver\pgsql\Upsert as CoreUpsert;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Query\Upsert.
*/
class Upsert extends CoreUpsert {}

View File

@ -0,0 +1,65 @@
<?php
namespace Drupal\FunctionalTests\Installer;
use Drupal\Core\Database\Database;
/**
* Tests the interactive installer.
*
* @group Installer
*/
class InstallerNonDefaultDatabaseDriverTest extends InstallerTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The name of the test database driver in use.
* @var string
*/
protected $testDriverName;
/**
* {@inheritdoc}
*/
protected function setUpSettings() {
$driver = Database::getConnection()->driver();
if (!in_array($driver, ['mysql', 'pgsql'])) {
$this->markTestSkipped("This test does not support the {$driver} database driver.");
}
$this->testDriverName = 'Drivertest' . ucfirst($driver);
// Assert that we are using the database drivers from the driver_test module.
$elements = $this->xpath('//label[@for="edit-driver-drivertestmysql"]');
$this->assertEqual(current($elements)->getText(), 'MySQL by the driver_test module');
$elements = $this->xpath('//label[@for="edit-driver-drivertestpgsql"]');
$this->assertEqual(current($elements)->getText(), 'PostgreSQL by the driver_test module');
$settings = $this->parameters['forms']['install_settings_form'];
$settings['driver'] = $this->testDriverName;
$settings[$this->testDriverName] = $settings[$driver];
unset($settings[$driver]);
$edit = $this->translatePostValues($settings);
$this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
}
/**
* Confirms that the installation succeeded.
*/
public function testInstalled() {
$this->assertUrl('user/1');
$this->assertResponse(200);
// Assert that in the settings.php the database connection array has the
// correct values set.
$contents = file_get_contents($this->root . '/' . $this->siteDirectory . '/settings.php');
$this->assertContains("'namespace' => 'Drupal\\\\driver_test\\\\Driver\\\\Database\\\\{$this->testDriverName}',", $contents);
$this->assertContains("'driver' => '{$this->testDriverName}',", $contents);
$this->assertContains("'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/{$this->testDriverName}/',", $contents);
}
}

View File

@ -75,6 +75,13 @@ class InstallerTest extends InstallerTestBase {
// Assert that the expected title is present.
$this->assertEqual('Database configuration', $this->cssSelect('main h2')[0]->getText());
// Assert that we use the by core supported database drivers by default and
// not the ones from the driver_test module.
$elements = $this->xpath('//label[@for="edit-driver-mysql"]');
$this->assertEqual(current($elements)->getText(), 'MySQL, MariaDB, Percona Server, or equivalent');
$elements = $this->xpath('//label[@for="edit-driver-pgsql"]');
$this->assertEqual(current($elements)->getText(), 'PostgreSQL');
parent::setUpSettings();
}

View File

@ -312,8 +312,10 @@ abstract class UpdatePathTestBase extends BrowserTestBase {
\Drupal::setContainer($container);
require_once __DIR__ . '/../../../../includes/install.inc';
$connection = Database::getConnection();
$errors = db_installer_object($connection->driver())->runTasks();
$connection_info = Database::getConnectionInfo();
$driver = $connection_info['default']['driver'];
$namespace = $connection_info['default']['namespace'] ?? NULL;
$errors = db_installer_object($driver, $namespace)->runTasks();
if (!empty($errors)) {
$this->fail('Failed to run installer database tasks: ' . implode(', ', $errors));
}

View File

@ -366,8 +366,10 @@ abstract class KernelTestBase extends TestCase implements ServiceProviderInterfa
// Ensure database tasks have been run.
require_once __DIR__ . '/../../../includes/install.inc';
$connection = Database::getConnection();
$errors = db_installer_object($connection->driver())->runTasks();
$connection_info = Database::getConnectionInfo();
$driver = $connection_info['default']['driver'];
$namespace = $connection_info['default']['namespace'] ?? NULL;
$errors = db_installer_object($driver, $namespace)->runTasks();
if (!empty($errors)) {
$this->fail('Failed to run installer database tasks: ' . implode(', ', $errors));
}

View File

@ -0,0 +1,123 @@
<?php
namespace Drupal\Tests\Core\Database;
use Composer\Autoload\ClassLoader;
use Drupal\Core\Database\Database;
use Drupal\Core\Site\Settings;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @coversDefaultClass \Drupal\Core\Database\Database
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*
* @group Database
*/
class DatabaseTest extends UnitTestCase {
/**
* A classloader to enable testing of contrib drivers.
*
* @var \Composer\Autoload\ClassLoader
*/
protected $additionalClassloader;
/**
* Path to DRUPAL_ROOT.
*
* @var string
*/
protected $root;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->additionalClassloader = new ClassLoader();
$this->additionalClassloader->register();
// Mock the container so we don't need to mock drupal_valid_test_ua().
// @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
$this->root = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
$container = $this->createMock(ContainerInterface::class);
$container->expects($this->any())
->method('has')
->with('kernel')
->willReturn(TRUE);
$container->expects($this->any())
->method('get')
->with('site.path')
->willReturn('');
\Drupal::setContainer($container);
}
/**
* @covers ::findDriverAutoloadDirectory
* @dataProvider providerFindDriverAutoloadDirectory
*/
public function testFindDriverAutoloadDirectory($expected, $namespace) {
new Settings(['extension_discovery_scan_tests' => TRUE]);
// The only module that provides a driver in core is a test module.
$this->assertSame($expected, Database::findDriverAutoloadDirectory($namespace, $this->root));
}
/**
* Data provider for ::testFindDriverAutoloadDirectory().
*
* @return array
*/
public function providerFindDriverAutoloadDirectory() {
return [
'core mysql' => [FALSE, 'Drupal\Core\Database\Driver\mysql'],
'D8 custom fake' => [FALSE, 'Drupal\Driver\Database\corefake'],
'module mysql' => ['core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql/', 'Drupal\driver_test\Driver\Database\DrivertestMysql'],
];
}
/**
* @covers ::findDriverAutoloadDirectory
* @dataProvider providerFindDriverAutoloadDirectoryException
*/
public function testFindDriverAutoloadDirectoryException($expected_message, $namespace, $include_tests) {
new Settings(['extension_discovery_scan_tests' => $include_tests]);
if ($include_tests === FALSE) {
// \Drupal\Core\Extension\ExtensionDiscovery::scan() needs
// drupal_valid_test_ua().
include $this->root . '/core/includes/bootstrap.inc';
}
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage($expected_message);
Database::findDriverAutoloadDirectory($namespace, $this->root);
}
/**
* Data provider for ::testFindDriverAutoloadDirectoryException().
*
* @return array
*/
public function providerFindDriverAutoloadDirectoryException() {
return [
'test module but tests not included' => ["Cannot find the module 'driver_test' for the database driver namespace 'Drupal\driver_test\Driver\Database\DrivertestMysql'", 'Drupal\driver_test\Driver\Database\DrivertestMysql', FALSE],
'non-existent driver in test module' => ["Cannot find the database driver namespace 'Drupal\driver_test\Driver\Database\sqlite' in module 'driver_test'", 'Drupal\driver_test\Driver\Database\sqlite', TRUE],
'non-existent module' => ["Cannot find the module 'does_not_exist' for the database driver namespace 'Drupal\does_not_exist\Driver\Database\mysql'", 'Drupal\does_not_exist\Driver\Database\mysql', TRUE],
];
}
/**
* Adds a database driver that uses the D8's Drupal\Driver\Database namespace.
*/
protected function addD8CustomDrivers() {
$this->additionalClassloader->addPsr4("Drupal\\Driver\\Database\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/corefake");
}
/**
* Adds database drivers that are provided by modules.
*/
protected function addModuleDrivers() {
$this->additionalClassloader->addPsr4("Drupal\\driver_test\\Driver\\Database\\DrivertestMysql\\", __DIR__ . "/../../../../../modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql");
$this->additionalClassloader->addPsr4("Drupal\\corefake\\Driver\\Database\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/module/corefake/src/Driver/Database/corefake");
}
}

View File

@ -6,6 +6,7 @@ use Composer\Autoload\ClassLoader;
use Drupal\Core\Database\Driver\mysql\Install\Tasks as MysqlInstallTasks;
use Drupal\Driver\Database\fake\Install\Tasks as FakeInstallTasks;
use Drupal\Driver\Database\corefake\Install\Tasks as CustomCoreFakeInstallTasks;
use Drupal\driver_test\Driver\Database\DrivertestMysql\Install\Tasks as DriverTestMysqlInstallTasks;
use Drupal\Tests\UnitTestCase;
/**
@ -33,14 +34,15 @@ class InstallerObjectTest extends UnitTestCase {
$additional_class_loader->addPsr4("Drupal\\Driver\\Database\\fake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/fake");
$additional_class_loader->addPsr4("Drupal\\Core\\Database\\Driver\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/core/corefake");
$additional_class_loader->addPsr4("Drupal\\Driver\\Database\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/corefake");
$additional_class_loader->addPsr4("Drupal\\driver_test\\Driver\\Database\\DrivertestMysql\\", __DIR__ . "/../../../../../../modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql");
$additional_class_loader->register(TRUE);
}
/**
* @dataProvider providerDbInstallerObject
*/
public function testDbInstallerObject($driver, $expected_class_name) {
$object = db_installer_object($driver);
public function testDbInstallerObject($driver, $namespace, $expected_class_name) {
$object = db_installer_object($driver, $namespace);
$this->assertEquals(get_class($object), $expected_class_name);
}
@ -50,18 +52,22 @@ class InstallerObjectTest extends UnitTestCase {
* @return array
* Array of arrays with the following elements:
* - driver: The driver name.
* - namespace: The namespace providing the driver.
* - class: The fully qualified class name of the expected install task.
*/
public function providerDbInstallerObject() {
return [
// A driver only in the core namespace.
['mysql', MysqlInstallTasks::class],
['mysql', NULL, MysqlInstallTasks::class],
// A driver only in the custom namespace.
['fake', FakeInstallTasks::class],
['fake', "Drupal\\Driver\\Database\\fake", FakeInstallTasks::class],
// A driver in both namespaces. The custom one takes precedence.
['corefake', CustomCoreFakeInstallTasks::class],
['corefake', "Drupal\\Driver\\Database\\corefake", CustomCoreFakeInstallTasks::class],
// A driver from a module that has a different name as the driver.
['DrivertestMysql', "Drupal\\driver_test\\Driver\\Database\\DrivertestMysql", DriverTestMysqlInstallTasks::class],
];
}

View File

@ -2,8 +2,8 @@
namespace Drupal\Tests\Core\Database;
use Composer\Autoload\ClassLoader;
use Drupal\Core\Database\Database;
use Drupal\Core\Site\Settings;
use Drupal\Tests\UnitTestCase;
/**
@ -26,11 +26,21 @@ class UrlConversionTest extends UnitTestCase {
*/
protected function setUp() {
parent::setUp();
$additional_class_loader = new ClassLoader();
$additional_class_loader->addPsr4("Drupal\\Driver\\Database\\fake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/fake");
$additional_class_loader->addPsr4("Drupal\\Core\\Database\\Driver\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/core/corefake");
$additional_class_loader->addPsr4("Drupal\\Driver\\Database\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/corefake");
$additional_class_loader->register(TRUE);
$this->root = dirname(dirname(dirname(dirname(dirname(dirname(dirname(__FILE__)))))));
// Mock the container so we don't need to mock drupal_valid_test_ua().
// @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
$container = $this->createMock('Symfony\Component\DependencyInjection\ContainerInterface');
$container->expects($this->any())
->method('has')
->with('kernel')
->willReturn(TRUE);
$container->expects($this->any())
->method('get')
->with('site.path')
->willReturn('');
\Drupal::setContainer($container);
new Settings(['extension_discovery_scan_tests' => TRUE]);
}
/**
@ -39,7 +49,7 @@ class UrlConversionTest extends UnitTestCase {
* @dataProvider providerConvertDbUrlToConnectionInfo
*/
public function testDbUrltoConnectionConversion($root, $url, $database_array) {
$result = Database::convertDbUrlToConnectionInfo($url, $root);
$result = Database::convertDbUrlToConnectionInfo($url, $root ?: $this->root);
$this->assertEquals($database_array, $result);
}
@ -116,30 +126,66 @@ class UrlConversionTest extends UnitTestCase {
'namespace' => 'Drupal\Core\Database\Driver\sqlite',
],
],
'Fake custom database driver, without prefix' => [
'MySQL contrib test driver without prefix' => [
'',
'fake://fake_user:fake_pass@fake_host:3456/fake_database',
'DrivertestMysql://test_user:test_pass@test_host:3306/test_database?module=driver_test',
[
'driver' => 'fake',
'username' => 'fake_user',
'password' => 'fake_pass',
'host' => 'fake_host',
'database' => 'fake_database',
'port' => 3456,
'namespace' => 'Drupal\Driver\Database\fake',
'driver' => 'DrivertestMysql',
'username' => 'test_user',
'password' => 'test_pass',
'host' => 'test_host',
'database' => 'test_database',
'port' => 3306,
'namespace' => 'Drupal\driver_test\Driver\Database\DrivertestMysql',
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql/',
],
],
'Fake core driver with custom override, without prefix' => [
'MySQL contrib test driver with prefix' => [
'',
'corefake://fake_user:fake_pass@fake_host:3456/fake_database',
'DrivertestMysql://test_user:test_pass@test_host:3306/test_database?module=driver_test#bar',
[
'driver' => 'corefake',
'username' => 'fake_user',
'password' => 'fake_pass',
'host' => 'fake_host',
'database' => 'fake_database',
'port' => 3456,
'namespace' => 'Drupal\Driver\Database\corefake',
'driver' => 'DrivertestMysql',
'username' => 'test_user',
'password' => 'test_pass',
'host' => 'test_host',
'database' => 'test_database',
'prefix' => [
'default' => 'bar',
],
'port' => 3306,
'namespace' => 'Drupal\driver_test\Driver\Database\DrivertestMysql',
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql/',
],
],
'PostgreSQL contrib test driver without prefix' => [
'',
'DrivertestPgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test',
[
'driver' => 'DrivertestPgsql',
'username' => 'test_user',
'password' => 'test_pass',
'host' => 'test_host',
'database' => 'test_database',
'port' => 5432,
'namespace' => 'Drupal\driver_test\Driver\Database\DrivertestPgsql',
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestPgsql/',
],
],
'PostgreSQL contrib test driver with prefix' => [
'',
'DrivertestPgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test#bar',
[
'driver' => 'DrivertestPgsql',
'username' => 'test_user',
'password' => 'test_pass',
'host' => 'test_host',
'database' => 'test_database',
'prefix' => [
'default' => 'bar',
],
'port' => 5432,
'namespace' => 'Drupal\driver_test\Driver\Database\DrivertestPgsql',
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestPgsql/',
],
],
];
@ -233,11 +279,67 @@ class UrlConversionTest extends UnitTestCase {
];
$expected_url4 = 'sqlite://localhost/test_database#pre';
$info5 = [
'database' => 'test_database',
'username' => 'test_user',
'password' => 'test_pass',
'prefix' => '',
'host' => 'test_host',
'port' => '3306',
'driver' => 'DrivertestMysql',
'namespace' => 'Drupal\\driver_test\\Driver\\Database\\DrivertestMysql',
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql/',
];
$expected_url5 = 'DrivertestMysql://test_user:test_pass@test_host:3306/test_database?module=driver_test';
$info6 = [
'database' => 'test_database',
'username' => 'test_user',
'password' => 'test_pass',
'prefix' => 'pre',
'host' => 'test_host',
'port' => '3306',
'driver' => 'DrivertestMysql',
'namespace' => 'Drupal\\driver_test\\Driver\\Database\\DrivertestMysql',
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql/',
];
$expected_url6 = 'DrivertestMysql://test_user:test_pass@test_host:3306/test_database?module=driver_test#pre';
$info7 = [
'database' => 'test_database',
'username' => 'test_user',
'password' => 'test_pass',
'prefix' => '',
'host' => 'test_host',
'port' => '5432',
'driver' => 'DrivertestPgsql',
'namespace' => 'Drupal\\driver_test\\Driver\\Database\\DrivertestPgsql',
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/drivertestpqsql/',
];
$expected_url7 = 'DrivertestPgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test';
$info8 = [
'database' => 'test_database',
'username' => 'test_user',
'password' => 'test_pass',
'prefix' => 'pre',
'host' => 'test_host',
'port' => '5432',
'driver' => 'DrivertestPgsql',
'namespace' => 'Drupal\\driver_test\\Driver\\Database\\DrivertestPgsql',
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/drivertestpqsql/',
];
$expected_url8 = 'DrivertestPgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test#pre';
return [
[$info1, $expected_url1],
[$info2, $expected_url2],
[$info3, $expected_url3],
[$info4, $expected_url4],
[$info5, $expected_url5],
[$info6, $expected_url6],
[$info7, $expected_url7],
[$info8, $expected_url8],
];
}
@ -282,4 +384,24 @@ class UrlConversionTest extends UnitTestCase {
];
}
/**
* @covers ::convertDbUrlToConnectionInfo
*/
public function testDriverModuleDoesNotExist() {
$url = 'mysql://test_user:test_pass@test_host:3306/test_database?module=does_not_exist';
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage("Cannot find the module 'does_not_exist' for the database driver namespace 'Drupal\does_not_exist\Driver\Database\mysql'");
Database::convertDbUrlToConnectionInfo($url, $this->root);
}
/**
* @covers ::convertDbUrlToConnectionInfo
*/
public function testModuleDriverDoesNotExist() {
$url = 'mysql://test_user:test_pass@test_host:3306/test_database?module=driver_test';
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage("Cannot find the database driver namespace 'Drupal\driver_test\Driver\Database\mysql' in module 'driver_test'");
Database::convertDbUrlToConnectionInfo($url, $this->root);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Drupal\corefake\Driver\Database\corefake;
use Drupal\Driver\Database\fake\Connection as BaseConnection;
class Connection extends BaseConnection {
/**
* {@inheritdoc}
*/
public $driver = 'corefake';
}

View File

@ -0,0 +1,16 @@
<?php
namespace Drupal\corefake\Driver\Database\corefake\Install;
use Drupal\Core\Database\Install\Tasks as InstallTasks;
class Tasks extends InstallTasks {
/**
* {@inheritdoc}
*/
public function name() {
return 'corefake';
}
}

View File

@ -105,6 +105,16 @@ $databases = [];
* webserver. For most other drivers, you must specify a
* username, password, host, and database name.
*
* Drupal core implements drivers for mysql, pgsql, and sqlite. Other drivers
* can be provided by contributed or custom modules. To use a contributed or
* custom driver, the "namespace" property must be set to the namespace of the
* driver. The code in this namespace must be autoloadable prior to connecting
* to the database, and therefore, prior to when module root namespaces are
* added to the autoloader. To add the driver's namespace to the autoloader,
* set the "autoload" property to the PSR-4 base directory of the driver's
* namespace. This is optional for projects managed with Composer if the
* driver's namespace is in Composer's autoloader.
*
* Transaction support is enabled by default for all drivers that support it,
* including MySQL. To explicitly disable it, set the 'transactions' key to
* FALSE.
@ -224,6 +234,20 @@ $databases = [];
* 'database' => '/path/to/databasefilename',
* ];
* @endcode
*
* Sample Database configuration format for a driver in a contributed module:
* @code
* $databases['default']['default'] = [
* 'driver' => 'mydriver',
* 'namespace' => 'Drupal\mymodule\Driver\Database\mydriver',
* 'autoload' => 'modules/mymodule/src/Driver/Database/mydriver/',
* 'database' => 'databasename',
* 'username' => 'sqlusername',
* 'password' => 'sqlpassword',
* 'host' => 'localhost',
* 'prefix' => '',
* ];
* @endcode
*/
/**