Skip to content

Commit a7d019f

Browse files
author
Tom Schlick
authored
Merge pull request #9 from orisintel/feature/include-data
toggle to include data in dump
2 parents 4312ace + 53d86d3 commit a7d019f

File tree

3 files changed

+273
-60
lines changed

3 files changed

+273
-60
lines changed

config/migration-snapshot.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,16 @@
4444
|
4545
*/
4646
'trim-underscores' => env('MIGRATION_SNAPSHOT_TRIM_UNDERSCORES', true),
47+
48+
/*
49+
|--------------------------------------------------------------------------
50+
| Include Data
51+
|--------------------------------------------------------------------------
52+
|
53+
| Include existing table data in the database dump. Useful for when you
54+
| have constant defined values like a system user with a specific ID or
55+
| records with special IDs which must match another environment.
56+
|
57+
*/
58+
'data' => env('MIGRATION_SNAPSHOT_DATA', false),
4759
];

src/Commands/MigrateDumpCommand.php

Lines changed: 201 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,30 @@
22

33
namespace OrisIntel\MigrationSnapshot\Commands;
44

5-
final class MigrateDumpCommand extends \Illuminate\Console\Command
5+
use Illuminate\Console\Command;
6+
use Illuminate\Support\Facades\DB;
7+
8+
final class MigrateDumpCommand extends Command
69
{
710
public const SCHEMA_SQL_PATH_SUFFIX = '/migrations/sql/schema.sql';
11+
public const DATA_SQL_PATH_SUFFIX = '/migrations/sql/data.sql';
12+
813
public const SUPPORTED_DB_DRIVERS = ['mysql', 'pgsql', 'sqlite'];
914

1015
protected $signature = 'migrate:dump
11-
{--database= : The database connection to use}';
16+
{--database= : The database connection to use}
17+
{--include-data : Include data present in the tables that was created via migrations. }
18+
';
1219

1320
protected $description = 'Dump current database schema/structure as plain-text SQL file.';
1421

1522
public function handle()
1623
{
1724
$exit_code = null;
1825

19-
$database = $this->option('database') ?: \DB::getDefaultConnection();
20-
\DB::setDefaultConnection($database);
21-
$db_config = \DB::getConfig();
26+
$database = $this->option('database') ?: DB::getDefaultConnection();
27+
DB::setDefaultConnection($database);
28+
$db_config = DB::getConfig();
2229

2330
// CONSIDER: Ending with ".mysql" or "-mysql.sql" unless in
2431
// compatibility mode.
@@ -41,7 +48,7 @@ public function handle()
4148
// CONSIDER: Option to dump to console Stdout instead.
4249
// CONSIDER: Option to dump for each DB connection instead of only one.
4350
// CONSIDER: Separate classes.
44-
$method = $db_config['driver'] . 'Dump';
51+
$method = $db_config['driver'] . 'SchemaDump';
4552
$exit_code = self::{$method}($db_config, $schema_sql_path);
4653

4754
if (0 !== $exit_code) {
@@ -54,8 +61,34 @@ public function handle()
5461
}
5562

5663
$this->info('Dumped schema');
64+
65+
if (! $this->option('include-data')) {
66+
return;
67+
}
68+
69+
$this->info('Starting Data Dump');
70+
71+
$data_sql_path = database_path() . self::DATA_SQL_PATH_SUFFIX;
72+
73+
$method = $db_config['driver'] . 'DataDump';
74+
$exit_code = self::{$method}($db_config, $data_sql_path);
75+
76+
if (0 !== $exit_code) {
77+
if (file_exists($data_sql_path)) {
78+
unlink($data_sql_path);
79+
}
80+
81+
exit($exit_code);
82+
}
83+
84+
$this->info('Dumped Data');
5785
}
5886

87+
/**
88+
* @param array $output
89+
*
90+
* @return array
91+
*/
5992
public static function reorderMigrationRows(array $output) : array
6093
{
6194
if (config('migration-snapshot.reorder')) {
@@ -95,31 +128,18 @@ public static function reorderMigrationRows(array $output) : array
95128
*
96129
* @return int containing exit code.
97130
*/
98-
private static function mysqlDump(array $db_config, string $schema_sql_path) : int
131+
private static function mysqlSchemaDump(array $db_config, string $schema_sql_path) : int
99132
{
100-
// CONSIDER: Supporting unix_socket.
101-
// CONSIDER: Alternative tools like `xtrabackup` or even just querying
102-
// "SHOW CREATE TABLE" via Eloquent.
103-
// CONSIDER: Capturing Stderr and outputting with `$this->error()`.
104-
105-
// Not including connection name in file since typically only one DB.
106-
// Excluding any hash or date suffix since only current is relevant.
107-
$command_prefix = 'mysqldump --routines --skip-add-drop-table'
108-
. ' --skip-add-locks --skip-comments --skip-set-charset --tz-utc'
109-
. ' --host=' . escapeshellarg($db_config['host'])
110-
. ' --port=' . escapeshellarg($db_config['port'])
111-
. ' --user=' . escapeshellarg($db_config['username'])
112-
. ' --password=' . escapeshellarg($db_config['password'])
113-
. ' ' . escapeshellarg($db_config['database']);
114133
// TODO: Suppress warning about insecure password.
115134
// CONSIDER: Intercepting stdout and stderr and converting to colorized
116135
// console output with `$this->info` and `->error`.
117136
passthru(
118-
$command_prefix
137+
static::mysqlCommandPrefix($db_config)
119138
. ' --result-file=' . escapeshellarg($schema_sql_path)
120139
. ' --no-data',
121140
$exit_code
122141
);
142+
123143
if (0 !== $exit_code) {
124144
return $exit_code;
125145
}
@@ -128,6 +148,7 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i
128148
if (false === $schema_sql) {
129149
return 1;
130150
}
151+
131152
$schema_sql = preg_replace('/\s+AUTO_INCREMENT=[0-9]+/iu', '', $schema_sql);
132153
$schema_sql = self::trimUnderscoresFromForeign($schema_sql);
133154
if (false === file_put_contents($schema_sql_path, $schema_sql)) {
@@ -136,10 +157,11 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i
136157

137158
// Include migration rows to avoid unnecessary reruns conflicting.
138159
exec(
139-
$command_prefix . ' migrations --no-create-info --skip-extended-insert --compact',
160+
static::mysqlCommandPrefix($db_config) . ' migrations --no-create-info --skip-extended-insert --compact',
140161
$output,
141162
$exit_code
142163
);
164+
143165
if (0 !== $exit_code) {
144166
return $exit_code;
145167
}
@@ -157,6 +179,63 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i
157179
return $exit_code;
158180
}
159181

182+
/**
183+
* @param array $db_config like ['host' => , 'port' => ].
184+
* @param string $data_sql_path like '.../data.sql'
185+
*
186+
* @return int containing exit code.
187+
*/
188+
private static function mysqlDataDump(array $db_config, string $data_sql_path) : int
189+
{
190+
passthru(
191+
static::mysqlCommandPrefix($db_config)
192+
. ' --result-file=' . escapeshellarg($data_sql_path)
193+
. ' --no-create-info --skip-triggers'
194+
. ' --ignore-table=' . escapeshellarg($db_config['database'] . '.migrations'),
195+
$exit_code
196+
);
197+
198+
if (0 !== $exit_code) {
199+
return $exit_code;
200+
}
201+
202+
$data_sql = file_get_contents($data_sql_path);
203+
if (false === $data_sql) {
204+
return 1;
205+
}
206+
207+
$data_sql = preg_replace('/\s+AUTO_INCREMENT=[0-9]+/iu', '', $data_sql);
208+
if (false === file_put_contents($data_sql_path, $data_sql)) {
209+
return 1;
210+
}
211+
212+
return $exit_code;
213+
}
214+
215+
/**
216+
* @param array $db_config
217+
*
218+
* @return string
219+
*/
220+
private static function mysqlCommandPrefix(array $db_config) : string
221+
{
222+
// CONSIDER: Supporting unix_socket.
223+
// CONSIDER: Alternative tools like `xtrabackup` or even just querying
224+
// "SHOW CREATE TABLE" via Eloquent.
225+
// CONSIDER: Capturing Stderr and outputting with `$this->error()`.
226+
227+
// Not including connection name in file since typically only one DB.
228+
// Excluding any hash or date suffix since only current is relevant.
229+
230+
return 'mysqldump --routines --skip-add-drop-table'
231+
. ' --skip-add-locks --skip-comments --skip-set-charset --tz-utc'
232+
. ' --host=' . escapeshellarg($db_config['host'])
233+
. ' --port=' . escapeshellarg($db_config['port'])
234+
. ' --user=' . escapeshellarg($db_config['username'])
235+
. ' --password=' . escapeshellarg($db_config['password'])
236+
. ' ' . escapeshellarg($db_config['database']);
237+
}
238+
160239
/**
161240
* Trim underscores from FK constraint names to workaround PTOSC quirk.
162241
*
@@ -208,38 +287,30 @@ public static function trimUnderscoresFromForeign(string $sql) : string
208287

209288
/**
210289
* @param array $db_config like ['host' => , 'port' => ].
290+
* @param string $schema_sql_path
211291
*
212292
* @return int containing exit code.
213293
*/
214-
private static function pgsqlDump(array $db_config, string $schema_sql_path) : int
294+
private static function pgsqlSchemaDump(array $db_config, string $schema_sql_path) : int
215295
{
216-
// CONSIDER: Supporting unix_socket.
217-
// CONSIDER: Instead querying pg catalog tables via Eloquent.
218-
// CONSIDER: Capturing Stderr and outputting with `$this->error()`.
219-
220-
// CONSIDER: Instead using DSN-like URL instead of env. var. for pass.
221-
$command_prefix = 'PGPASSWORD=' . escapeshellarg($db_config['password'])
222-
. ' pg_dump'
223-
. ' --host=' . escapeshellarg($db_config['host'])
224-
. ' --port=' . escapeshellarg($db_config['port'])
225-
. ' --username=' . escapeshellarg($db_config['username'])
226-
. ' ' . escapeshellarg($db_config['database']);
227296
passthru(
228-
$command_prefix
297+
static::pgsqlCommandPrefix($db_config)
229298
. ' --file=' . escapeshellarg($schema_sql_path)
230299
. ' --schema-only',
231300
$exit_code
232301
);
302+
233303
if (0 !== $exit_code) {
234304
return $exit_code;
235305
}
236306

237307
// Include migration rows to avoid unnecessary reruns conflicting.
238308
exec(
239-
$command_prefix . ' --table=migrations --data-only --inserts',
309+
static::pgsqlCommandPrefix($db_config) . ' --table=migrations --data-only --inserts',
240310
$output,
241311
$exit_code
242312
);
313+
243314
if (0 !== $exit_code) {
244315
return $exit_code;
245316
}
@@ -264,13 +335,51 @@ function ($line) {
264335
return $exit_code;
265336
}
266337

338+
/**
339+
* @param array $db_config
340+
* @param string $data_sql_path
341+
*
342+
* @return int
343+
*/
344+
private static function pgsqlDataDump(array $db_config, string $data_sql_path) : int
345+
{
346+
passthru(
347+
static::pgsqlCommandPrefix($db_config)
348+
. ' --file=' . escapeshellarg($data_sql_path)
349+
. ' --exclude-table=' . escapeshellarg($db_config['database'] . '.migrations')
350+
. ' --data-only',
351+
$exit_code
352+
);
353+
354+
if (0 !== $exit_code) {
355+
return $exit_code;
356+
}
357+
358+
return $exit_code;
359+
}
360+
361+
/**
362+
* @param array $db_config
363+
*
364+
* @return string
365+
*/
366+
private static function pgsqlCommandPrefix(array $db_config) : string
367+
{
368+
return 'PGPASSWORD=' . escapeshellarg($db_config['password'])
369+
. ' pg_dump'
370+
. ' --host=' . escapeshellarg($db_config['host'])
371+
. ' --port=' . escapeshellarg($db_config['port'])
372+
. ' --username=' . escapeshellarg($db_config['username'])
373+
. ' ' . escapeshellarg($db_config['database']);
374+
}
375+
267376
/**
268377
* @param array $db_config like ['host' => , 'port' => ].
269378
* @param string $schema_sql_path like '.../schema.sql'
270379
*
271380
* @return int containing exit code.
272381
*/
273-
private static function sqliteDump(array $db_config, string $schema_sql_path) : int
382+
private static function sqliteSchemaDump(array $db_config, string $schema_sql_path) : int
274383
{
275384
// CONSIDER: Accepting command name as option or from config.
276385
$command_prefix = 'sqlite3 ' . escapeshellarg($db_config['database']);
@@ -283,6 +392,7 @@ private static function sqliteDump(array $db_config, string $schema_sql_path) :
283392
if (0 !== $exit_code) {
284393
return $exit_code;
285394
}
395+
286396
$tables = preg_split('/\s+/', implode(' ', $output));
287397

288398
file_put_contents($schema_sql_path, '');
@@ -297,6 +407,7 @@ private static function sqliteDump(array $db_config, string $schema_sql_path) :
297407
$output,
298408
$exit_code
299409
);
410+
300411
if (0 !== $exit_code) {
301412
return $exit_code;
302413
}
@@ -316,4 +427,56 @@ private static function sqliteDump(array $db_config, string $schema_sql_path) :
316427

317428
return $exit_code;
318429
}
430+
431+
/**
432+
* @param array $db_config
433+
* @param string $data_sql_path
434+
*
435+
* @return int
436+
*/
437+
private static function sqliteDataDump(array $db_config, string $data_sql_path) : int
438+
{
439+
// CONSIDER: Accepting command name as option or from config.
440+
$command_prefix = 'sqlite3 ' . escapeshellarg($db_config['database']);
441+
442+
// Since Sqlite lacks Information Schema, and dumping everything may be
443+
// too slow or memory intense, just query tables and dump them
444+
// individually.
445+
// CONSIDER: Using Laravel's `Schema` code instead.
446+
exec($command_prefix . ' .tables', $output, $exit_code);
447+
if (0 !== $exit_code) {
448+
return $exit_code;
449+
}
450+
451+
$tables = preg_split('/\s+/', implode(' ', $output));
452+
453+
foreach ($tables as $table) {
454+
// We don't want to dump the migrations table here
455+
if ('migrations' === $table) {
456+
continue;
457+
}
458+
459+
// Only migrations should dump data with schema.
460+
$sql_command = '.dump';
461+
462+
$output = [];
463+
exec(
464+
$command_prefix . ' ' . escapeshellarg("$sql_command $table"),
465+
$output,
466+
$exit_code
467+
);
468+
469+
if (0 !== $exit_code) {
470+
return $exit_code;
471+
}
472+
473+
file_put_contents(
474+
$data_sql_path,
475+
implode(PHP_EOL, $output) . PHP_EOL,
476+
FILE_APPEND
477+
);
478+
}
479+
480+
return $exit_code;
481+
}
319482
}

0 commit comments

Comments
 (0)