22
33namespace 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