2
2
3
3
namespace OrisIntel \MigrationSnapshot \Commands ;
4
4
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
6
9
{
7
10
public const SCHEMA_SQL_PATH_SUFFIX = '/migrations/sql/schema.sql ' ;
11
+ public const DATA_SQL_PATH_SUFFIX = '/migrations/sql/data.sql ' ;
12
+
8
13
public const SUPPORTED_DB_DRIVERS = ['mysql ' , 'pgsql ' , 'sqlite ' ];
9
14
10
15
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
+ ' ;
12
19
13
20
protected $ description = 'Dump current database schema/structure as plain-text SQL file. ' ;
14
21
15
22
public function handle ()
16
23
{
17
24
$ exit_code = null ;
18
25
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 ();
22
29
23
30
// CONSIDER: Ending with ".mysql" or "-mysql.sql" unless in
24
31
// compatibility mode.
@@ -41,7 +48,7 @@ public function handle()
41
48
// CONSIDER: Option to dump to console Stdout instead.
42
49
// CONSIDER: Option to dump for each DB connection instead of only one.
43
50
// CONSIDER: Separate classes.
44
- $ method = $ db_config ['driver ' ] . 'Dump ' ;
51
+ $ method = $ db_config ['driver ' ] . 'SchemaDump ' ;
45
52
$ exit_code = self ::{$ method }($ db_config , $ schema_sql_path );
46
53
47
54
if (0 !== $ exit_code ) {
@@ -54,8 +61,34 @@ public function handle()
54
61
}
55
62
56
63
$ 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 ' );
57
85
}
58
86
87
+ /**
88
+ * @param array $output
89
+ *
90
+ * @return array
91
+ */
59
92
public static function reorderMigrationRows (array $ output ) : array
60
93
{
61
94
if (config ('migration-snapshot.reorder ' )) {
@@ -95,31 +128,18 @@ public static function reorderMigrationRows(array $output) : array
95
128
*
96
129
* @return int containing exit code.
97
130
*/
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
99
132
{
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 ' ]);
114
133
// TODO: Suppress warning about insecure password.
115
134
// CONSIDER: Intercepting stdout and stderr and converting to colorized
116
135
// console output with `$this->info` and `->error`.
117
136
passthru (
118
- $ command_prefix
137
+ static :: mysqlCommandPrefix ( $ db_config )
119
138
. ' --result-file= ' . escapeshellarg ($ schema_sql_path )
120
139
. ' --no-data ' ,
121
140
$ exit_code
122
141
);
142
+
123
143
if (0 !== $ exit_code ) {
124
144
return $ exit_code ;
125
145
}
@@ -128,6 +148,7 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i
128
148
if (false === $ schema_sql ) {
129
149
return 1 ;
130
150
}
151
+
131
152
$ schema_sql = preg_replace ('/\s+AUTO_INCREMENT=[0-9]+/iu ' , '' , $ schema_sql );
132
153
$ schema_sql = self ::trimUnderscoresFromForeign ($ schema_sql );
133
154
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
136
157
137
158
// Include migration rows to avoid unnecessary reruns conflicting.
138
159
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 ' ,
140
161
$ output ,
141
162
$ exit_code
142
163
);
164
+
143
165
if (0 !== $ exit_code ) {
144
166
return $ exit_code ;
145
167
}
@@ -157,6 +179,63 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i
157
179
return $ exit_code ;
158
180
}
159
181
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
+
160
239
/**
161
240
* Trim underscores from FK constraint names to workaround PTOSC quirk.
162
241
*
@@ -208,38 +287,30 @@ public static function trimUnderscoresFromForeign(string $sql) : string
208
287
209
288
/**
210
289
* @param array $db_config like ['host' => , 'port' => ].
290
+ * @param string $schema_sql_path
211
291
*
212
292
* @return int containing exit code.
213
293
*/
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
215
295
{
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 ' ]);
227
296
passthru (
228
- $ command_prefix
297
+ static :: pgsqlCommandPrefix ( $ db_config )
229
298
. ' --file= ' . escapeshellarg ($ schema_sql_path )
230
299
. ' --schema-only ' ,
231
300
$ exit_code
232
301
);
302
+
233
303
if (0 !== $ exit_code ) {
234
304
return $ exit_code ;
235
305
}
236
306
237
307
// Include migration rows to avoid unnecessary reruns conflicting.
238
308
exec (
239
- $ command_prefix . ' --table=migrations --data-only --inserts ' ,
309
+ static :: pgsqlCommandPrefix ( $ db_config ) . ' --table=migrations --data-only --inserts ' ,
240
310
$ output ,
241
311
$ exit_code
242
312
);
313
+
243
314
if (0 !== $ exit_code ) {
244
315
return $ exit_code ;
245
316
}
@@ -264,13 +335,51 @@ function ($line) {
264
335
return $ exit_code ;
265
336
}
266
337
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
+
267
376
/**
268
377
* @param array $db_config like ['host' => , 'port' => ].
269
378
* @param string $schema_sql_path like '.../schema.sql'
270
379
*
271
380
* @return int containing exit code.
272
381
*/
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
274
383
{
275
384
// CONSIDER: Accepting command name as option or from config.
276
385
$ command_prefix = 'sqlite3 ' . escapeshellarg ($ db_config ['database ' ]);
@@ -283,6 +392,7 @@ private static function sqliteDump(array $db_config, string $schema_sql_path) :
283
392
if (0 !== $ exit_code ) {
284
393
return $ exit_code ;
285
394
}
395
+
286
396
$ tables = preg_split ('/\s+/ ' , implode (' ' , $ output ));
287
397
288
398
file_put_contents ($ schema_sql_path , '' );
@@ -297,6 +407,7 @@ private static function sqliteDump(array $db_config, string $schema_sql_path) :
297
407
$ output ,
298
408
$ exit_code
299
409
);
410
+
300
411
if (0 !== $ exit_code ) {
301
412
return $ exit_code ;
302
413
}
@@ -316,4 +427,56 @@ private static function sqliteDump(array $db_config, string $schema_sql_path) :
316
427
317
428
return $ exit_code ;
318
429
}
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
+ }
319
482
}
0 commit comments