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,6 +61,27 @@ 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 ('Finished Data Dump ' );
5785 }
5886
5987 public static function reorderMigrationRows (array $ output ) : array
@@ -95,32 +123,18 @@ public static function reorderMigrationRows(array $output) : array
95123 *
96124 * @return int containing exit code.
97125 */
98- private static function mysqlDump (array $ db_config , string $ schema_sql_path ) : int
126+ private static function mysqlSchemaDump (array $ db_config , string $ schema_sql_path ) : int
99127 {
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 ' ]);
114128 // TODO: Suppress warning about insecure password.
115129 // CONSIDER: Intercepting stdout and stderr and converting to colorized
116130 // console output with `$this->info` and `->error`.
117131 passthru (
118- $ command_prefix
119- . ' --result-file= '
120- . escapeshellarg ($ schema_sql_path )
121- . (config ('migration-snapshot.data ' ) ? '' : ' --no-data ' ),
132+ static ::mysqlCommandPrefix ($ db_config )
133+ . ' --result-file= ' . escapeshellarg ($ schema_sql_path )
134+ . ' --no-data ' ,
122135 $ exit_code
123136 );
137+
124138 if (0 !== $ exit_code ) {
125139 return $ exit_code ;
126140 }
@@ -129,6 +143,7 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i
129143 if (false === $ schema_sql ) {
130144 return 1 ;
131145 }
146+
132147 $ schema_sql = preg_replace ('/\s+AUTO_INCREMENT=[0-9]+/iu ' , '' , $ schema_sql );
133148 $ schema_sql = self ::trimUnderscoresFromForeign ($ schema_sql );
134149 if (false === file_put_contents ($ schema_sql_path , $ schema_sql )) {
@@ -137,10 +152,11 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i
137152
138153 // Include migration rows to avoid unnecessary reruns conflicting.
139154 exec (
140- $ command_prefix . ' migrations --no-create-info --skip-extended-insert --compact ' ,
155+ static :: mysqlCommandPrefix ( $ db_config ) . ' migrations --no-create-info --skip-extended-insert --compact ' ,
141156 $ output ,
142157 $ exit_code
143158 );
159+
144160 if (0 !== $ exit_code ) {
145161 return $ exit_code ;
146162 }
@@ -158,6 +174,63 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i
158174 return $ exit_code ;
159175 }
160176
177+ /**
178+ * @param array $db_config like ['host' => , 'port' => ].
179+ * @param string $data_sql_path like '.../data.sql'
180+ *
181+ * @return int containing exit code.
182+ */
183+ private static function mysqlDataDump (array $ db_config , string $ data_sql_path ) : int
184+ {
185+ passthru (
186+ static ::mysqlCommandPrefix ($ db_config )
187+ . ' --result-file= ' . escapeshellarg ($ data_sql_path )
188+ . ' --no-create-info --skip-triggers '
189+ . ' --ignore-table= ' . escapeshellarg ($ db_config ['database ' ]) . '.migrations ' ,
190+ $ exit_code
191+ );
192+
193+ if (0 !== $ exit_code ) {
194+ return $ exit_code ;
195+ }
196+
197+ $ data_sql = file_get_contents ($ data_sql_path );
198+ if (false === $ data_sql ) {
199+ return 1 ;
200+ }
201+
202+ $ data_sql = preg_replace ('/\s+AUTO_INCREMENT=[0-9]+/iu ' , '' , $ data_sql );
203+ if (false === file_put_contents ($ data_sql_path , $ data_sql )) {
204+ return 1 ;
205+ }
206+
207+ return $ exit_code ;
208+ }
209+
210+ /**
211+ * @param array $db_config
212+ *
213+ * @return string
214+ */
215+ private static function mysqlCommandPrefix (array $ db_config ) : string
216+ {
217+ // CONSIDER: Supporting unix_socket.
218+ // CONSIDER: Alternative tools like `xtrabackup` or even just querying
219+ // "SHOW CREATE TABLE" via Eloquent.
220+ // CONSIDER: Capturing Stderr and outputting with `$this->error()`.
221+
222+ // Not including connection name in file since typically only one DB.
223+ // Excluding any hash or date suffix since only current is relevant.
224+
225+ return 'mysqldump --routines --skip-add-drop-table '
226+ . ' --skip-add-locks --skip-comments --skip-set-charset --tz-utc '
227+ . ' --host= ' . escapeshellarg ($ db_config ['host ' ])
228+ . ' --port= ' . escapeshellarg ($ db_config ['port ' ])
229+ . ' --user= ' . escapeshellarg ($ db_config ['username ' ])
230+ . ' --password= ' . escapeshellarg ($ db_config ['password ' ])
231+ . ' ' . escapeshellarg ($ db_config ['database ' ]);
232+ }
233+
161234 /**
162235 * Trim underscores from FK constraint names to workaround PTOSC quirk.
163236 *
@@ -209,38 +282,30 @@ public static function trimUnderscoresFromForeign(string $sql) : string
209282
210283 /**
211284 * @param array $db_config like ['host' => , 'port' => ].
285+ * @param string $schema_sql_path
212286 *
213287 * @return int containing exit code.
214288 */
215- private static function pgsqlDump (array $ db_config , string $ schema_sql_path ) : int
289+ private static function pgsqlSchemaDump (array $ db_config , string $ schema_sql_path ) : int
216290 {
217- // CONSIDER: Supporting unix_socket.
218- // CONSIDER: Instead querying pg catalog tables via Eloquent.
219- // CONSIDER: Capturing Stderr and outputting with `$this->error()`.
220-
221- // CONSIDER: Instead using DSN-like URL instead of env. var. for pass.
222- $ command_prefix = 'PGPASSWORD= ' . escapeshellarg ($ db_config ['password ' ])
223- . ' pg_dump '
224- . ' --host= ' . escapeshellarg ($ db_config ['host ' ])
225- . ' --port= ' . escapeshellarg ($ db_config ['port ' ])
226- . ' --username= ' . escapeshellarg ($ db_config ['username ' ])
227- . ' ' . escapeshellarg ($ db_config ['database ' ]);
228291 passthru (
229- $ command_prefix
292+ static :: pgsqlCommandPrefix ( $ db_config )
230293 . ' --file= ' . escapeshellarg ($ schema_sql_path )
231- . ( config ( ' migration-snapshot.data ' ) ? '' : ' --schema-only ') ,
294+ . ' --schema-only ' ,
232295 $ exit_code
233296 );
297+
234298 if (0 !== $ exit_code ) {
235299 return $ exit_code ;
236300 }
237301
238302 // Include migration rows to avoid unnecessary reruns conflicting.
239303 exec (
240- $ command_prefix . ' --table=migrations --data-only --inserts ' ,
304+ static :: pgsqlCommandPrefix ( $ db_config ) . ' --table=migrations --data-only --inserts ' ,
241305 $ output ,
242306 $ exit_code
243307 );
308+
244309 if (0 !== $ exit_code ) {
245310 return $ exit_code ;
246311 }
@@ -265,13 +330,44 @@ function ($line) {
265330 return $ exit_code ;
266331 }
267332
333+ private static function pgsqlDataDump (array $ db_config , string $ data_sql_path ) : int
334+ {
335+ passthru (
336+ static ::pgsqlCommandPrefix ($ db_config )
337+ . ' --file= ' . escapeshellarg ($ data_sql_path )
338+ . ' --data-only ' ,
339+ $ exit_code
340+ );
341+
342+ if (0 !== $ exit_code ) {
343+ return $ exit_code ;
344+ }
345+
346+ return $ exit_code ;
347+ }
348+
349+ /**
350+ * @param array $db_config
351+ *
352+ * @return string
353+ */
354+ private static function pgsqlCommandPrefix (array $ db_config ) : string
355+ {
356+ return 'PGPASSWORD= ' . escapeshellarg ($ db_config ['password ' ])
357+ . ' pg_dump '
358+ . ' --host= ' . escapeshellarg ($ db_config ['host ' ])
359+ . ' --port= ' . escapeshellarg ($ db_config ['port ' ])
360+ . ' --username= ' . escapeshellarg ($ db_config ['username ' ])
361+ . ' ' . escapeshellarg ($ db_config ['database ' ]);
362+ }
363+
268364 /**
269365 * @param array $db_config like ['host' => , 'port' => ].
270366 * @param string $schema_sql_path like '.../schema.sql'
271367 *
272368 * @return int containing exit code.
273369 */
274- private static function sqliteDump (array $ db_config , string $ schema_sql_path ) : int
370+ private static function sqliteSchemaDump (array $ db_config , string $ schema_sql_path ) : int
275371 {
276372 // CONSIDER: Accepting command name as option or from config.
277373 $ command_prefix = 'sqlite3 ' . escapeshellarg ($ db_config ['database ' ]);
@@ -284,6 +380,7 @@ private static function sqliteDump(array $db_config, string $schema_sql_path) :
284380 if (0 !== $ exit_code ) {
285381 return $ exit_code ;
286382 }
383+
287384 $ tables = preg_split ('/\s+/ ' , implode (' ' , $ output ));
288385
289386 file_put_contents ($ schema_sql_path , '' );
@@ -298,11 +395,59 @@ private static function sqliteDump(array $db_config, string $schema_sql_path) :
298395 $ output ,
299396 $ exit_code
300397 );
398+
399+ if (0 !== $ exit_code ) {
400+ return $ exit_code ;
401+ }
402+
403+ if ('migrations ' === $ table ) {
404+ $ insert_rows = array_slice ($ output , 4 , -1 );
405+ $ sorted = self ::reorderMigrationRows ($ insert_rows );
406+ array_splice ($ output , 4 , -1 , $ sorted );
407+ }
408+
409+ file_put_contents (
410+ $ schema_sql_path ,
411+ implode (PHP_EOL , $ output ) . PHP_EOL ,
412+ FILE_APPEND
413+ );
414+ }
415+
416+ return $ exit_code ;
417+ }
418+
419+ private static function sqliteDataDump (array $ db_config , string $ schema_sql_path ) : int
420+ {
421+ // CONSIDER: Accepting command name as option or from config.
422+ $ command_prefix = 'sqlite3 ' . escapeshellarg ($ db_config ['database ' ]);
423+
424+ // Since Sqlite lacks Information Schema, and dumping everything may be
425+ // too slow or memory intense, just query tables and dump them
426+ // individually.
427+ // CONSIDER: Using Laravel's `Schema` code instead.
428+ exec ($ command_prefix . ' .tables ' , $ output , $ exit_code );
429+ if (0 !== $ exit_code ) {
430+ return $ exit_code ;
431+ }
432+
433+ $ tables = preg_split ('/\s+/ ' , implode (' ' , $ output ));
434+
435+ foreach ($ tables as $ table ) {
436+ // Only migrations should dump data with schema.
437+ $ sql_command = 'migrations ' !== $ table ? '.dump ' : '.schema ' ;
438+
439+ $ output = [];
440+ exec (
441+ $ command_prefix . ' ' . escapeshellarg ("$ sql_command $ table " ),
442+ $ output ,
443+ $ exit_code
444+ );
445+
301446 if (0 !== $ exit_code ) {
302447 return $ exit_code ;
303448 }
304449
305- if (config ( ' migration-snapshot.data ' ) || ' migrations ' = == $ table ) {
450+ if (' migrations ' ! == $ table ) {
306451 $ insert_rows = array_slice ($ output , 4 , -1 );
307452 $ sorted = self ::reorderMigrationRows ($ insert_rows );
308453 array_splice ($ output , 4 , -1 , $ sorted );
0 commit comments