summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorwebchick <drupal@webchick.net>2015-10-02 11:59:39 -0700
committerwebchick <drupal@webchick.net>2015-10-02 11:59:39 -0700
commitb29158839c8cb829132323a54230eb85ab7a6765 (patch)
treefe302a1a86ece0f9cd2b0ce0d807f59d3379d64f
parentb904c4b375ab4cb3b029d2b65512545d2e5192af (diff)
downloaddrupal-b29158839c8cb829132323a54230eb85ab7a6765.tar.gz
drupal-b29158839c8cb829132323a54230eb85ab7a6765.zip
Issue #2550291 by neclimdul, phenaproxima: Improve and generalize database dump tools
-rw-r--r--core/lib/Drupal/Core/Command/DbCommandBase.php65
-rw-r--r--core/lib/Drupal/Core/Command/DbDumpApplication.php32
-rw-r--r--core/lib/Drupal/Core/Command/DbDumpCommand.php180
-rw-r--r--core/lib/Drupal/Core/Command/DbImportCommand.php72
-rw-r--r--core/lib/Drupal/Core/Command/DbToolsApplication.php34
-rw-r--r--core/modules/system/src/Tests/Update/DbDumpTest.php14
-rw-r--r--core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php137
-rw-r--r--core/modules/system/tests/src/Kernel/Scripts/DbDumpCommandTest.php88
-rw-r--r--core/modules/system/tests/src/Kernel/Scripts/DbImportCommandTest.php78
-rw-r--r--core/modules/system/tests/src/Kernel/Scripts/DbToolsApplicationTest.php41
-rw-r--r--core/scripts/db-tools.php22
-rwxr-xr-xcore/scripts/dump-database-d8-mysql.php3
12 files changed, 619 insertions, 147 deletions
diff --git a/core/lib/Drupal/Core/Command/DbCommandBase.php b/core/lib/Drupal/Core/Command/DbCommandBase.php
new file mode 100644
index 00000000000..bd401d80497
--- /dev/null
+++ b/core/lib/Drupal/Core/Command/DbCommandBase.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Command\DbCommandBase.
+ */
+
+namespace Drupal\Core\Command;
+
+use Drupal\Core\Database\Database;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * Base command that abstracts handling of database connection arguments.
+ */
+class DbCommandBase extends Command {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure() {
+ $this->addOption('database', NULL, InputOption::VALUE_OPTIONAL, 'The database connection name to use.', 'default')
+ ->addOption('database-url', 'db-url', InputOption::VALUE_OPTIONAL, 'A database url to parse and use as the database connection.')
+ ->addOption('prefix', NULL, InputOption::VALUE_OPTIONAL, 'Override or set the table prefix used in the database connection.');
+ }
+
+ /**
+ * Parse input options decide on a database.
+ *
+ * @param \Symfony\Component\Console\Input\InputInterface $input
+ * Input object.
+ * @return \Drupal\Core\Database\Connection
+ */
+ protected function getDatabaseConnection(InputInterface $input) {
+ // Load connection from a url.
+ if ($input->getOption('database-url')) {
+ // @todo this could probably be refactored to not use a global connection.
+ // Ensure database connection isn't set.
+ if (Database::getConnectionInfo('db-tools')) {
+ throw new \RuntimeException('Database "db-tools" is already defined. Cannot define database provided.');
+ }
+ $info = Database::convertDbUrlToConnectionInfo($input->getOption('database-url'), \Drupal::root());
+ Database::addConnectionInfo('db-tools', 'default', $info);
+ $key = 'db-tools';
+ }
+ else {
+ $key = $input->getOption('database');
+ }
+
+ // If they supplied a prefix, replace it in the connection information.
+ $prefix = $input->getOption('prefix');
+ if ($prefix) {
+ $info = Database::getConnectionInfo($key)['default'];
+ $info['prefix']['default'] = $prefix;
+
+ Database::removeConnection($key);
+ Database::addConnectionInfo($key, 'default', $info);
+ }
+
+ return Database::getConnection('default', $key);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Command/DbDumpApplication.php b/core/lib/Drupal/Core/Command/DbDumpApplication.php
index 080e20f88b2..bb5b715b43b 100644
--- a/core/lib/Drupal/Core/Command/DbDumpApplication.php
+++ b/core/lib/Drupal/Core/Command/DbDumpApplication.php
@@ -7,8 +7,6 @@
namespace Drupal\Core\Command;
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Extension\ModuleHandlerInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
@@ -18,34 +16,6 @@ use Symfony\Component\Console\Input\InputInterface;
class DbDumpApplication extends Application {
/**
- * The database connection.
- *
- * @var \Drupal\Core\Database\Connection
- */
- protected $connection;
-
- /**
- * The module handler.
- *
- * @var \Drupal\Core\Extension\ModuleHandlerInterface
- */
- protected $moduleHandler;
-
- /**
- * Construct the application.
- *
- * @param \Drupal\Core\Database\Connection $connection
- * The database connection.
- * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
- * The module handler.
- */
- function __construct(Connection $connection, ModuleHandlerInterface $module_handler) {
- $this->connection = $connection;
- $this->moduleHandler = $module_handler;
- parent::__construct();
- }
-
- /**
* {@inheritdoc}
*/
protected function getCommandName(InputInterface $input) {
@@ -58,7 +28,7 @@ class DbDumpApplication extends Application {
protected function getDefaultCommands() {
// Even though this is a single command, keep the HelpCommand (--help).
$default_commands = parent::getDefaultCommands();
- $default_commands[] = new DbDumpCommand($this->connection, $this->moduleHandler);
+ $default_commands[] = new DbDumpCommand();
return $default_commands;
}
diff --git a/core/lib/Drupal/Core/Command/DbDumpCommand.php b/core/lib/Drupal/Core/Command/DbDumpCommand.php
index 69512705f2d..95e99069d43 100644
--- a/core/lib/Drupal/Core/Command/DbDumpCommand.php
+++ b/core/lib/Drupal/Core/Command/DbDumpCommand.php
@@ -9,9 +9,8 @@ namespace Drupal\Core\Command;
use Drupal\Component\Utility\Variable;
use Drupal\Core\Database\Connection;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
@@ -30,21 +29,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*
* @see \Drupal\Core\Command\DbDumpApplication
*/
-class DbDumpCommand extends Command {
-
- /**
- * The database connection.
- *
- * @var \Drupal\Core\Database\Connection $connection
- */
- protected $connection;
-
- /**
- * The module handler.
- *
- * @var \Drupal\Core\Extension\ModuleHandlerInterface
- */
- protected $moduleHandler;
+class DbDumpCommand extends DbCommandBase {
/**
* An array of table patterns to exclude completely.
@@ -56,80 +41,77 @@ class DbDumpCommand extends Command {
protected $excludeTables = ['simpletest.+'];
/**
- * Table patterns for which to only dump the schema, no data.
- *
- * @var array
- */
- protected $schemaOnly = ['cache.*', 'sessions', 'watchdog'];
-
- /**
- * Construct the database dump command.
- *
- * @param \Drupal\Core\Database\Connection $connection
- * The database connection to use.
- * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
- * The module handler to use.
- */
- function __construct(Connection $connection, ModuleHandlerInterface $module_handler) {
- // Check this is MySQL.
- if ($connection->databaseType() !== 'mysql') {
- throw new \RuntimeException('This script can only be used with MySQL database backends.');
- }
-
- $this->connection = $connection;
- $this->moduleHandler = $module_handler;
- parent::__construct();
- }
-
- /**
* {@inheritdoc}
*/
protected function configure() {
$this->setName('dump-database-d8-mysql')
- ->setDescription('Dump the current database to a generation script');
+ ->setDescription('Dump the current database to a generation script')
+ ->addOption('schema-only', NULL, InputOption::VALUE_OPTIONAL, 'A comma separated list of tables to only export the schema without data.', 'cache.*,sessions,watchdog');
+ parent::configure();
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
+ $connection = $this->getDatabaseConnection($input);
+
// If not explicitly set, disable ANSI which will break generated php.
if ($input->hasParameterOption(['--ansi']) !== TRUE) {
$output->setDecorated(FALSE);
}
- $output->writeln($this->generateScript(), OutputInterface::OUTPUT_RAW);
+ $schema_tables = $input->getOption('schema-only');
+ $schema_tables = explode(',', $schema_tables);
+
+ $output->writeln($this->generateScript($connection, $schema_tables), OutputInterface::OUTPUT_RAW);
}
/**
* Generates the database script.
*
- * @return string
+ * @param \Drupal\Core\Database\Connection $connection
+ * The database connection to use.
+ * @param array $schema_only
+ * Table patterns for which to only dump the schema, no data.
+ * @return string The PHP script.
* The PHP script.
*/
- protected function generateScript() {
+ protected function generateScript(Connection $connection, array $schema_only = []) {
$tables = '';
- foreach ($this->getTables() as $table) {
- $schema = $this->getTableSchema($table);
- $data = $this->getTableData($table);
+
+ $schema_only_patterns = [];
+ foreach ($schema_only as $match) {
+ $schema_only_patterns[] = '/^' . $match . '$/';
+ }
+
+ foreach ($this->getTables($connection) as $table) {
+ $schema = $this->getTableSchema($connection, $table);
+ // Check for schema only.
+ if (empty($schema_only_patterns) || preg_replace($schema_only_patterns, '', $table)) {
+ $data = $this->getTableData($connection, $table);
+ }
+ else {
+ $data = [];
+ }
$tables .= $this->getTableScript($table, $schema, $data);
}
$script = $this->getTemplate();
// Substitute in the tables.
$script = str_replace('{{TABLES}}', trim($tables), $script);
- // Modules.
- $script = str_replace('{{MODULES}}', $this->getModulesScript(), $script);
return trim($script);
}
/**
* Returns a list of tables, not including those set to be excluded.
*
- * @return array
+ * @param \Drupal\Core\Database\Connection $connection
+ * The database connection to use.
+ * @return array An array of table names.
* An array of table names.
*/
- protected function getTables() {
- $tables = array_values($this->connection->schema()->findTables('%'));
+ protected function getTables(Connection $connection) {
+ $tables = array_values($connection->schema()->findTables('%'));
foreach ($tables as $key => $table) {
// Remove any explicitly excluded tables.
@@ -146,6 +128,8 @@ class DbDumpCommand extends Command {
/**
* Returns a schema array for a given table.
*
+ * @param \Drupal\Core\Database\Connection $connection
+ * The database connection to use.
* @param string $table
* The table name.
*
@@ -154,14 +138,19 @@ class DbDumpCommand extends Command {
*
* @todo This implementation is hard-coded for MySQL.
*/
- protected function getTableSchema($table) {
- $query = $this->connection->query("SHOW FULL COLUMNS FROM {" . $table . "}");
+ protected function getTableSchema(Connection $connection, $table) {
+ // Check this is MySQL.
+ if ($connection->databaseType() !== 'mysql') {
+ throw new \RuntimeException('This script can only be used with MySQL database backends.');
+ }
+
+ $query = $connection->query("SHOW FULL COLUMNS FROM {" . $table . "}");
$definition = [];
while (($row = $query->fetchAssoc()) !== FALSE) {
$name = $row['Field'];
// Parse out the field type and meta information.
preg_match('@([a-z]+)(?:\((\d+)(?:,(\d+))?\))?\s*(unsigned)?@', $row['Type'], $matches);
- $type = $this->fieldTypeMap($matches[1]);
+ $type = $this->fieldTypeMap($connection, $matches[1]);
if ($row['Extra'] === 'auto_increment') {
// If this is an auto increment, then the type is 'serial'.
$type = 'serial';
@@ -170,7 +159,7 @@ class DbDumpCommand extends Command {
'type' => $type,
'not null' => $row['Null'] === 'NO',
];
- if ($size = $this->fieldSizeMap($matches[1])) {
+ if ($size = $this->fieldSizeMap($connection, $matches[1])) {
$definition['fields'][$name]['size'] = $size;
}
if (isset($matches[2]) && $type === 'numeric') {
@@ -216,10 +205,10 @@ class DbDumpCommand extends Command {
}
// Set primary key, unique keys, and indexes.
- $this->getTableIndexes($table, $definition);
+ $this->getTableIndexes($connection, $table, $definition);
// Set table collation.
- $this->getTableCollation($table, $definition);
+ $this->getTableCollation($connection, $table, $definition);
return $definition;
}
@@ -227,15 +216,17 @@ class DbDumpCommand extends Command {
/**
* Adds primary key, unique keys, and index information to the schema.
*
+ * @param \Drupal\Core\Database\Connection $connection
+ * The database connection to use.
* @param string $table
* The table to find indexes for.
* @param array &$definition
* The schema definition to modify.
*/
- protected function getTableIndexes($table, &$definition) {
+ protected function getTableIndexes(Connection $connection, $table, &$definition) {
// Note, this query doesn't support ordering, so that is worked around
// below by keying the array on Seq_in_index.
- $query = $this->connection->query("SHOW INDEX FROM {" . $table . "}");
+ $query = $connection->query("SHOW INDEX FROM {" . $table . "}");
while (($row = $query->fetchAssoc()) !== FALSE) {
$index_name = $row['Key_name'];
$column = $row['Column_name'];
@@ -262,13 +253,15 @@ class DbDumpCommand extends Command {
/**
* Set the table collation.
*
+ * @param \Drupal\Core\Database\Connection $connection
+ * The database connection to use.
* @param string $table
* The table to find indexes for.
* @param array &$definition
* The schema definition to modify.
*/
- protected function getTableCollation($table, &$definition) {
- $query = $this->connection->query("SHOW TABLE STATUS LIKE '{" . $table . "}'");
+ protected function getTableCollation(Connection $connection, $table, &$definition) {
+ $query = $connection->query("SHOW TABLE STATUS LIKE '{" . $table . "}'");
$data = $query->fetchAssoc();
// Set `mysql_character_set`. This will be ignored by other backends.
@@ -280,21 +273,17 @@ class DbDumpCommand extends Command {
*
* If a table is set to be schema only, and empty array is returned.
*
+ * @param \Drupal\Core\Database\Connection $connection
+ * The database connection to use.
* @param string $table
* The table to query.
*
* @return array
* The data from the table as an array.
*/
- protected function getTableData($table) {
- // Check for schema only.
- foreach ($this->schemaOnly as $schema_only) {
- if (preg_match('/^' . $schema_only . '$/', $table)) {
- return [];
- }
- }
- $order = $this->getFieldOrder($table);
- $query = $this->connection->query("SELECT * FROM {" . $table . "} " . $order );
+ protected function getTableData(Connection $connection, $table) {
+ $order = $this->getFieldOrder($connection, $table);
+ $query = $connection->query("SELECT * FROM {" . $table . "} " . $order);
$results = [];
while (($row = $query->fetchAssoc()) !== FALSE) {
$results[] = $row;
@@ -305,6 +294,8 @@ class DbDumpCommand extends Command {
/**
* Given a database field type, return a Drupal type.
*
+ * @param \Drupal\Core\Database\Connection $connection
+ * The database connection to use.
* @param string $type
* The MySQL field type.
*
@@ -312,9 +303,9 @@ class DbDumpCommand extends Command {
* The Drupal schema field type. If there is no mapping, the original field
* type is returned.
*/
- protected function fieldTypeMap($type) {
+ protected function fieldTypeMap(Connection $connection, $type) {
// Convert everything to lowercase.
- $map = array_map('strtolower', $this->connection->schema()->getFieldTypeMap());
+ $map = array_map('strtolower', $connection->schema()->getFieldTypeMap());
$map = array_flip($map);
// The MySql map contains type:size. Remove the size part.
@@ -324,15 +315,17 @@ class DbDumpCommand extends Command {
/**
* Given a database field type, return a Drupal size.
*
+ * @param \Drupal\Core\Database\Connection $connection
+ * The database connection to use.
* @param string $type
* The MySQL field type.
*
* @return string
* The Drupal schema field size.
*/
- protected function fieldSizeMap($type) {
+ protected function fieldSizeMap(Connection $connection, $type) {
// Convert everything to lowercase.
- $map = array_map('strtolower', $this->connection->schema()->getFieldTypeMap());
+ $map = array_map('strtolower', $connection->schema()->getFieldTypeMap());
$map = array_flip($map);
$schema_type = explode(':', $map[$type])[0];
@@ -346,24 +339,26 @@ class DbDumpCommand extends Command {
/**
* Gets field ordering for a given table.
*
+ * @param \Drupal\Core\Database\Connection $connection
+ * The database connection to use.
* @param string $table
* The table name.
*
* @return string
* The order string to append to the query.
*/
- protected function getFieldOrder($table) {
+ protected function getFieldOrder(Connection $connection, $table) {
// @todo this is MySQL only since there are no Database API functions for
// table column data.
// @todo this code is duplicated in `core/scripts/migrate-db.sh`.
- $connection_info = $this->connection->getConnectionOptions();
+ $connection_info = $connection->getConnectionOptions();
// Order by primary keys.
$order = '';
$query = "SELECT `COLUMN_NAME` FROM `information_schema`.`COLUMNS`
WHERE (`TABLE_SCHEMA` = '" . $connection_info['database'] . "')
AND (`TABLE_NAME` = '{" . $table . "}') AND (`COLUMN_KEY` = 'PRI')
ORDER BY COLUMN_NAME";
- $results = $this->connection->query($query);
+ $results = $connection->query($query);
while (($row = $results->fetchAssoc()) !== FALSE) {
$order .= $row['COLUMN_NAME'] . ', ';
}
@@ -384,12 +379,9 @@ class DbDumpCommand extends Command {
<?php
/**
* @file
- * Filled installation of Drupal 8.0, for test purposes.
- *
- * This file was generated by the dump-database-d8.php script, from an
- * installation of Drupal 8. It has the following modules installed:
+ * A database agnostic dump for testing purposes.
*
-{{MODULES}}
+ * This file was generated by the Drupal 8.0 db-tools.php script.
*/
use Drupal\Core\Database\Database;
@@ -431,20 +423,4 @@ ENDOFSCRIPT;
return $output;
}
- /**
- * List of modules enabled for insertion into the script docblock.
- *
- * @return string
- * The formatted list of enabled modules.
- */
- protected function getModulesScript() {
- $output = '';
- $modules = $this->moduleHandler->getModuleList();
- ksort($modules);
- foreach ($modules as $module => $filename) {
- $output .= " * - $module\n";
- }
- return rtrim($output, "\n");
- }
-
}
diff --git a/core/lib/Drupal/Core/Command/DbImportCommand.php b/core/lib/Drupal/Core/Command/DbImportCommand.php
new file mode 100644
index 00000000000..de41089d2b1
--- /dev/null
+++ b/core/lib/Drupal/Core/Command/DbImportCommand.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Command\DbDumpCommand.
+ */
+
+namespace Drupal\Core\Command;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\SchemaObjectExistsException;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Provides a command to import the current database from a script.
+ *
+ * This script runs on databases exported using using one of the database dump
+ * commands and imports it into the current database connection.
+ *
+ * @see \Drupal\Core\Command\DbImportApplication
+ */
+class DbImportCommand extends DbCommandBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure() {
+ parent::configure();
+ $this->setName('import')
+ ->setDescription('Import database from a generation script.')
+ ->addArgument('script', InputOption::VALUE_REQUIRED, 'Import script');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $script = $input->getArgument('script');
+ if (!is_file($script)) {
+ $output->writeln('File must exist.');
+ return;
+ }
+
+ $connection = $this->getDatabaseConnection($input);
+ $this->runScript($connection, $script);
+ $output->writeln('Import completed successfully.');
+ }
+
+ /**
+ * Run the database script.
+ *
+ * @param \Drupal\Core\Database\Connection $connection
+ * Connection used by the script when included.
+ * @param string $script
+ * Path to dump script.
+ */
+ protected function runScript(Connection $connection, $script) {
+ if (substr($script, -3) == '.gz') {
+ $script = "compress.zlib://$script";
+ }
+ try {
+ require $script;
+ }
+ catch (SchemaObjectExistsException $e) {
+ throw new \RuntimeException('An existing Drupal installation exists at this location. Try removing all tables or changing the database prefix in your settings.php file.');
+ }
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Command/DbToolsApplication.php b/core/lib/Drupal/Core/Command/DbToolsApplication.php
new file mode 100644
index 00000000000..fbf0b4bb169
--- /dev/null
+++ b/core/lib/Drupal/Core/Command/DbToolsApplication.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Command\DbToolsApplication.
+ */
+
+namespace Drupal\Core\Command;
+
+use Symfony\Component\Console\Application;
+
+/**
+ * Provides a command to import a database generation script.
+ */
+class DbToolsApplication extends Application {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct() {
+ parent::__construct('Database Tools', '8.0.x');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultCommands() {
+ $default_commands = parent::getDefaultCommands();
+ $default_commands[] = new DbDumpCommand();
+ $default_commands[] = new DbImportCommand();
+ return $default_commands;
+ }
+
+}
diff --git a/core/modules/system/src/Tests/Update/DbDumpTest.php b/core/modules/system/src/Tests/Update/DbDumpTest.php
index 9d96fac1ea7..e04bb0502f9 100644
--- a/core/modules/system/src/Tests/Update/DbDumpTest.php
+++ b/core/modules/system/src/Tests/Update/DbDumpTest.php
@@ -151,21 +151,11 @@ class DbDumpTest extends KernelTestBase {
return;
}
- $application = new DbDumpApplication(Database::getConnection(), $this->container->get('module_handler'));
+ $application = new DbDumpApplication();
$command = $application->find('dump-database-d8-mysql');
$command_tester = new CommandTester($command);
$command_tester->execute([]);
- // The enabled modules should be present in the docblock.
- $modules = static::$modules;
- asort($modules);
- $pattern = preg_quote(implode("\n * - ", $modules));
- $this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Module list is contained in the docblock of the script.');
-
- // A module that is not enabled should not be listed.
- $pattern = preg_quote(" * - telephone");
- $this->assertFalse(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Disabled modules do not appear in the docblock of the script.');
-
// Tables that are schema-only should not have data exported.
$pattern = preg_quote("\$connection->insert('sessions')");
$this->assertFalse(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Tables defined as schema-only do not have data exported to the script.');
@@ -195,7 +185,7 @@ class DbDumpTest extends KernelTestBase {
}
// Generate the script.
- $application = new DbDumpApplication(Database::getConnection(), $this->container->get('module_handler'));
+ $application = new DbDumpApplication();
$command = $application->find('dump-database-d8-mysql');
$command_tester = new CommandTester($command);
$command_tester->execute([]);
diff --git a/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php b/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php
new file mode 100644
index 00000000000..c11770d4903
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\system\Kernel\Scripts\DbCommandBaseTest.
+ */
+
+namespace Drupal\Tests\system\Kernel\Scripts;
+
+use Drupal\Core\Command\DbCommandBase;
+use Drupal\Core\Database\Database;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Tester\CommandTester;
+
+/**
+ * Test that the DbToolsApplication works correctly.
+ *
+ * The way console application's run it is impossible to test. For now we only
+ * test that we are registering the correct commands.
+ *
+ * @group console
+ */
+class DbCommandBaseTest extends KernelTestBase {
+
+ /**
+ * Test specifying a database key.
+ */
+ public function testSpecifyDatabaseKey() {
+ $command = new DbCommandBaseTester();
+ $command_tester = new CommandTester($command);
+
+ Database::addConnectionInfo('magic_db', 'default', Database::getConnectionInfo('default')['default']);
+
+ $command_tester->execute([
+ '--database' => 'magic_db'
+ ]);
+ $this->assertEquals('magic_db', $command->getDatabaseConnection($command_tester->getInput())->getKey(),
+ 'Special db key is returned');
+ }
+
+ /**
+ * Invalid database names will throw a useful exception.
+ *
+ * @expectedException \Drupal\Core\Database\ConnectionNotDefinedException
+ */
+ public function testSpecifyDatabaseDoesNotExist() {
+ $command = new DbCommandBaseTester();
+ $command_tester = new CommandTester($command);
+ $command_tester->execute([
+ '--database' => 'dne'
+ ]);
+ $command->getDatabaseConnection($command_tester->getInput());
+ }
+
+ /**
+ * Test supplying database connection as a url.
+ */
+ public function testSpecifyDbUrl() {
+ $connection_info = Database::getConnectionInfo('default')['default'];
+
+ $command = new DbCommandBaseTester();
+ $command_tester = new CommandTester($command);
+ $command_tester->execute([
+ '-db-url' => $connection_info['driver'] . '://' . $connection_info['username'] . ':' . $connection_info['password'] . '@' . $connection_info['host'] . '/' . $connection_info['database']
+ ]);
+ $this->assertEquals('db-tools', $command->getDatabaseConnection($command_tester->getInput())->getKey());
+
+ Database::removeConnection('db-tools');
+ $command_tester->execute([
+ '--database-url' => $connection_info['driver'] . '://' . $connection_info['username'] . ':' . $connection_info['password'] . '@' . $connection_info['host'] . '/' . $connection_info['database']
+ ]);
+ $this->assertEquals('db-tools', $command->getDatabaseConnection($command_tester->getInput())->getKey());
+ }
+
+ /**
+ * Test specifying a prefix for different connections.
+ */
+ public function testPrefix() {
+ if (Database::getConnection()->driver() == 'sqlite') {
+ $this->markTestSkipped('SQLITE modifies the prefixes so we cannot effectively test it');
+ }
+
+ Database::addConnectionInfo('magic_db', 'default', Database::getConnectionInfo('default')['default']);
+ $command = new DbCommandBaseTester();
+ $command_tester = new CommandTester($command);
+ $command_tester->execute([
+ '--database' => 'magic_db',
+ '--prefix' => 'extra',
+ ]);
+ $this->assertEquals('extra', $command->getDatabaseConnection($command_tester->getInput())->tablePrefix());
+
+ $connection_info = Database::getConnectionInfo('default')['default'];
+ $command_tester->execute([
+ '-db-url' => $connection_info['driver'] . '://' . $connection_info['username'] . ':' . $connection_info['password'] . '@' . $connection_info['host'] . '/' . $connection_info['database'],
+ '--prefix' => 'extra2',
+ ]);
+ $this->assertEquals('extra2', $command->getDatabaseConnection($command_tester->getInput())->tablePrefix());
+
+ // This breaks simpletest cleanup.
+// $command_tester->execute([
+// '--prefix' => 'notsimpletest',
+// ]);
+// $this->assertEquals('notsimpletest', $command->getDatabaseConnection($command_tester->getInput())->tablePrefix());
+ }
+
+}
+
+/**
+ * Concrete command implementation for testing base features.
+ */
+class DbCommandBaseTester extends DbCommandBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function configure() {
+ parent::configure();
+ $this->setName('test');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDatabaseConnection(InputInterface $input) {
+ return parent::getDatabaseConnection($input);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ // Empty implementation for testing.
+ }
+
+}
diff --git a/core/modules/system/tests/src/Kernel/Scripts/DbDumpCommandTest.php b/core/modules/system/tests/src/Kernel/Scripts/DbDumpCommandTest.php
new file mode 100644
index 00000000000..3f6c768440b
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/Scripts/DbDumpCommandTest.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\system\Kernel\Scripts\DbDumpCommandTest.
+ */
+
+namespace Drupal\Tests\system\Kernel\Scripts;
+
+use Drupal\Core\Command\DbDumpCommand;
+use Drupal\Core\Database\Database;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\Console\Tester\CommandTester;
+
+/**
+ * Test that the DbDumpCommand works correctly.
+ *
+ * @group console
+ */
+class DbDumpCommandTest extends KernelTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $modules = ['system'];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ parent::setUp();
+
+ // Determine what database backend is running, and set the skip flag.
+ if (Database::getConnection()->databaseType() !== 'mysql') {
+ $this->markTestSkipped("Skipping test since the DbDumpCommand is currently only compatible with MySQL");
+ }
+
+ $this->installSchema('system', 'router');
+
+ /** @var \Drupal\Core\Database\Connection $connection */
+ $connection = $this->container->get('database');
+ $connection->insert('router')->fields(['name', 'path', 'pattern_outline'])->values(['test', 'test', 'test'])->execute();
+ }
+
+ /**
+ * Test the command directly.
+ */
+ public function testDbDumpCommand() {
+ $command = new DbDumpCommand();
+ $command_tester = new CommandTester($command);
+ $command_tester->execute([]);
+
+ // Assert that insert exists and that some expected fields exist.
+ $output = $command_tester->getDisplay();
+ $this->assertContains("createTable('router", $output, 'Table router found');
+ $this->assertContains("insert('router", $output, 'Insert found');
+ $this->assertContains("'name' => 'test", $output, 'Insert name field found');
+ $this->assertContains("'path' => 'test", $output, 'Insert path field found');
+ $this->assertContains("'pattern_outline' => 'test", $output, 'Insert pattern_outline field found');
+ }
+
+ /**
+ * Test schema only option.
+ */
+ public function testSchemaOnly() {
+ $command = new DbDumpCommand();
+ $command_tester = new CommandTester($command);
+ $command_tester->execute(['--schema-only' => 'router']);
+
+ // Assert that insert statement doesn't exist for schema only table.
+ $output = $command_tester->getDisplay();
+ $this->assertContains("createTable('router", $output, 'Table router found');
+ $this->assertNotContains("insert('router", $output, 'Insert not found');
+ $this->assertNotContains("'name' => 'test", $output, 'Insert name field not found');
+ $this->assertNotContains("'path' => 'test", $output, 'Insert path field not found');
+ $this->assertNotContains("'pattern_outline' => 'test", $output, 'Insert pattern_outline field not found');
+
+ // Assert that insert statement doesn't exist for wildcard schema only match.
+ $command_tester->execute(['--schema-only' => 'route.*']);
+ $output = $command_tester->getDisplay();
+ $this->assertContains("createTable('router", $output, 'Table router found');
+ $this->assertNotContains("insert('router", $output, 'Insert not found');
+ $this->assertNotContains("'name' => 'test", $output, 'Insert name field not found');
+ $this->assertNotContains("'path' => 'test", $output, 'Insert path field not found');
+ $this->assertNotContains("'pattern_outline' => 'test", $output, 'Insert pattern_outline field not found');
+ }
+
+}
diff --git a/core/modules/system/tests/src/Kernel/Scripts/DbImportCommandTest.php b/core/modules/system/tests/src/Kernel/Scripts/DbImportCommandTest.php
new file mode 100644
index 00000000000..9dc3407eb2d
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/Scripts/DbImportCommandTest.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\system\Kernel\Scripts\DbImportCommandTest.
+ */
+
+namespace Drupal\Tests\system\Kernel\Scripts;
+
+use Drupal\Core\Command\DbImportCommand;
+use Drupal\Core\Config\DatabaseStorage;
+use Drupal\Core\Database\Database;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\Console\Tester\CommandTester;
+
+/**
+ * Test that the DbImportCommand works correctly.
+ *
+ * @group console
+ */
+class DbImportCommandTest extends KernelTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $modules = ['system', 'config', 'dblog', 'menu_link_content', 'link', 'block_content', 'file', 'user'];
+
+ /**
+ * Tables that should be part of the exported script.
+ *
+ * @var array
+ */
+ protected $tables = [
+ 'block_content',
+ 'block_content_field_data',
+ 'block_content_field_revision',
+ 'block_content_revision',
+ 'cachetags',
+ 'config',
+ 'cache_discovery',
+ 'cache_bootstrap',
+ 'file_managed',
+ 'key_value_expire',
+ 'menu_link_content',
+ 'menu_link_content_data',
+ 'semaphore',
+ 'sessions',
+ 'url_alias',
+ 'user__roles',
+ 'users',
+ 'users_field_data',
+ 'watchdog',
+ ];
+
+ /**
+ * Test the command directly.
+ */
+ public function testDbImportCommand() {
+ /** @var \Drupal\Core\Database\Connection $connection */
+ $connection = $this->container->get('database');
+ // Drop tables to avoid conflicts.
+ foreach ($this->tables as $table) {
+ $connection->schema()->dropTable($table);
+ }
+
+ $command = new DbImportCommand();
+ $command_tester = new CommandTester($command);
+ $command_tester->execute(['script' => __DIR__ . '/../../../fixtures/update/drupal-8.bare.standard.php.gz']);
+
+ // The tables should now exist.
+ foreach ($this->tables as $table) {
+ $this->assertTrue($connection
+ ->schema()
+ ->tableExists($table), strtr('Table @table created by the database script.', ['@table' => $table]));
+ }
+ }
+
+}
diff --git a/core/modules/system/tests/src/Kernel/Scripts/DbToolsApplicationTest.php b/core/modules/system/tests/src/Kernel/Scripts/DbToolsApplicationTest.php
new file mode 100644
index 00000000000..445b30bdca4
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/Scripts/DbToolsApplicationTest.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\system\Kernel\Scripts\DbToolsApplicationTest.
+ */
+
+namespace Drupal\Tests\system\Kernel\Scripts;
+
+use Drupal\Core\Command\DbToolsApplication;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Test that the DbToolsApplication works correctly.
+ *
+ * The way console application's run it is impossible to test. For now we only
+ * test that we are registering the correct commands.
+ *
+ * @group console
+ */
+class DbToolsApplicationTest extends KernelTestBase {
+
+ /**
+ * Test that the dump command is correctly registered.
+ */
+ public function testDumpCommandRegistration() {
+ $application = new DbToolsApplication();
+ $command = $application->find('dump');
+ $this->assertInstanceOf('\Drupal\Core\Command\DbDumpCommand', $command);
+ }
+
+ /**
+ * Test that the dump command is correctly registered.
+ */
+ public function testImportCommandRegistration() {
+ $application = new DbToolsApplication();
+ $command = $application->find('import');
+ $this->assertInstanceOf('\Drupal\Core\Command\DbImportCommand', $command);
+ }
+
+}
diff --git a/core/scripts/db-tools.php b/core/scripts/db-tools.php
new file mode 100644
index 00000000000..316c62fce64
--- /dev/null
+++ b/core/scripts/db-tools.php
@@ -0,0 +1,22 @@
+#!/usr/bin/env php
+<?php
+
+use Drupal\Core\Command\DbToolsApplication;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Site\Settings;
+use Symfony\Component\HttpFoundation\Request;
+
+if (PHP_SAPI !== 'cli') {
+ return;
+}
+
+// Bootstrap.
+$autoloader = require __DIR__ . '/../../autoload.php';
+require_once __DIR__ . '/../includes/bootstrap.inc';
+$request = Request::createFromGlobals();
+Settings::initialize(dirname(dirname(__DIR__)), DrupalKernel::findSitePath($request), $autoloader);
+$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod')->boot();
+
+// Run the database dump command.
+$application = new DbToolsApplication();
+$application->run();
diff --git a/core/scripts/dump-database-d8-mysql.php b/core/scripts/dump-database-d8-mysql.php
index b50f4c01f83..c4f75074cea 100755
--- a/core/scripts/dump-database-d8-mysql.php
+++ b/core/scripts/dump-database-d8-mysql.php
@@ -2,7 +2,6 @@
<?php
use Drupal\Core\Command\DbDumpApplication;
-use Drupal\Core\Database\Database;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\Request;
@@ -19,5 +18,5 @@ Settings::initialize(dirname(dirname(__DIR__)), DrupalKernel::findSitePath($requ
$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod')->boot();
// Run the database dump command.
-$application = new DbDumpApplication(Database::getConnection(), \Drupal::moduleHandler());
+$application = new DbDumpApplication();
$application->run();