From 3ee2fc3dabd06c2a16342568acd6e6d394e10a07 Mon Sep 17 00:00:00 2001 From: James King Date: Fri, 1 Jan 2016 23:19:38 +0000 Subject: [PATCH] PostgreSQL support --- .../Database/DatabaseBackupHandler.php | 4 +- .../Database/DatabaseBuilder.php | 41 ++++++++- .../Database/Databases/PgSQLDatabase.php | 91 +++++++++++++++++++ src/Console.php | 9 +- src/config/laravel-backup.php | 19 ++++ tests/PgSQLDatabaseTest.php | 60 ++++++++++++ 6 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 src/BackupHandlers/Database/Databases/PgSQLDatabase.php create mode 100644 tests/PgSQLDatabaseTest.php diff --git a/src/BackupHandlers/Database/DatabaseBackupHandler.php b/src/BackupHandlers/Database/DatabaseBackupHandler.php index 48932950..c0f55d63 100644 --- a/src/BackupHandlers/Database/DatabaseBackupHandler.php +++ b/src/BackupHandlers/Database/DatabaseBackupHandler.php @@ -29,8 +29,8 @@ public function getDatabase($connectionName = '') $dbDriver = config("database.connections.{$connectionName}.driver"); - if ($dbDriver != 'mysql') { - throw new Exception('laravel-backup can only backup mysql databases'); + if ($dbDriver != 'mysql' && $dbDriver != 'pgsql') { + throw new Exception('laravel-backup can only backup mysql / pgsql databases'); } return $this->databaseBuilder->getDatabase(config("database.connections.{$connectionName}")); diff --git a/src/BackupHandlers/Database/DatabaseBuilder.php b/src/BackupHandlers/Database/DatabaseBuilder.php index b0f9fb05..c2fd85ef 100644 --- a/src/BackupHandlers/Database/DatabaseBuilder.php +++ b/src/BackupHandlers/Database/DatabaseBuilder.php @@ -17,10 +17,22 @@ public function __construct() public function getDatabase(array $realConfig) { - try { - $this->buildMySQL($realConfig); - } catch (Exception $e) { - throw new \Exception('Whoops, '.$e->getMessage()); + switch($realConfig['driver']) { + case 'mysql': + try { + $this->buildMySQL($realConfig); + } catch (Exception $e) { + throw new \Exception('Whoops, '.$e->getMessage()); + } + break; + + case 'pgsql': + try { + $this->buildPgSql($realConfig); + } catch (Exception $e) { + throw new \Exception('Whoops, '.$e->getMessage()); + } + break; } return $this->database; @@ -43,6 +55,27 @@ protected function buildMySQL(array $config) ); } + /** + * Build a PgSQLDatabase instance. + * @param array $config + */ + protected function buildPgSql(array $config) + { + $port = isset($config['port']) ? $config['port'] : 5432; + + $schema = isset($config['schema']) ? $config['schema'] : 'public'; + + $this->database = new Databases\PgSQLDatabase( + $this->console, + $config['database'], + $schema, + $config['username'], + $config['password'], + $this->determineHost($config), + $port + ); + } + /** * Determine the host from the given config. * diff --git a/src/BackupHandlers/Database/Databases/PgSQLDatabase.php b/src/BackupHandlers/Database/Databases/PgSQLDatabase.php new file mode 100644 index 00000000..5f41f833 --- /dev/null +++ b/src/BackupHandlers/Database/Databases/PgSQLDatabase.php @@ -0,0 +1,91 @@ +console = $console; + $this->database = $database; + $this->schema = $schema; + $this->username = $username; + $this->password = $password; + $this->host = $host; + $this->port = $port; + } + + /** + * Create a database dump. + * + * @param $destinationFile + * + * @return bool + */ + public function dump($destinationFile) + { + $command = sprintf('export PGHOST && %spg_dump '.(!$this->useCopy() ? '--inserts' : '').' --schema=%s %s > %s', + $this->getDumpCommandPath(), + escapeshellarg($this->schema), + escapeshellarg($this->database), + escapeshellarg($destinationFile) + ); + + $env = [ + 'PGHOST' => $this->host, + 'PGUSER' => $this->username, + 'PGPASSWORD' => $this->password, + 'PGPORT' => $this->port + ]; + + return $this->console->run($command, config('laravel-backup.pgsql.timeoutInSeconds'), $env); + } + + /** + * Return the file extension of a dump file (sql, ...). + * + * @return string + */ + public function getFileExtension() + { + return 'sql'; + } + + /** + * Get the path to the pgsql_dump. + * + * @return string + */ + protected function getDumpCommandPath() + { + return config('laravel-backup.pgsql.dump_command_path'); + } + + /** + * Determine if COPY should be used instead of INSERT. + */ + protected function useCopy() + { + return config('laravel-backup.pgsql.use_copy'); + } +} \ No newline at end of file diff --git a/src/Console.php b/src/Console.php index 7a807b4e..4a5f4d69 100644 --- a/src/Console.php +++ b/src/Console.php @@ -10,16 +10,21 @@ class Console * Run a command in the shell. * * @param $command - * @param $timeoutInSeconds + * @param int $timeoutInSeconds + * @param array $env * * @return bool|string */ - public function run($command, $timeoutInSeconds = 60) + public function run($command, $timeoutInSeconds = 60, array $env = null) { $process = new Process($command); $process->setTimeout($timeoutInSeconds); + if ($env != null) { + $process->setEnv($env); + } + $process->run(); if ($process->isSuccessful()) { diff --git a/src/config/laravel-backup.php b/src/config/laravel-backup.php index c32b6b32..765b4dd3 100644 --- a/src/config/laravel-backup.php +++ b/src/config/laravel-backup.php @@ -87,4 +87,23 @@ */ 'timeoutInSeconds' => 60, ], + + 'pgsql' => [ + /* + * The path to the pg_dump binary. You can leave this empty + * if the binary is installed in the default location. + */ + 'dump_command_path' => '', + + /* + * Set to true to use pgsql 'COPY' statements instead of 'INSERT's. + */ + 'use_copy' => true, + + /* + * If the dump of the db takes more seconds that the specified value, + * it will abort the backup. + */ + 'timeoutInSeconds' => 60, + ] ]; diff --git a/tests/PgSQLDatabaseTest.php b/tests/PgSQLDatabaseTest.php new file mode 100644 index 00000000..cf8e6442 --- /dev/null +++ b/tests/PgSQLDatabaseTest.php @@ -0,0 +1,60 @@ +console = m::mock('Spatie\Backup\Console'); + + $this->database = new PgSQLDatabase( + $this->console, + 'testDatabase', + 'public', + 'testUser', + 'password', + 'localhost', + '5432' + ); + } + + public function tearDown() + { + m::close(); + } + + public function testFileExtension() + { + $this->assertEquals( + 'sql', $this->database->getFileExtension() + ); + } + + public function testDump() + { + $this->console->shouldReceive('run') + ->with(m::on(function ($parameter) { + $pattern = "/pg_dump --inserts --schema='public' 'testDatabase' > 'testfile.sql'/"; + + return preg_match($pattern, $parameter) == true; + }), null, [ + 'PGHOST' => 'localhost', + 'PGUSER' => 'testUser', + 'PGPASSWORD' => 'password', + 'PGPORT' => '5432' + ]) + ->once() + ->andReturn(true); + + $this->assertTrue( + $this->database->dump('testfile.sql') + ); + } +} \ No newline at end of file