Commit a5a2ce92 authored by catch's avatar catch

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
......@@ -740,6 +740,20 @@ public function 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.
*
......@@ -1088,6 +1102,16 @@ public function supportsTransactionalDDL() {
*/
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.
......
......@@ -10,6 +10,7 @@
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseNotFoundException;
use Drupal\Core\Database\TransactionCommitFailedException;
use Drupal\Core\Database\DatabaseException;
use Drupal\Core\Database\Connection as DatabaseConnection;
......@@ -23,6 +24,11 @@
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.
*
......@@ -47,7 +53,9 @@ public function __construct(array $connection_options = array()) {
// Default to TCP connection on port 3306.
$dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
}
$dsn .= ';dbname=' . $connection_options['database'];
if (!empty($connection_options['database'])) {
$dsn .= ';dbname=' . $connection_options['database'];
}
// Allow PDO options to be overridden.
$connection_options += array(
'pdo' => array(),
......@@ -113,6 +121,28 @@ public function databaseType() {
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) {
// We don't want to override any of the defaults.
return NULL;
......
......@@ -8,6 +8,9 @@
namespace Drupal\Core\Database\Driver\mysql\Install;
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.
......@@ -33,4 +36,50 @@ public function name() {
public function minimumVersion() {
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 @@
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Connection as DatabaseConnection;
use Drupal\Core\Database\DatabaseNotFoundException;
use Drupal\Core\Database\StatementInterface;
use Locale;
use PDO;
use PDOException;
......@@ -26,6 +28,11 @@ class Connection extends DatabaseConnection {
*/
const POSTGRESQL_NEXTID_LOCK = 1000;
/**
* Error code for "Unknown database" error.
*/
const DATABASE_NOT_FOUND = 7;
public function __construct(array $connection_options = array()) {
// This driver defaults to transaction support, except if explicitly passed FALSE.
$this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
......@@ -55,6 +62,7 @@ public function __construct(array $connection_options = array()) {
$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'];
// Allow PDO options to be overridden.
......@@ -167,6 +175,36 @@ public function databaseType() {
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) {
static $specials = array(
// In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE
......
......@@ -9,6 +9,8 @@
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Install\Tasks as InstallTasks;
use Drupal\Core\Database\Driver\pgsql\Connection;
use Drupal\Core\Database\DatabaseNotFoundException;
use Exception;
......@@ -41,6 +43,62 @@ public function minimumVersion() {
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.
*/
......
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Database\Driver\sqlite;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseNotFoundException;
use Drupal\Core\Database\TransactionNoActiveException;
use Drupal\Core\Database\TransactionNameNonUniqueException;
use Drupal\Core\Database\TransactionCommitFailedException;
......@@ -16,6 +17,7 @@
use PDO;
use Exception;
use SplFileInfo;
/**
* Specific SQLite implementation of DatabaseConnection.
......@@ -32,6 +34,11 @@ class Connection extends DatabaseConnection {
*/
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.
*
......@@ -267,6 +274,22 @@ public function databaseType() {
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) {
// We don't want to override any of the defaults.
static $specials = array(
......
......@@ -7,9 +7,12 @@
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 SplFileInfo;
use Exception;
class Tasks extends InstallTasks {
protected $pdoDriver = 'sqlite';
......@@ -41,17 +44,61 @@ public function getFormOptions($database) {
return $form;
}
public function validateDatabaseSettings($database) {
// Perform standard validation.
$errors = parent::validateDatabaseSettings($database);
// Verify the database is writable.
$db_directory = new SplFileInfo(dirname($database['database']));
if (!$db_directory->isWritable()) {
$errors[$database['driver'] . '][database'] = st('The directory you specified is not writable by the web server.');
/**
* 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'];
// 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');
return $errors;
// 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();
// 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;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment