Skip to content

Commit

Permalink
CLI-1210: Drop tables instead of database (#1638)
Browse files Browse the repository at this point in the history
* CLI-1210: Drop tables instead of database

* fix tests
  • Loading branch information
danepowell authored Nov 28, 2023
1 parent 928c361 commit 218c676
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 72 deletions.
78 changes: 48 additions & 30 deletions src/Command/Pull/PullCommandBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,36 @@ abstract class PullCommandBase extends CommandBase {

private UriInterface $backupDownloadUrl;

/**
* @see https://github.com/drush-ops/drush/blob/c21a5a24a295cc0513bfdecead6f87f1a2cf91a2/src/Sql/SqlMysql.php#L168
* @return string[]
*/
private function listTables(string $out): array {
$tables = [];
if ($out = trim($out)) {
$tables = explode(PHP_EOL, $out);
}
return $tables;
}

/**
* @see https://github.com/drush-ops/drush/blob/c21a5a24a295cc0513bfdecead6f87f1a2cf91a2/src/Sql/SqlMysql.php#L178
* @return string[]
*/
private function listTablesQuoted(string $out): array {
$tables = $this->listTables($out);
foreach ($tables as &$table) {
$table = "`$table`";
}
return $tables;
}

protected function getCloudFilesDir(EnvironmentResponse $chosenEnvironment, string $site): string {
$sitegroup = self::getSiteGroupFromSshUrl($chosenEnvironment->sshUrl);
if ($this->isAcsfEnv($chosenEnvironment)) {
return '/mnt/files/' . $sitegroup . '.' . $chosenEnvironment->name . '/sites/g/files/' . $site . '/files';
}
else {
return $this->getCloudSitesPath($chosenEnvironment, $sitegroup) . "/$site/files";
}
return $this->getCloudSitesPath($chosenEnvironment, $sitegroup) . "/$site/files";
}

protected function getLocalFilesDir(string $site): string {
Expand Down Expand Up @@ -189,8 +211,7 @@ private function doImportRemoteDatabase(
string $localFilepath,
Closure $outputCallback = NULL
): void {
$this->dropLocalDatabase($databaseHost, $databaseUser, $databaseName, $databasePassword, $outputCallback);
$this->createLocalDatabase($databaseHost, $databaseUser, $databaseName, $databasePassword, $outputCallback);
$this->dropDbTables($databaseHost, $databaseUser, $databaseName, $databasePassword, $outputCallback);
$this->importDatabaseDump($localFilepath, $databaseHost, $databaseUser, $databaseName, $databasePassword, $outputCallback);
$this->localMachineHelper->getFilesystem()->remove($localFilepath);
}
Expand Down Expand Up @@ -341,29 +362,9 @@ private function connectToLocalDatabase(string $dbHost, string $dbUser, string $
}
}

private function dropLocalDatabase(string $dbHost, string $dbUser, string $dbName, string $dbPassword, ?\Closure $outputCallback = NULL): void {
if ($outputCallback) {
$outputCallback('out', "Dropping database $dbName");
}
$this->localMachineHelper->checkRequiredBinariesExist(['mysql']);
$command = [
'mysql',
'--host',
$dbHost,
'--user',
$dbUser,
'-e',
'DROP DATABASE IF EXISTS ' . $dbName,
];
$process = $this->localMachineHelper->execute($command, $outputCallback, NULL, FALSE, NULL, ['MYSQL_PWD' => $dbPassword]);
if (!$process->isSuccessful()) {
throw new AcquiaCliException('Unable to drop a local database. {message}', ['message' => $process->getErrorOutput()]);
}
}

private function createLocalDatabase(string $dbHost, string $dbUser, string $dbName, string $dbPassword, ?\Closure $outputCallback = NULL): void {
private function dropDbTables(string $dbHost, string $dbUser, string $dbName, string $dbPassword, ?\Closure $outputCallback = NULL): void {
if ($outputCallback) {
$outputCallback('out', "Creating new empty database $dbName");
$outputCallback('out', "Dropping tables from database $dbName");
}
$this->localMachineHelper->checkRequiredBinariesExist(['mysql']);
$command = [
Expand All @@ -372,12 +373,29 @@ private function createLocalDatabase(string $dbHost, string $dbUser, string $dbN
$dbHost,
'--user',
$dbUser,
$dbName,
'--silent',
'-e',
'create database ' . $dbName,
'SHOW TABLES;',
];
$process = $this->localMachineHelper->execute($command, $outputCallback, NULL, FALSE, NULL, ['MYSQL_PWD' => $dbPassword]);
if (!$process->isSuccessful()) {
throw new AcquiaCliException('Unable to create a local database. {message}', ['message' => $process->getErrorOutput()]);
$tables = $this->listTablesQuoted($process->getOutput());
if ($tables) {
$sql = 'DROP TABLE ' . implode(', ', $tables);
$command = [
'mysql',
'--host',
$dbHost,
'--user',
$dbUser,
$dbName,
'-e',
$sql,
];
$process = $this->localMachineHelper->execute($command, $outputCallback, NULL, FALSE, NULL, ['MYSQL_PWD' => $dbPassword]);
if (!$process->isSuccessful()) {
throw new AcquiaCliException('Unable to drop tables from database. {message}', ['message' => $process->getErrorOutput()]);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/phpunit/src/CommandTestBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ protected function mockNotificationResponseFromObject(object $responseWithNotifi

protected function mockCreateMySqlDumpOnLocal(ObjectProphecy $localMachineHelper): void {
$localMachineHelper->checkRequiredBinariesExist(["mysqldump", "gzip"])->shouldBeCalled();
$process = $this->mockProcess(TRUE);
$process = $this->mockProcess();
$process->getOutput()->willReturn('');
$command = 'MYSQL_PWD=drupal mysqldump --host=localhost --user=drupal drupal | pv --rate --bytes | gzip -9 > ' . sys_get_temp_dir() . '/acli-mysql-dump-drupal.sql.gz';
$localMachineHelper->executeFromCmd($command, Argument::type('callable'), NULL, TRUE)->willReturn($process->reveal())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ protected function createCommand(): Command {
return $this->injectCommand(ArchiveExportCommand::class);
}

public function setUp(): void {
self::unsetEnvVars(['ACLI_DB_HOST', 'ACLI_DB_USER', 'ACLI_DB_PASSWORD', 'ACLI_DB_NAME']);
parent::setUp();
}

public function testArchiveExport(): void {
touch(Path::join($this->projectDir, '.gitignore'));
$destinationDir = 'foo';
Expand Down
83 changes: 42 additions & 41 deletions tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Acquia\Cli\Command\Pull\PullCommandBase;
use Acquia\Cli\Command\Pull\PullDatabaseCommand;
use Acquia\Cli\Exception\AcquiaCliException;
use Acquia\Cli\Helpers\LocalMachineHelper;
use AcquiaCloudApi\Response\DatabaseResponse;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Uri;
Expand All @@ -29,6 +30,11 @@ class PullDatabaseCommandTest extends PullCommandTestBase {
protected string $dbHost = 'localhost';
protected string $dbName = 'drupal';

public function setUp(): void {
self::unsetEnvVars(['ACLI_DB_HOST', 'ACLI_DB_USER', 'ACLI_DB_PASSWORD', 'ACLI_DB_NAME']);
parent::setUp();
}

/**
* @return int[][]
*/
Expand All @@ -41,7 +47,7 @@ protected function createCommand(): Command {
}

public function testPullDatabases(): void {
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE);
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE);
$inputs = $this->getInputs();

$this->executeCommand([
Expand All @@ -60,7 +66,7 @@ public function testPullDatabases(): void {
}

public function testPullDatabasesLocalConnectionFailure(): void {
$this->setupPullDatabase(FALSE, TRUE, TRUE, TRUE, TRUE);
$this->setupPullDatabase(FALSE, TRUE, TRUE, TRUE);
$inputs = $this->getInputs();

$this->expectException(AcquiaCliException::class);
Expand All @@ -71,7 +77,7 @@ public function testPullDatabasesLocalConnectionFailure(): void {
}

public function testPullDatabaseNoPv(): void {
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, 0, TRUE, FALSE);
$this->setupPullDatabase(TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, 0, TRUE, FALSE);
$inputs = $this->getInputs();

$this->executeCommand(['--no-scripts' => TRUE], $inputs);
Expand All @@ -80,7 +86,7 @@ public function testPullDatabaseNoPv(): void {
}

public function testPullMultipleDatabases(): void {
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE);
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE);
$inputs = [
// Would you like Acquia CLI to search for a Cloud application that matches your local git config?
'n',
Expand All @@ -103,7 +109,7 @@ public function testPullMultipleDatabases(): void {
}

public function testPullDatabasesOnDemand(): void {
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE);
$inputs = $this->getInputs();

$this->executeCommand([
Expand All @@ -122,7 +128,7 @@ public function testPullDatabasesOnDemand(): void {
}

public function testPullDatabasesNoExistingBackup(): void {
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, 0, FALSE);
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, 0, FALSE);
$inputs = $this->getInputs();

$this->executeCommand([
Expand All @@ -141,7 +147,7 @@ public function testPullDatabasesNoExistingBackup(): void {
}

public function testPullDatabasesSiteArgument(): void {
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE);
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, FALSE, FALSE);
$inputs = $this->getInputs();

$this->executeCommand([
Expand All @@ -159,24 +165,15 @@ public function testPullDatabasesSiteArgument(): void {
}

public function testPullDatabaseWithMySqlDropError(): void {
$this->setupPullDatabase(TRUE, FALSE, TRUE, TRUE);
$this->setupPullDatabase(TRUE, FALSE, TRUE);
$inputs = $this->getInputs();
$this->expectException(AcquiaCliException::class);
$this->expectExceptionMessage('Unable to drop a local database');
$this->executeCommand(['--no-scripts' => TRUE], $inputs);
}

public function testPullDatabaseWithMySqlCreateError(): void {
$this->setupPullDatabase(TRUE, TRUE, FALSE, TRUE);
$inputs = $this->getInputs();

$this->expectException(AcquiaCliException::class);
$this->expectExceptionMessage('Unable to create a local database');
$this->expectExceptionMessage('Unable to drop tables from database');
$this->executeCommand(['--no-scripts' => TRUE], $inputs);
}

public function testPullDatabaseWithMySqlImportError(): void {
$this->setupPullDatabase(TRUE, TRUE, TRUE, FALSE);
$this->setupPullDatabase(TRUE, TRUE, FALSE);
$inputs = $this->getInputs();

$this->expectException(AcquiaCliException::class);
Expand All @@ -188,7 +185,7 @@ public function testPullDatabaseWithMySqlImportError(): void {
* @dataProvider providerTestPullDatabaseWithInvalidSslCertificate
*/
public function testPullDatabaseWithInvalidSslCertificate(int $errorCode): void {
$this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, $errorCode);
$this->setupPullDatabase(TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, $errorCode);
$inputs = $this->getInputs();

$this->executeCommand(['--no-scripts' => TRUE], $inputs);
Expand All @@ -197,7 +194,7 @@ public function testPullDatabaseWithInvalidSslCertificate(int $errorCode): void
$this->assertStringContainsString('Trying alternative host other.example.com', $output);
}

protected function setupPullDatabase(bool $mysqlConnectSuccessful, bool $mysqlDropSuccessful, bool $mysqlCreateSuccessful, bool $mysqlImportSuccessful, bool $mockIdeFs = FALSE, bool $onDemand = FALSE, bool $mockGetAcsfSites = TRUE, bool $multidb = FALSE, int $curlCode = 0, bool $existingBackups = TRUE, bool $pvExists = TRUE): void {
protected function setupPullDatabase(bool $mysqlConnectSuccessful, bool $mysqlDropSuccessful, bool $mysqlImportSuccessful, bool $mockIdeFs = FALSE, bool $onDemand = FALSE, bool $mockGetAcsfSites = TRUE, bool $multiDb = FALSE, int $curlCode = 0, bool $existingBackups = TRUE, bool $pvExists = TRUE): void {
$applicationsResponse = $this->mockApplicationsRequest();
$this->mockApplicationRequest();
$environmentsResponse = $this->mockAcsfEnvironmentsRequest($applicationsResponse);
Expand All @@ -209,7 +206,7 @@ protected function setupPullDatabase(bool $mysqlConnectSuccessful, bool $mysqlDr
$databaseBackupsResponse = $this->mockDatabaseBackupsResponse($selectedEnvironment, $databaseResponse->name, 1, $existingBackups);
$selectedDatabase = $this->mockDownloadBackup($databaseResponse, $selectedEnvironment, $databaseBackupsResponse->_embedded->items[0], $curlCode);

if ($multidb) {
if ($multiDb) {
$databaseResponse2 = $databasesResponse[array_search('profserv2', array_column($databasesResponse, 'name'), TRUE)];
$databaseBackupsResponse2 = $this->mockDatabaseBackupsResponse($selectedEnvironment, $databaseResponse2->name, 1, $existingBackups);
$this->mockDownloadBackup($databaseResponse2, $selectedEnvironment, $databaseBackupsResponse2->_embedded->items[0], $curlCode);
Expand Down Expand Up @@ -239,10 +236,11 @@ protected function setupPullDatabase(bool $mysqlConnectSuccessful, bool $mysqlDr
}

// Database.
$this->mockExecuteMySqlListTables($localMachineHelper);
$this->mockExecuteMySqlDropDb($localMachineHelper, $mysqlDropSuccessful);
$this->mockExecuteMySqlCreateDb($localMachineHelper, $mysqlCreateSuccessful);
$this->mockExecuteMySqlImport($localMachineHelper, $mysqlImportSuccessful, $pvExists);
if ($multidb) {
if ($multiDb) {
$this->mockExecuteMySqlListTables($localMachineHelper, 'drupal');
$this->mockExecuteMySqlImport($localMachineHelper, $mysqlImportSuccessful, $pvExists, 'profserv2', 'profserv2dev', 'drupal');
}
$this->command->localMachineHelper = $localMachineHelper->reveal();
Expand All @@ -268,35 +266,38 @@ protected function mockExecuteMySqlConnect(
->shouldBeCalled();
}

protected function mockExecuteMySqlDropDb(
\Acquia\Cli\Helpers\LocalMachineHelper|ObjectProphecy $localMachineHelper,
bool $success
protected function mockExecuteMySqlListTables(
LocalMachineHelper|ObjectProphecy $localMachineHelper,
string $dbName = 'jxr5000596dev',
): void {
$localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled();
$process = $this->mockProcess($success);
$process = $this->mockProcess();
$process->getOutput()->willReturn('table1');
$command = [
'mysql',
'--host',
'localhost',
'--user',
'drupal',
$dbName,
'--silent',
'-e',
'SHOW TABLES;',
];
$localMachineHelper
->execute(Argument::type('array'), Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword])
->execute($command, Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword])
->willReturn($process->reveal())
->shouldBeCalled();
}

protected function mockExecuteMySqlCreateDb(
ObjectProphecy $localMachineHelper,
protected function mockExecuteMySqlDropDb(
LocalMachineHelper|ObjectProphecy $localMachineHelper,
bool $success
): void {
$localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled();
$process = $this->mockProcess($success);
$localMachineHelper
->execute([
'mysql',
'--host',
$this->dbHost,
'--user',
$this->dbUser,
'-e',
//'create database drupal',
'create database jxr5000596dev',
], Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => 'drupal'])
->execute(Argument::type('array'), Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword])
->willReturn($process->reveal())
->shouldBeCalled();
}
Expand Down
5 changes: 5 additions & 0 deletions tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ protected function createCommand(): Command {
return $this->injectCommand(PushDatabaseCommand::class);
}

public function setUp(): void {
self::unsetEnvVars(['ACLI_DB_HOST', 'ACLI_DB_USER', 'ACLI_DB_PASSWORD', 'ACLI_DB_NAME']);
parent::setUp();
}

public function testPushDatabase(): void {
$applicationsResponse = $this->mockApplicationsRequest();
$this->mockApplicationRequest();
Expand Down

0 comments on commit 218c676

Please sign in to comment.