10
10
use Illuminate \Database \Query \Builder as QueryBuilder ;
11
11
use Illuminate \Support \Collection ;
12
12
use Illuminate \Support \Facades \DB ;
13
+ use Illuminate \Support \Facades \Log ;
14
+ use Illuminate \Support \Facades \Schema ;
13
15
14
16
class Migrate extends Command
15
17
{
16
18
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} ' ;
19
23
20
24
protected $ description = 'Data transfer from one database to another ' ;
21
25
@@ -25,56 +29,198 @@ class Migrate extends Command
25
29
/** @var \DragonCode\Contracts\MigrateDB\Builder */
26
30
protected $ target ;
27
31
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
+
28
68
public function handle ()
29
69
{
30
70
$ this ->validateOptions ();
31
71
$ this ->resolveBuilders ();
72
+ $ this ->resolveOptions ();
32
73
$ this ->cleanTargetDatabase ();
33
74
$ this ->runMigrations ();
34
75
35
76
$ this ->disableForeign ();
36
77
$ this ->runTransfer ();
37
78
$ 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
+ }
38
97
}
39
98
40
99
protected function runTransfer (): void
41
100
{
42
- $ this ->info ('Transferring data... ' );
101
+ $ this ->displayMessage ('Transferring data... ' . PHP_EOL );
43
102
44
103
$ 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 );
45
117
$ this ->migrateTable ($ table , $ this ->source ->getPrimaryKey ($ table ));
46
118
});
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
+ }
47
128
}
48
129
49
130
protected function migrateTable (string $ table , string $ column ): void
50
131
{
132
+ Log::info ('Transferring data from: ' . $ table );
133
+
51
134
$ 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
+ )
52
145
->orderBy ($ column )
53
146
->chunk (1000 , function (Collection $ items ) use ($ table ) {
54
147
$ items = Arr::toArray ($ items );
55
148
56
149
$ this ->builder ($ this ->target (), $ table )->insert ($ items );
57
150
});
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 ' ;
58
163
}
59
164
60
165
protected function tables (): array
61
166
{
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 ();
63
174
}
64
175
65
176
protected function cleanTargetDatabase (): void
66
177
{
67
- $ this ->info ('Clearing the target database... ' );
178
+ if (! $ this ->drop_target ) {
179
+ return ;
180
+ }
181
+
182
+ $ this ->displayMessage ('Clearing the target database... ' );
68
183
69
184
$ this ->target ->dropAllTables ();
70
185
}
71
186
72
187
protected function runMigrations (): void
73
188
{
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... ' );
75
196
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 ]);
78
224
}
79
225
80
226
protected function disableForeign (): void
@@ -97,6 +243,36 @@ protected function target(): string
97
243
return $ this ->validatedOption ('schema-to ' );
98
244
}
99
245
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
+
100
276
protected function validatedOption (string $ key ): string
101
277
{
102
278
if ($ schema = $ this ->option ($ key )) {
@@ -123,8 +299,36 @@ protected function resolveBuilders(): void
123
299
$ this ->target = $ this ->resolveBuilder ($ this ->target ());
124
300
}
125
301
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
+
126
320
protected function builder (string $ connection , string $ table ): QueryBuilder
127
321
{
128
322
return DB ::connection ($ connection )->table ($ table );
129
323
}
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
+ }
130
334
}
0 commit comments