1010use Illuminate \Database \Query \Builder as QueryBuilder ;
1111use Illuminate \Support \Collection ;
1212use Illuminate \Support \Facades \DB ;
13+ use Illuminate \Support \Facades \Log ;
14+ use Illuminate \Support \Facades \Schema ;
1315
1416class Migrate extends Command
1517{
1618 protected $ signature = 'db:migrate '
17- . ' {--schema-from= : Source connection name} '
18- . ' {--schema-to= : Target connection name} ' ;
19+ . ' {--schema-from= : Source connection name} '
20+ . ' {--schema-to= : Target connection name} '
21+ . ' {--exclude=* : Comma separated table names to exclude} '
22+ . ' {--tables=* : Comma separated table names to migrate only} ' ;
1923
2024 protected $ description = 'Data transfer from one database to another ' ;
2125
@@ -25,56 +29,198 @@ class Migrate extends Command
2529 /** @var \DragonCode\Contracts\MigrateDB\Builder */
2630 protected $ target ;
2731
32+ /** @var array */
33+ protected $ tables ;
34+
35+ /** @var array */
36+ protected $ excludes ;
37+
38+ /** @var bool */
39+ protected $ retrieve_tables_from_target = false ;
40+
41+ /** @var bool */
42+ protected $ drop_target = false ;
43+
44+ /** @var bool */
45+ protected $ truncate = false ;
46+
47+ /** @var string */
48+ protected $ target_connection = 'target ' ;
49+
50+ /** @var string */
51+ protected $ source_connection = 'source ' ;
52+
53+ /** @var string */
54+ protected $ none = 'none ' ;
55+
56+ /** @var array */
57+ protected $ choices = ['target ' , 'source ' , 'none ' ];
58+
59+ /** @var array */
60+ protected $ migrated = [];
61+
62+ /** @var array */
63+ protected $ tables_not_exists = [];
64+
65+ /** @var array */
66+ protected $ excluded = [];
67+
2868 public function handle ()
2969 {
3070 $ this ->validateOptions ();
3171 $ this ->resolveBuilders ();
72+ $ this ->resolveOptions ();
3273 $ this ->cleanTargetDatabase ();
3374 $ this ->runMigrations ();
3475
3576 $ this ->disableForeign ();
3677 $ this ->runTransfer ();
3778 $ this ->enableForeign ();
79+
80+ $ this ->showStatus ();
81+ }
82+
83+ protected function showStatus (): void
84+ {
85+ $ this ->displayMessage ('Migrated Tables ' , $ this ->migrated );
86+ $ this ->displayMessage ('Excluded Tables ' , $ this ->excluded );
87+ $ this ->displayMessage ('Tables does not exist in source connection ' , $ this ->tables_not_exists );
88+ }
89+
90+ protected function displayMessage (string $ message , array $ context = []): void
91+ {
92+ $ this ->info ($ message );
93+
94+ if ($ context ) {
95+ $ this ->info (implode (', ' , $ context ));
96+ }
3897 }
3998
4099 protected function runTransfer (): void
41100 {
42- $ this ->info ('Transferring data... ' );
101+ $ this ->displayMessage ('Transferring data... ' . PHP_EOL );
43102
44103 $ this ->withProgressBar ($ this ->tables (), function (string $ table ) {
104+ if (in_array ($ table , $ this ->excludes )) {
105+ $ this ->excluded [] = $ table ;
106+
107+ return ;
108+ }
109+
110+ if ($ this ->doesntHasTable ($ this ->source (), $ table )) {
111+ $ this ->tables_not_exists [] = $ table ;
112+
113+ return ;
114+ }
115+
116+ $ this ->truncateTable ($ table );
45117 $ this ->migrateTable ($ table , $ this ->source ->getPrimaryKey ($ table ));
46118 });
119+
120+ $ this ->displayMessage (PHP_EOL );
121+ }
122+
123+ protected function truncateTable (string $ table ): void
124+ {
125+ if ($ this ->truncate ) {
126+ $ this ->builder ($ this ->target (), $ table )->truncate ();
127+ }
47128 }
48129
49130 protected function migrateTable (string $ table , string $ column ): void
50131 {
132+ Log::info ('Transferring data from: ' . $ table );
133+
51134 $ this ->builder ($ this ->source (), $ table )
135+ ->when (
136+ $ this ->isSkippable ($ table , $ column ),
137+ function ($ query ) use ($ table , $ column ) {
138+ $ lastRecord = $ this ->builder ($ this ->target (), $ table )->max ($ column ) ?: 0 ;
139+
140+ Log::info ('last record: ' . $ lastRecord );
141+
142+ return $ query ->where ($ column , '> ' , $ lastRecord );
143+ }
144+ )
52145 ->orderBy ($ column )
53146 ->chunk (1000 , function (Collection $ items ) use ($ table ) {
54147 $ items = Arr::toArray ($ items );
55148
56149 $ this ->builder ($ this ->target (), $ table )->insert ($ items );
57150 });
151+
152+ $ this ->migrated [] = $ table ;
153+ }
154+
155+ protected function isSkippable (string $ table , string $ column ): bool
156+ {
157+ return ! $ this ->truncate && $ this ->isNumericColumn ($ table , $ column );
158+ }
159+
160+ protected function isNumericColumn (string $ table , string $ column ): bool
161+ {
162+ return $ this ->getPrimaryKeyType ($ this ->source (), $ table , $ column ) !== 'string ' ;
58163 }
59164
60165 protected function tables (): array
61166 {
62- return $ this ->source ->getAllTables ();
167+ if ($ this ->tables ) {
168+ return $ this ->tables ;
169+ }
170+
171+ return $ this ->retrieve_tables_from_target
172+ ? $ this ->target ->getAllTables ()
173+ : $ this ->source ->getAllTables ();
63174 }
64175
65176 protected function cleanTargetDatabase (): void
66177 {
67- $ this ->info ('Clearing the target database... ' );
178+ if (! $ this ->drop_target ) {
179+ return ;
180+ }
181+
182+ $ this ->displayMessage ('Clearing the target database... ' );
68183
69184 $ this ->target ->dropAllTables ();
70185 }
71186
72187 protected function runMigrations (): void
73188 {
74- $ this ->info ('Run migrations on the databases... ' );
189+ $ on = $ this ->getMigrationOption ();
190+
191+ if ($ this ->isMigrationNotRequired ($ on )) {
192+ return ;
193+ }
194+
195+ $ this ->displayMessage ('Run migrations on the databases... ' );
75196
76- $ this ->call ('migrate ' , ['--database ' => $ this ->source ()]);
77- $ this ->call ('migrate ' , ['--database ' => $ this ->target ()]);
197+ if ($ this ->shouldRunOnSource ($ on )) {
198+ $ this ->migrate ($ this ->source ());
199+ }
200+
201+ if ($ this ->drop_target || $ this ->shouldRunOnSource ($ on ) || $ this ->shouldRunOnTarget ($ on )) {
202+ $ this ->migrate ($ this ->target ());
203+ }
204+ }
205+
206+ protected function isMigrationNotRequired (string $ on ): bool
207+ {
208+ return $ on === $ this ->none ;
209+ }
210+
211+ protected function shouldRunOnTarget (string $ on ): bool
212+ {
213+ return $ on === $ this ->target_connection ;
214+ }
215+
216+ protected function shouldRunOnSource (string $ on ): bool
217+ {
218+ return $ on === $ this ->source_connection ;
219+ }
220+
221+ protected function migrate (string $ connection ): void
222+ {
223+ $ this ->call ('migrate ' , ['--database ' => $ connection ]);
78224 }
79225
80226 protected function disableForeign (): void
@@ -97,6 +243,36 @@ protected function target(): string
97243 return $ this ->validatedOption ('schema-to ' );
98244 }
99245
246+ protected function getTablesOption (): array
247+ {
248+ return $ this ->option ('tables ' );
249+ }
250+
251+ protected function getExcludeOption (): array
252+ {
253+ return $ this ->option ('exclude ' );
254+ }
255+
256+ protected function getMigrationOption (): string
257+ {
258+ return $ this ->choice ('Please choose option to run migration on which connection? ' , $ this ->choices , 0 );
259+ }
260+
261+ protected function confirmTableListOption (): bool
262+ {
263+ return $ this ->confirm ('Please confirm table list should be retrieved from target connection? (incase if source connection does not support it) ' , false );
264+ }
265+
266+ protected function confirmTruncateTableOption (): bool
267+ {
268+ return $ this ->confirm ('Please confirm whether to truncate target table before transfer? ' , false );
269+ }
270+
271+ protected function confirmDropOption (): bool
272+ {
273+ return $ this ->confirm ('Please choose whether to drop target tables before migration? ' , false );
274+ }
275+
100276 protected function validatedOption (string $ key ): string
101277 {
102278 if ($ schema = $ this ->option ($ key )) {
@@ -123,8 +299,36 @@ protected function resolveBuilders(): void
123299 $ this ->target = $ this ->resolveBuilder ($ this ->target ());
124300 }
125301
302+ protected function resolveOptions (): void
303+ {
304+ $ this ->tables = $ this ->getTablesOption ();
305+ $ this ->excludes = $ this ->getExcludeOption ();
306+
307+ if (empty ($ this ->tables ) && $ this ->confirmTableListOption ()) {
308+ $ this ->retrieve_tables_from_target = true ;
309+ }
310+
311+ if ($ this ->confirmTruncateTableOption ()) {
312+ $ this ->truncate = true ;
313+ }
314+
315+ if (empty ($ this ->tables ) && empty ($ this ->excludes ) && $ this ->truncate && $ this ->confirmDropOption ()) {
316+ $ this ->drop_target = true ;
317+ }
318+ }
319+
126320 protected function builder (string $ connection , string $ table ): QueryBuilder
127321 {
128322 return DB ::connection ($ connection )->table ($ table );
129323 }
324+
325+ protected function doesntHasTable (string $ connection , string $ table ): bool
326+ {
327+ return ! Schema::connection ($ connection )->hasTable ($ table );
328+ }
329+
330+ protected function getPrimaryKeyType (string $ connection , string $ table , string $ column ): string
331+ {
332+ return DB ::connection ($ connection )->getDoctrineColumn ($ table , $ column )->getType ()->getName ();
333+ }
130334}
0 commit comments