-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #48 from swiftotter/46-mysqldump-per-table
#46: Split local export mysqldump command to multiple commands (one per table)
- Loading branch information
Showing
4 changed files
with
250 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Driver\Engines\MySql\Export; | ||
|
||
use Driver\Engines\ConnectionInterface; | ||
use Driver\Pipeline\Environment\EnvironmentInterface; | ||
|
||
use function implode; | ||
use function in_array; | ||
|
||
class CommandAssembler | ||
{ | ||
private TablesProvider $tablesProvider; | ||
|
||
public function __construct(TablesProvider $tablesProvider) | ||
{ | ||
$this->tablesProvider = $tablesProvider; | ||
} | ||
|
||
public function execute( | ||
ConnectionInterface $connection, | ||
EnvironmentInterface $environment, | ||
string $dumpFile | ||
): string { | ||
$commands = []; | ||
$ignoredTables = $this->tablesProvider->getIgnoredTables($environment); | ||
$emptyTables = $this->tablesProvider->getEmptyTables($environment); | ||
foreach ($this->tablesProvider->getAllTables($connection) as $table) { | ||
if (in_array($table, $ignoredTables) || in_array($table, $emptyTables)) { | ||
continue; | ||
} | ||
$commands[] = $this->getSingleCommand($connection, [$table], $dumpFile); | ||
} | ||
if (!empty($emptyTables)) { | ||
$commands[] = $this->getSingleCommand($connection, $emptyTables, $dumpFile, false); | ||
} | ||
if (empty($commands)) { | ||
return ''; | ||
} | ||
$commands[] = "cat $dumpFile | " | ||
. "sed -E 's/DEFINER[ ]*=[ ]*`[^`]+`@`[^`]+`/DEFINER=CURRENT_USER/g' | gzip > $dumpFile.gz"; | ||
return implode(';', $commands); | ||
} | ||
|
||
/** | ||
* @param string[] $tables | ||
*/ | ||
private function getSingleCommand( | ||
ConnectionInterface $connection, | ||
array $tables, | ||
string $dumpFile, | ||
bool $withData = true | ||
): string { | ||
$parts = [ | ||
"mysqldump --user=\"{$connection->getUser()}\"", | ||
"--password=\"{$connection->getPassword()}\"", | ||
"--single-transaction", | ||
"--host={$connection->getHost()}", | ||
$connection->getDatabase(), | ||
implode(' ', $tables) | ||
]; | ||
if (!$withData) { | ||
$parts[] = '--no-data'; | ||
} | ||
$parts[] = ">> $dumpFile"; | ||
return implode(' ', $parts); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Driver\Engines\MySql\Export; | ||
|
||
use Driver\Engines\ConnectionInterface; | ||
use Driver\Pipeline\Environment\EnvironmentInterface; | ||
|
||
use function exec; | ||
use function explode; | ||
|
||
class TablesProvider | ||
{ | ||
/** | ||
* @return string[] | ||
*/ | ||
public function getAllTables(ConnectionInterface $connection): array | ||
{ | ||
$command = "mysql --user=\"{$connection->getUser()}\" --password=\"{$connection->getPassword()}\" " | ||
. "--host=\"{$connection->getHost()}\" --skip-column-names " | ||
. "-e \"SELECT GROUP_CONCAT(table_name SEPARATOR ',') FROM information_schema.tables " | ||
. "WHERE table_schema = '{$connection->getDatabase()}';\""; | ||
$result = exec($command); | ||
if (!$result) { | ||
throw new \RuntimeException('Unable to get table names'); | ||
} | ||
return explode(",", $result); | ||
} | ||
|
||
/** | ||
* @return string[] | ||
*/ | ||
public function getEmptyTables(EnvironmentInterface $environment): array | ||
{ | ||
return $environment->getEmptyTables(); | ||
} | ||
|
||
/** | ||
* @return string[] | ||
*/ | ||
public function getIgnoredTables(EnvironmentInterface $environment): array | ||
{ | ||
return $environment->getIgnoredTables(); | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
src/Tests/Unit/Engines/MySql/Export/CommandAssemblerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Driver\Tests\Unit\Engines\MySql\Export; | ||
|
||
use Driver\Engines\ConnectionInterface; | ||
use Driver\Engines\MySql\Export\CommandAssembler; | ||
use Driver\Engines\MySql\Export\TablesProvider; | ||
use Driver\Pipeline\Environment\EnvironmentInterface; | ||
use PHPUnit\Framework\MockObject\MockObject; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class CommandAssemblerTest extends TestCase | ||
{ | ||
private CommandAssembler $commandAssembler; | ||
|
||
/** @var TablesProvider&MockObject */ | ||
private MockObject $tablesProviderMock; | ||
|
||
/** @var ConnectionInterface&MockObject */ | ||
private MockObject $connectionMock; | ||
|
||
/** @var EnvironmentInterface&MockObject */ | ||
private MockObject $environmentMock; | ||
|
||
public function setUp(): void | ||
{ | ||
$this->tablesProviderMock = $this->getMockBuilder(TablesProvider::class) | ||
->disableOriginalConstructor()->getMock(); | ||
$this->connectionMock = $this->getMockBuilder(ConnectionInterface::class)->getMockForAbstractClass(); | ||
$this->connectionMock->expects($this->any())->method('getUser')->willReturn('user'); | ||
$this->connectionMock->expects($this->any())->method('getPassword')->willReturn('password'); | ||
$this->connectionMock->expects($this->any())->method('getHost')->willReturn('host'); | ||
$this->connectionMock->expects($this->any())->method('getDatabase')->willReturn('db'); | ||
$this->environmentMock = $this->getMockBuilder(EnvironmentInterface::class)->getMockForAbstractClass(); | ||
$this->commandAssembler = new CommandAssembler($this->tablesProviderMock); | ||
} | ||
|
||
public function testReturnsEmptyStringIfNoTables(): void | ||
{ | ||
$this->tablesProviderMock->expects($this->any())->method('getAllTables')->willReturn([]); | ||
$this->assertSame( | ||
'', | ||
$this->commandAssembler->execute($this->connectionMock, $this->environmentMock, 'dump.sql') | ||
); | ||
} | ||
|
||
public function testReturnsEmptyStringIfAllTablesAreIgnored(): void | ||
{ | ||
$this->tablesProviderMock->expects($this->any())->method('getAllTables')->willReturn(['a', 'b']); | ||
$this->tablesProviderMock->expects($this->any())->method('getIgnoredTables')->willReturn(['a', 'b']); | ||
$this->assertSame( | ||
'', | ||
$this->commandAssembler->execute($this->connectionMock, $this->environmentMock, 'dump.sql') | ||
); | ||
} | ||
|
||
public function testReturnsCommandForNormalTables(): void | ||
{ | ||
$this->tablesProviderMock->expects($this->any())->method('getAllTables')->willReturn(['a', 'b']); | ||
$this->tablesProviderMock->expects($this->any())->method('getIgnoredTables')->willReturn([]); | ||
$this->assertSame( | ||
'mysqldump --user="user" --password="password" --single-transaction --host=host db a >> dump.sql;' | ||
. 'mysqldump --user="user" --password="password" --single-transaction --host=host db b >> dump.sql;' | ||
. "cat dump.sql | sed -E 's/DEFINER[ ]*=[ ]*`[^`]+`@`[^`]+`/DEFINER=CURRENT_USER/g' | gzip > dump.sql.gz", | ||
$this->commandAssembler->execute($this->connectionMock, $this->environmentMock, 'dump.sql') | ||
); | ||
} | ||
|
||
public function testReturnsCommandForEmptyTables(): void | ||
{ | ||
$this->tablesProviderMock->expects($this->any())->method('getAllTables')->willReturn(['a', 'b']); | ||
$this->tablesProviderMock->expects($this->any())->method('getIgnoredTables')->willReturn([]); | ||
$this->tablesProviderMock->expects($this->any())->method('getEmptyTables')->willReturn(['a', 'b']); | ||
$this->assertSame( | ||
'mysqldump --user="user" --password="password" --single-transaction --host=host ' | ||
. 'db a b --no-data >> dump.sql;' | ||
. "cat dump.sql | sed -E 's/DEFINER[ ]*=[ ]*`[^`]+`@`[^`]+`/DEFINER=CURRENT_USER/g' | gzip > dump.sql.gz", | ||
$this->commandAssembler->execute($this->connectionMock, $this->environmentMock, 'dump.sql') | ||
); | ||
} | ||
|
||
public function testReturnsCommandForMixedTables(): void | ||
{ | ||
$this->tablesProviderMock->expects($this->any())->method('getAllTables') | ||
->willReturn(['a', 'b', 'c', 'd', 'e', 'f']); | ||
$this->tablesProviderMock->expects($this->any())->method('getIgnoredTables')->willReturn(['c', 'f']); | ||
$this->tablesProviderMock->expects($this->any())->method('getEmptyTables')->willReturn(['b', 'e']); | ||
$this->assertSame( | ||
'mysqldump --user="user" --password="password" --single-transaction --host=host db a >> dump.sql;' | ||
. 'mysqldump --user="user" --password="password" --single-transaction --host=host db d >> dump.sql;' | ||
. 'mysqldump --user="user" --password="password" --single-transaction --host=host ' | ||
. 'db b e --no-data >> dump.sql;' | ||
. "cat dump.sql | sed -E 's/DEFINER[ ]*=[ ]*`[^`]+`@`[^`]+`/DEFINER=CURRENT_USER/g' | gzip > dump.sql.gz", | ||
$this->commandAssembler->execute($this->connectionMock, $this->environmentMock, 'dump.sql') | ||
); | ||
} | ||
} |