Skip to content
Snippets Groups Projects
Commit a5a2ce92 authored by catch's avatar catch
Browse files

Issue #203955 by pdrake, webchick, andrewsl, iflista: Create database at...

Issue #203955 by pdrake, webchick, andrewsl, iflista: Create database at installation time if the db user has appropriate permissions.
parent 465d86e2
Branches
Tags
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
...@@ -740,6 +740,20 @@ public function schema() { ...@@ -740,6 +740,20 @@ public function schema() {
return $this->schema; return $this->schema;
} }
/**
* Escapes a database name string.
*
* Force all database names to be strictly alphanumeric-plus-underscore.
* For some database drivers, it may also wrap the database name in
* database-specific escape characters.
*
* @return string
* The sanitized database name string.
*/
public function escapeDatabase($database) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $database);
}
/** /**
* Escapes a table name string. * Escapes a table name string.
* *
...@@ -1088,6 +1102,16 @@ public function supportsTransactionalDDL() { ...@@ -1088,6 +1102,16 @@ public function supportsTransactionalDDL() {
*/ */
abstract public function databaseType(); abstract public function databaseType();
/**
* Creates a database.
*
* In order to use this method, you must be connected without a database
* specified.
*
* @param string $database
* The name of the database to create.
*/
abstract public function createDatabase($database);
/** /**
* Gets any special processing requirements for the condition operator. * Gets any special processing requirements for the condition operator.
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
use Drupal\Core\Database\DatabaseExceptionWrapper; use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Database\Database; use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseNotFoundException;
use Drupal\Core\Database\TransactionCommitFailedException; use Drupal\Core\Database\TransactionCommitFailedException;
use Drupal\Core\Database\DatabaseException; use Drupal\Core\Database\DatabaseException;
use Drupal\Core\Database\Connection as DatabaseConnection; use Drupal\Core\Database\Connection as DatabaseConnection;
...@@ -23,6 +24,11 @@ ...@@ -23,6 +24,11 @@
class Connection extends DatabaseConnection { class Connection extends DatabaseConnection {
/**
* Error code for "Unknown database" error.
*/
const DATABASE_NOT_FOUND = 1049;
/** /**
* Flag to indicate if the cleanup function in __destruct() should run. * Flag to indicate if the cleanup function in __destruct() should run.
* *
...@@ -47,7 +53,9 @@ public function __construct(array $connection_options = array()) { ...@@ -47,7 +53,9 @@ public function __construct(array $connection_options = array()) {
// Default to TCP connection on port 3306. // Default to TCP connection on port 3306.
$dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']); $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
} }
if (!empty($connection_options['database'])) {
$dsn .= ';dbname=' . $connection_options['database']; $dsn .= ';dbname=' . $connection_options['database'];
}
// Allow PDO options to be overridden. // Allow PDO options to be overridden.
$connection_options += array( $connection_options += array(
'pdo' => array(), 'pdo' => array(),
...@@ -113,6 +121,28 @@ public function databaseType() { ...@@ -113,6 +121,28 @@ public function databaseType() {
return 'mysql'; return 'mysql';
} }
/**
* Overrides \Drupal\Core\Database\Connection::createDatabase().
*
* @param string $database
* The name of the database to create.
*
* @throws DatabaseNotFoundException
*/
public function createDatabase($database) {
// Escape the database name.
$database = Database::getConnection()->escapeDatabase($database);
try {
// Create the database and set it as active.
$this->exec("CREATE DATABASE $database");
$this->exec("USE $database");
}
catch (\Exception $e) {
throw new DatabaseNotFoundException($e->getMessage());
}
}
public function mapConditionOperator($operator) { public function mapConditionOperator($operator) {
// We don't want to override any of the defaults. // We don't want to override any of the defaults.
return NULL; return NULL;
......
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
namespace Drupal\Core\Database\Driver\mysql\Install; namespace Drupal\Core\Database\Driver\mysql\Install;
use Drupal\Core\Database\Install\Tasks as InstallTasks; use Drupal\Core\Database\Install\Tasks as InstallTasks;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Driver\mysql\Connection;
use Drupal\Core\Database\DatabaseNotFoundException;
/** /**
* Specifies installation tasks for MySQL and equivalent databases. * Specifies installation tasks for MySQL and equivalent databases.
...@@ -33,4 +36,50 @@ public function name() { ...@@ -33,4 +36,50 @@ public function name() {
public function minimumVersion() { public function minimumVersion() {
return '5.0.15'; return '5.0.15';
} }
/**
* Check database connection and attempt to create database if the database is
* missing.
*/
protected function connect() {
try {
// This doesn't actually test the connection.
db_set_active();
// Now actually do a check.
Database::getConnection();
$this->pass('Drupal can CONNECT to the database ok.');
}
catch (\Exception $e) {
// Attempt to create the database if it is not found.
if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
// Remove the database string from connection info.
$connection_info = Database::getConnectionInfo();
$database = $connection_info['default']['database'];
unset($connection_info['default']['database']);
// In order to change the Database::$databaseInfo array, need to remove
// the active connection, then re-add it with the new info.
Database::removeConnection('default');
Database::addConnectionInfo('default', 'default', $connection_info['default']);
try {
// Now, attempt the connection again; if it's successful, attempt to
// create the database.
Database::getConnection()->createDatabase($database);
}
catch (DatabaseNotFoundException $e) {
// Still no dice; probably a permission issue. Raise the error to the
// installer.
$this->fail(st('Database %database not found. The server reports the following message when attempting to create the database: %error.', array('%database' => $database, '%error' => $e->getMessage())));
}
}
else {
// Database connection failed for some other reason than the database
// not existing.
$this->fail(st('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist or does the database user have sufficient privileges to create the database?</li><li>Have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
return FALSE;
}
}
return TRUE;
}
} }
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
use Drupal\Core\Database\Database; use Drupal\Core\Database\Database;
use Drupal\Core\Database\Connection as DatabaseConnection; use Drupal\Core\Database\Connection as DatabaseConnection;
use Drupal\Core\Database\DatabaseNotFoundException;
use Drupal\Core\Database\StatementInterface; use Drupal\Core\Database\StatementInterface;
use Locale;
use PDO; use PDO;
use PDOException; use PDOException;
...@@ -26,6 +28,11 @@ class Connection extends DatabaseConnection { ...@@ -26,6 +28,11 @@ class Connection extends DatabaseConnection {
*/ */
const POSTGRESQL_NEXTID_LOCK = 1000; const POSTGRESQL_NEXTID_LOCK = 1000;
/**
* Error code for "Unknown database" error.
*/
const DATABASE_NOT_FOUND = 7;
public function __construct(array $connection_options = array()) { public function __construct(array $connection_options = array()) {
// This driver defaults to transaction support, except if explicitly passed FALSE. // This driver defaults to transaction support, except if explicitly passed FALSE.
$this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE); $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
...@@ -55,6 +62,7 @@ public function __construct(array $connection_options = array()) { ...@@ -55,6 +62,7 @@ public function __construct(array $connection_options = array()) {
$this->connectionOptions = $connection_options; $this->connectionOptions = $connection_options;
$connection_options['database'] = (!empty($connection_options['database']) ? $connection_options['database'] : 'template1');
$dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port']; $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'];
// Allow PDO options to be overridden. // Allow PDO options to be overridden.
...@@ -167,6 +175,36 @@ public function databaseType() { ...@@ -167,6 +175,36 @@ public function databaseType() {
return 'pgsql'; return 'pgsql';
} }
/**
* Overrides \Drupal\Core\Database\Connection::createDatabase().
*
* @param string $database
* The name of the database to create.
*
* @throws DatabaseNotFoundException
*/
public function createDatabase($database) {
// Escape the database name.
$database = Database::getConnection()->escapeDatabase($database);
// If the PECL intl extension is installed, use it to determine the proper
// locale. Otherwise, fall back to en_US.
if (class_exists('Locale')) {
$locale = Locale::getDefault();
}
else {
$locale = 'en_US';
}
try {
// Create the database and set it as active.
$this->exec("CREATE DATABASE $database WITH TEMPLATE template0 ENCODING='utf8' LC_CTYPE='$locale.utf8' LC_COLLATE='$locale.utf8'");
}
catch (\Exception $e) {
throw new DatabaseNotFoundException($e->getMessage());
}
}
public function mapConditionOperator($operator) { public function mapConditionOperator($operator) {
static $specials = array( static $specials = array(
// In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
use Drupal\Core\Database\Database; use Drupal\Core\Database\Database;
use Drupal\Core\Database\Install\Tasks as InstallTasks; use Drupal\Core\Database\Install\Tasks as InstallTasks;
use Drupal\Core\Database\Driver\pgsql\Connection;
use Drupal\Core\Database\DatabaseNotFoundException;
use Exception; use Exception;
...@@ -41,6 +43,62 @@ public function minimumVersion() { ...@@ -41,6 +43,62 @@ public function minimumVersion() {
return '8.3'; return '8.3';
} }
/**
* Check database connection and attempt to create database if the database is
* missing.
*/
protected function connect() {
try {
// This doesn't actually test the connection.
db_set_active();
// Now actually do a check.
Database::getConnection();
$this->pass('Drupal can CONNECT to the database ok.');
}
catch (Exception $e) {
// Attempt to create the database if it is not found.
if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
// Remove the database string from connection info.
$connection_info = Database::getConnectionInfo();
$database = $connection_info['default']['database'];
unset($connection_info['default']['database']);
// In order to change the Database::$databaseInfo array, need to remove
// the active connection, then re-add it with the new info.
Database::removeConnection('default');
Database::addConnectionInfo('default', 'default', $connection_info['default']);
try {
// Now, attempt the connection again; if it's successful, attempt to
// create the database.
Database::getConnection()->createDatabase($database);
Database::closeConnection();
// Now, restore the database config.
Database::removeConnection('default');
$connection_info['default']['database'] = $database;
Database::addConnectionInfo('default', 'default', $connection_info['default']);
// Check the database connection.
Database::getConnection();
$this->pass('Drupal can CONNECT to the database ok.');
}
catch (DatabaseNotFoundException $e) {
// Still no dice; probably a permission issue. Raise the error to the
// installer.
$this->fail(st('Database %database not found. The server reports the following message when attempting to create the database: %error.', array('%database' => $database, '%error' => $e->getMessage())));
}
}
else {
// Database connection failed for some other reason than the database
// not existing.
$this->fail(st('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
return FALSE;
}
}
return TRUE;
}
/** /**
* Check encoding is UTF8. * Check encoding is UTF8.
*/ */
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
namespace Drupal\Core\Database\Driver\sqlite; namespace Drupal\Core\Database\Driver\sqlite;
use Drupal\Core\Database\Database; use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseNotFoundException;
use Drupal\Core\Database\TransactionNoActiveException; use Drupal\Core\Database\TransactionNoActiveException;
use Drupal\Core\Database\TransactionNameNonUniqueException; use Drupal\Core\Database\TransactionNameNonUniqueException;
use Drupal\Core\Database\TransactionCommitFailedException; use Drupal\Core\Database\TransactionCommitFailedException;
...@@ -16,6 +17,7 @@ ...@@ -16,6 +17,7 @@
use PDO; use PDO;
use Exception; use Exception;
use SplFileInfo;
/** /**
* Specific SQLite implementation of DatabaseConnection. * Specific SQLite implementation of DatabaseConnection.
...@@ -32,6 +34,11 @@ class Connection extends DatabaseConnection { ...@@ -32,6 +34,11 @@ class Connection extends DatabaseConnection {
*/ */
protected $savepointSupport = FALSE; protected $savepointSupport = FALSE;
/**
* Error code for "Unable to open database file" error.
*/
const DATABASE_NOT_FOUND = 14;
/** /**
* Whether or not the active transaction (if any) will be rolled back. * Whether or not the active transaction (if any) will be rolled back.
* *
...@@ -267,6 +274,22 @@ public function databaseType() { ...@@ -267,6 +274,22 @@ public function databaseType() {
return 'sqlite'; return 'sqlite';
} }
/**
* Overrides \Drupal\Core\Database\Connection::createDatabase().
*
* @param string $database
* The name of the database to create.
*
* @throws DatabaseNotFoundException
*/
public function createDatabase($database) {
// Verify the database is writable.
$db_directory = new SplFileInfo(dirname($database));
if (!$db_directory->isDir() && !drupal_mkdir($db_directory->getPathName(), 0755, TRUE)) {
throw new DatabaseNotFoundException('Unable to create database directory ' . $db_directory->getPathName());
}
}
public function mapConditionOperator($operator) { public function mapConditionOperator($operator) {
// We don't want to override any of the defaults. // We don't want to override any of the defaults.
static $specials = array( static $specials = array(
......
...@@ -7,9 +7,12 @@ ...@@ -7,9 +7,12 @@
namespace Drupal\Core\Database\Driver\sqlite\Install; namespace Drupal\Core\Database\Driver\sqlite\Install;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Driver\sqlite\Connection;
use Drupal\Core\Database\DatabaseNotFoundException;
use Drupal\Core\Database\Install\Tasks as InstallTasks; use Drupal\Core\Database\Install\Tasks as InstallTasks;
use SplFileInfo; use Exception;
class Tasks extends InstallTasks { class Tasks extends InstallTasks {
protected $pdoDriver = 'sqlite'; protected $pdoDriver = 'sqlite';
...@@ -41,17 +44,61 @@ public function getFormOptions($database) { ...@@ -41,17 +44,61 @@ public function getFormOptions($database) {
return $form; return $form;
} }
public function validateDatabaseSettings($database) { /**
// Perform standard validation. * Check database connection and attempt to create database if the database is
$errors = parent::validateDatabaseSettings($database); * missing.
*/
// Verify the database is writable. protected function connect() {
$db_directory = new SplFileInfo(dirname($database['database'])); try {
if (!$db_directory->isWritable()) { // This doesn't actually test the connection.
$errors[$database['driver'] . '][database'] = st('The directory you specified is not writable by the web server.'); db_set_active();
// Now actually do a check.
Database::getConnection();
$this->pass('Drupal can CONNECT to the database ok.');
} }
catch (Exception $e) {
// Attempt to create the database if it is not found.
if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
// Remove the database string from connection info.
$connection_info = Database::getConnectionInfo();
$database = $connection_info['default']['database'];
// We cannot use file_directory_temp() here because we haven't yet
// successfully connected to the database.
$connection_info['default']['database'] = drupal_tempnam(sys_get_temp_dir(), 'sqlite');
// In order to change the Database::$databaseInfo array, need to remove
// the active connection, then re-add it with the new info.
Database::removeConnection('default');
Database::addConnectionInfo('default', 'default', $connection_info['default']);
try {
Database::getConnection()->createDatabase($database);
Database::closeConnection();
return $errors; // Now, restore the database config.
Database::removeConnection('default');
$connection_info['default']['database'] = $database;
Database::addConnectionInfo('default', 'default', $connection_info['default']);
// Check the database connection.
Database::getConnection();
$this->pass('Drupal can CONNECT to the database ok.');
}
catch (DatabaseNotFoundException $e) {
// Still no dice; probably a permission issue. Raise the error to the
// installer.
$this->fail(st('Database %database not found. The server reports the following message when attempting to create the database: %error.', array('%database' => $database, '%error' => $e->getMessage())));
}
}
else {
// Database connection failed for some other reason than the database
// not existing.
$this->fail(st('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
return FALSE;
}
}
return TRUE;
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment