Skip to content

Commit

Permalink
Respect dump_binary_path setting when importing database (#40)
Browse files Browse the repository at this point in the history
* Add dumpBinaryPath to DbImporter

* Set and use Custom Binary Path in MySql

* Set and use Custom Binary Path in PostgreSql

* Disable Dependencies Check

* Use DIRECTORY_SEPARATOR when setting Binary Path

* Add Connections with Custom Binary Path

* Add Process Tests
  • Loading branch information
stefanzweifel authored Oct 31, 2023
1 parent 9e17e52 commit 87f0aab
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
touch database/database.sqlite
- name: Execute tests
run: vendor/bin/pest --exclude-group=pgsql --coverage --min=85
run: vendor/bin/pest --exclude-group=pgsql --coverage --min=80
env:
MYSQL_PORT: 3306
MYSQL_USERNAME: root
Expand Down
3 changes: 2 additions & 1 deletion src/Commands/RestoreCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public function handle(

$connection = $this->option('connection') ?? config('backup.backup.source.databases')[0];

$checkDependenciesAction->execute($connection);
// Dependencies-check is currently disabled. Custom binary paths are currently not supported by the Action.
// $checkDependenciesAction->execute($connection);

$pendingRestore = PendingRestore::make(
disk: $this->getDestinationDiskToRestoreFrom(),
Expand Down
23 changes: 23 additions & 0 deletions src/Databases/DbImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

abstract class DbImporter
{
protected string $dumpBinaryPath = '';

abstract public function getImportCommand(string $dumpFile, string $connection): string;

abstract public function getCliName(): string;
Expand All @@ -36,4 +38,25 @@ public function importToDatabase(string $dumpFile, string $connection): void

$this->checkIfImportWasSuccessful($process, $dumpFile);
}

public function setDumpBinaryPath(string $dumpBinaryPath): self
{
if ($dumpBinaryPath !== '' && ! str_ends_with($dumpBinaryPath, DIRECTORY_SEPARATOR)) {
$dumpBinaryPath .= DIRECTORY_SEPARATOR;
}

$this->dumpBinaryPath = $dumpBinaryPath;

return $this;
}

protected function determineQuote(): string
{
return $this->isWindows() ? '"' : "'";
}

protected function isWindows(): bool
{
return str_starts_with(strtoupper(PHP_OS), 'WIN');
}
}
16 changes: 12 additions & 4 deletions src/Databases/MySql.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public function getImportCommand(string $dumpFile, string $connection): string
->create()
->empty();

if (config("database.connections.{$connection}.dump.dump_binary_path")) {
$this->setDumpBinaryPath(config("database.connections.{$connection}.dump.dump_binary_path"));
}

$dumper = DbDumperFactory::createFromConnection($connection);
$importToDatabase = $dumper->getDbName();

Expand All @@ -45,20 +49,24 @@ public function getCliName(): string

private function getMySqlImportCommandForCompressedDump(string $storagePathToDatabaseFile, mixed $temporaryCredentialsFile, string $importToDatabase): string
{
$quote = $this->determineQuote();

return collect([
"gunzip < {$storagePathToDatabaseFile}",
'|',
'mysql',
"--defaults-extra-file=\"{$temporaryCredentialsFile}\"",
"{$quote}{$this->dumpBinaryPath}mysql{$quote}",
"--defaults-extra-file={$quote}{$temporaryCredentialsFile}{$quote}",
$importToDatabase,
])->implode(' ');
}

private function getMySqlImportCommandForUncompressedDump(mixed $temporaryCredentialsFile, string $importToDatabase, string $storagePathToDatabaseFile): string
{
$quote = $this->determineQuote();

return collect([
'mysql',
"--defaults-extra-file=\"{$temporaryCredentialsFile}\"",
"{$quote}{$this->dumpBinaryPath}mysql{$quote}",
"--defaults-extra-file={$quote}{$temporaryCredentialsFile}{$quote}",
$importToDatabase,
'<',
$storagePathToDatabaseFile,
Expand Down
19 changes: 17 additions & 2 deletions src/Databases/PostgreSql.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,31 @@ class PostgreSql extends DbImporter
*/
public function getImportCommand(string $dumpFile, string $connection): string
{
if (config("database.connections.{$connection}.dump.dump_binary_path")) {
$this->setDumpBinaryPath(config("database.connections.{$connection}.dump.dump_binary_path"));
}

/** @var \Spatie\DbDumper\Databases\PostgreSql $dumper */
$dumper = DbDumperFactory::createFromConnection($connection);
$dumper->getContentsOfCredentialsFile();

// @todo: Improve detection of compressed files
if (str($dumpFile)->endsWith('gz')) {
return 'gunzip -c '.$dumpFile.' | psql -U '.config("database.connections.{$connection}.username").' -d '.config("database.connections.{$connection}.database");
return collect([
'gunzip -c '.$dumpFile,
'|',
$this->dumpBinaryPath.'psql',
'-U '.config("database.connections.{$connection}.username"),
'-d '.config("database.connections.{$connection}.database"),
])->implode(' ');
}

return 'psql -U '.config("database.connections.{$connection}.username").' -d '.config("database.connections.{$connection}.database").' < '.$dumpFile;
return collect([
$this->dumpBinaryPath.'psql',
'-U '.config("database.connections.{$connection}.username"),
'-d '.config("database.connections.{$connection}.database"),
'< '.$dumpFile,
])->implode(' ');
}

public function getCliName(): string
Expand Down
71 changes: 71 additions & 0 deletions tests/Databases/MySqlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

declare(strict_types=1);

use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Process;
use Wnx\LaravelBackupRestore\Databases\MySql;
use Wnx\LaravelBackupRestore\Events\DatabaseDumpImportWasSuccessful;
use Wnx\LaravelBackupRestore\Exceptions\ImportFailed;

use function PHPUnit\Framework\assertStringContainsString;

it('imports mysql dump', function (string $dumpFile) {
Event::fake();

Expand All @@ -24,6 +28,73 @@
__DIR__.'/../storage/Laravel/2023-01-28-mysql-compression-no-encryption.sql.gz',
]);

it('uses default binary path to import mysql dump', function () {
Event::fake();
Process::fake();

$dumpFile = __DIR__.'/../storage/Laravel/2023-01-28-mysql-no-compression-no-encryption.sql';

app(MySql::class)->importToDatabase(
dumpFile: $dumpFile,
connection: 'mysql'
);

Process::assertRan(function (PendingProcess $process) {
assertStringContainsString("'mysql'", $process->command);

return true;
});

Event::assertDispatched(function (DatabaseDumpImportWasSuccessful $event) use ($dumpFile) {
return $event->absolutePathToDump === $dumpFile;
});
});

it('uses custom binary path to import mysql dump', function () {
Event::fake();
Process::fake();

$dumpFile = __DIR__.'/../storage/Laravel/2023-01-28-mysql-no-compression-no-encryption.sql';

app(MySql::class)->importToDatabase(
dumpFile: $dumpFile,
connection: 'mysql-restore-binary-path'
);

Process::assertRan(function (PendingProcess $process) {
assertStringContainsString('/usr/bin/mysql', $process->command);

return true;
});

Event::assertDispatched(function (DatabaseDumpImportWasSuccessful $event) use ($dumpFile) {
return $event->absolutePathToDump === $dumpFile;
});
});

it('uses custom binary path to import compressed mysql dump', function () {
Event::fake();
Process::fake();

$dumpFile = __DIR__.'/../storage/Laravel/2023-01-28-mysql-compression-no-encryption.sql.gz';

app(MySql::class)->importToDatabase(
dumpFile: $dumpFile,
connection: 'mysql-restore-binary-path'
);

Process::assertRan(function (PendingProcess $process) {
assertStringContainsString('gunzip <', $process->command);
assertStringContainsString('/usr/bin/mysql', $process->command);

return true;
});

Event::assertDispatched(function (DatabaseDumpImportWasSuccessful $event) use ($dumpFile) {
return $event->absolutePathToDump === $dumpFile;
});
});

it('throws import failed exception if mysql dump could not be imported')
->tap(fn () => app(MySql::class)->importToDatabase('file-does-not-exist', 'mysql'))
->throws(ImportFailed::class);
70 changes: 70 additions & 0 deletions tests/Databases/PostgreSqlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

declare(strict_types=1);

use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Process;
use Wnx\LaravelBackupRestore\Databases\PostgreSql;
use Wnx\LaravelBackupRestore\Events\DatabaseDumpImportWasSuccessful;
use Wnx\LaravelBackupRestore\Exceptions\ImportFailed;

use function PHPUnit\Framework\assertStringContainsString;
use function PHPUnit\Framework\assertStringNotContainsString;

it('imports pgsql dump', function (string $dumpFile) {
Event::fake();

Expand All @@ -24,6 +29,71 @@
__DIR__.'/../storage/Laravel/2023-03-04-pgsql-compression-no-encryption.sql.gz',
])->group('pgsql');

it('uses default binary to import pgsql dump', function () {
Event::fake();
Process::fake();

$dumpFile = __DIR__.'/../storage/Laravel/2023-03-04-pgsql-no-compression-no-encryption.sql';

app(PostgreSql::class)->importToDatabase($dumpFile, 'pgsql');

Process::assertRan(function (PendingProcess $process) {
assertStringNotContainsString('/usr/bin/psql', $process->command);
assertStringContainsString('psql', $process->command);

return true;
});
Event::assertDispatched(function (DatabaseDumpImportWasSuccessful $event) use ($dumpFile) {
return $event->absolutePathToDump === $dumpFile;
});

$result = DB::connection('pgsql')->table('users')->count();
expect($result)->toBe(10);
})->group('pgsql');

it('uses custom binary to import pgsql dump', function () {
Event::fake();
Process::fake();

$dumpFile = __DIR__.'/../storage/Laravel/2023-03-04-pgsql-no-compression-no-encryption.sql';

app(PostgreSql::class)->importToDatabase($dumpFile, 'pgsql-restore-binary-path');

Process::assertRan(function (PendingProcess $process) {
assertStringContainsString('/usr/bin/psql', $process->command);

return true;
});
Event::assertDispatched(function (DatabaseDumpImportWasSuccessful $event) use ($dumpFile) {
return $event->absolutePathToDump === $dumpFile;
});

$result = DB::connection('pgsql')->table('users')->count();
expect($result)->toBe(10);
})->group('pgsql');

it('uses custom binary to import compressed pgsql dump', function () {
Event::fake();
Process::fake();

$dumpFile = __DIR__.'/../storage/Laravel/2023-03-04-pgsql-compression-no-encryption.sql.gz';

app(PostgreSql::class)->importToDatabase($dumpFile, 'pgsql-restore-binary-path');

Process::assertRan(function (PendingProcess $process) {
assertStringContainsString('gunzip -c', $process->command);
assertStringContainsString('/usr/bin/psql', $process->command);

return true;
});
Event::assertDispatched(function (DatabaseDumpImportWasSuccessful $event) use ($dumpFile) {
return $event->absolutePathToDump === $dumpFile;
});

$result = DB::connection('pgsql')->table('users')->count();
expect($result)->toBe(10);
})->group('pgsql');

it('throws import failed exception if pgsql dump could not be imported')
->tap(fn () => app(PostgreSql::class)->importToDatabase('file-does-not-exist', 'pgsql'))
->throws(ImportFailed::class)
Expand Down
24 changes: 24 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ protected function defineEnvironment($app)
'password' => env('MYSQL_PASSWORD', ''),
]);

$app['config']->set('database.connections.mysql-restore-binary-path', [
'driver' => 'mysql',
'host' => env('MYSQL_HOST', '127.0.0.1'),
'port' => env('MYSQL_PORT', '3306'),
'database' => env('MYSQL_DATABASE', 'laravel_backup_restore'),
'username' => env('MYSQL_USERNAME', 'root'),
'password' => env('MYSQL_PASSWORD', ''),
'dump' => [
'dump_binary_path' => env('MYSQL_BINARY_PATH', '/usr/bin/'),
],
]);

$app['config']->set('database.connections.pgsql', [
'driver' => 'pgsql',
'host' => env('PGSQL_HOST', '127.0.0.1'),
Expand All @@ -65,6 +77,18 @@ protected function defineEnvironment($app)
'password' => env('PGSQL_PASSWORD', ''),
'search_path' => 'public',
]);
$app['config']->set('database.connections.pgsql-restore-binary-path', [
'driver' => 'pgsql',
'host' => env('PGSQL_HOST', '127.0.0.1'),
'port' => env('PGSQL_PORT', '5432'),
'database' => env('PGSQL_DATABASE', 'laravel_backup_restore'),
'username' => env('PGSQL_USERNAME', 'root'),
'password' => env('PGSQL_PASSWORD', ''),
'search_path' => 'public',
'dump' => [
'dump_binary_path' => env('PGSQL_BINARY_PATH', '/usr/bin/'),
],
]);

$app['config']->set('database.connections.unsupported-driver', [
'driver' => 'sqlsrv',
Expand Down

0 comments on commit 87f0aab

Please sign in to comment.