lhm 1.2.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rubocop.yml +256 -0
  4. data/.travis.yml +5 -1
  5. data/CHANGELOG.md +26 -0
  6. data/README.md +87 -8
  7. data/Rakefile +6 -4
  8. data/bin/lhm-config.sh +7 -0
  9. data/bin/lhm-kill-queue +13 -15
  10. data/bin/lhm-spec-clobber.sh +5 -4
  11. data/bin/lhm-spec-grants.sh +2 -2
  12. data/bin/lhm-spec-setup-cluster.sh +2 -3
  13. data/gemfiles/ar-2.3_mysql.gemfile +2 -1
  14. data/gemfiles/ar-3.2_mysql.gemfile +1 -1
  15. data/gemfiles/ar-3.2_mysql2.gemfile +1 -1
  16. data/gemfiles/dm_mysql.gemfile +1 -1
  17. data/lhm.gemspec +7 -8
  18. data/lib/lhm/atomic_switcher.rb +2 -1
  19. data/lib/lhm/chunker.rb +51 -39
  20. data/lib/lhm/command.rb +4 -2
  21. data/lib/lhm/connection.rb +14 -2
  22. data/lib/lhm/entangler.rb +5 -5
  23. data/lib/lhm/intersection.rb +29 -16
  24. data/lib/lhm/invoker.rb +31 -10
  25. data/lib/lhm/locked_switcher.rb +6 -6
  26. data/lib/lhm/migration.rb +7 -5
  27. data/lib/lhm/migrator.rb +57 -9
  28. data/lib/lhm/printer.rb +54 -0
  29. data/lib/lhm/sql_helper.rb +4 -4
  30. data/lib/lhm/table.rb +12 -12
  31. data/lib/lhm/throttler/time.rb +29 -0
  32. data/lib/lhm/throttler.rb +32 -0
  33. data/lib/lhm/version.rb +1 -1
  34. data/lib/lhm.rb +71 -6
  35. data/spec/.lhm.example +1 -1
  36. data/spec/README.md +20 -13
  37. data/spec/fixtures/lines.ddl +7 -0
  38. data/spec/fixtures/permissions.ddl +5 -0
  39. data/spec/fixtures/tracks.ddl +5 -0
  40. data/spec/fixtures/users.ddl +4 -2
  41. data/spec/integration/atomic_switcher_spec.rb +7 -7
  42. data/spec/integration/chunker_spec.rb +11 -5
  43. data/spec/integration/cleanup_spec.rb +72 -0
  44. data/spec/integration/entangler_spec.rb +11 -11
  45. data/spec/integration/integration_helper.rb +49 -17
  46. data/spec/integration/lhm_spec.rb +157 -37
  47. data/spec/integration/locked_switcher_spec.rb +7 -7
  48. data/spec/integration/table_spec.rb +15 -17
  49. data/spec/test_helper.rb +28 -0
  50. data/spec/unit/atomic_switcher_spec.rb +6 -6
  51. data/spec/unit/chunker_spec.rb +95 -73
  52. data/spec/unit/datamapper_connection_spec.rb +1 -0
  53. data/spec/unit/entangler_spec.rb +19 -19
  54. data/spec/unit/intersection_spec.rb +27 -15
  55. data/spec/unit/lhm_spec.rb +29 -0
  56. data/spec/unit/locked_switcher_spec.rb +14 -14
  57. data/spec/unit/migration_spec.rb +10 -5
  58. data/spec/unit/migrator_spec.rb +53 -41
  59. data/spec/unit/printer_spec.rb +79 -0
  60. data/spec/unit/sql_helper_spec.rb +10 -10
  61. data/spec/unit/table_spec.rb +11 -11
  62. data/spec/unit/throttler_spec.rb +73 -0
  63. data/spec/unit/unit_helper.rb +1 -13
  64. metadata +63 -24
  65. data/spec/bootstrap.rb +0 -13
@@ -1,19 +1,13 @@
1
1
  # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
2
  # Schmidt
3
-
4
- require File.expand_path(File.dirname(__FILE__)) + "/../bootstrap"
5
-
3
+ require 'test_helper'
4
+ require 'yaml'
6
5
  begin
7
- require 'active_record'
8
- begin
9
- require 'mysql2'
10
- rescue LoadError
11
- require 'mysql'
12
- end
6
+ require 'active_support'
13
7
  rescue LoadError
14
- require 'dm-core'
15
- require 'dm-mysql-adapter'
16
8
  end
9
+ $password = YAML.load_file(File.expand_path(File.dirname(__FILE__)) + '/database.yml')['password'] rescue nil
10
+
17
11
  require 'lhm/table'
18
12
  require 'lhm/sql_helper'
19
13
  require 'lhm/connection'
@@ -42,14 +36,19 @@ module IntegrationHelper
42
36
  :host => '127.0.0.1',
43
37
  :database => 'lhm',
44
38
  :username => 'root',
45
- :port => port
39
+ :port => port,
40
+ :password => $password
46
41
  )
47
42
  adapter = ActiveRecord::Base.connection
48
43
  elsif defined?(DataMapper)
49
- adapter = DataMapper.setup(:default, "mysql://root@localhost:#{port}/lhm")
44
+ adapter = DataMapper.setup(:default, "mysql://root:#{$password}@localhost:#{port}/lhm")
50
45
  end
51
46
 
52
47
  Lhm.setup(adapter)
48
+ unless defined?(@@cleaned_up)
49
+ Lhm.cleanup(true)
50
+ @@cleaned_up = true
51
+ end
53
52
  @connection = Lhm::Connection.new(adapter)
54
53
  end
55
54
 
@@ -87,7 +86,6 @@ module IntegrationHelper
87
86
  connect_master!
88
87
  end
89
88
 
90
-
91
89
  yield block
92
90
 
93
91
  if master_slave_mode?
@@ -109,6 +107,10 @@ module IntegrationHelper
109
107
  table_read(fixture_name)
110
108
  end
111
109
 
110
+ def table_rename(from_name, to_name)
111
+ execute "rename table `#{ from_name }` to `#{ to_name }`"
112
+ end
113
+
112
114
  def table_read(fixture_name)
113
115
  Lhm::Table.parse(fixture_name, @connection)
114
116
  end
@@ -127,7 +129,7 @@ module IntegrationHelper
127
129
  end
128
130
 
129
131
  def count_all(table)
130
- query = "select count(*) from #{ table }"
132
+ query = "select count(*) from `#{ table }`"
131
133
  select_value(query).to_i
132
134
  end
133
135
 
@@ -141,7 +143,7 @@ module IntegrationHelper
141
143
  non_unique = type == :non_unique ? 1 : 0
142
144
 
143
145
  !!select_one(%Q<
144
- show indexes in #{ table_name }
146
+ show indexes in `#{ table_name }`
145
147
  where key_name = '#{ key_name }'
146
148
  and non_unique = #{ non_unique }
147
149
  >)
@@ -152,6 +154,36 @@ module IntegrationHelper
152
154
  #
153
155
 
154
156
  def master_slave_mode?
155
- !!ENV["MASTER_SLAVE"]
157
+ !!ENV['MASTER_SLAVE']
158
+ end
159
+
160
+ #
161
+ # Misc
162
+ #
163
+
164
+ def capture_stdout
165
+ out = StringIO.new
166
+ $stdout = out
167
+ yield
168
+ return out.string
169
+ ensure
170
+ $stdout = ::STDOUT
171
+ end
172
+
173
+ def simulate_failed_migration
174
+ Lhm::Entangler.class_eval do
175
+ alias_method :old_after, :after
176
+ def after
177
+ true
178
+ end
179
+ end
180
+
181
+ yield
182
+ ensure
183
+ Lhm::Entangler.class_eval do
184
+ undef_method :after
185
+ alias_method :after, :old_after
186
+ undef_method :old_after
187
+ end
156
188
  end
157
189
  end
@@ -3,33 +3,71 @@
3
3
 
4
4
  require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
5
 
6
- require 'lhm'
7
-
8
6
  describe Lhm do
9
7
  include IntegrationHelper
10
8
 
11
9
  before(:each) { connect_master! }
12
10
 
13
- describe "changes" do
11
+ describe 'changes' do
14
12
  before(:each) do
15
13
  table_create(:users)
14
+ table_create(:tracks)
15
+ table_create(:permissions)
16
+ end
17
+
18
+ describe 'when providing a subset of data to copy' do
19
+
20
+ before do
21
+ execute('insert into tracks set id = 13, public = 0')
22
+ 11.times { |n| execute("insert into tracks set id = #{n + 1}, public = 1") }
23
+ 11.times { |n| execute("insert into permissions set track_id = #{n + 1}") }
24
+
25
+ Lhm.change_table(:permissions, :atomic_switch => false) do |t|
26
+ t.filter('inner join tracks on tracks.`id` = permissions.`track_id` and tracks.`public` = 1')
27
+ end
28
+ end
29
+
30
+ describe 'when no additional data is inserted into the table' do
31
+
32
+ it 'migrates the existing data' do
33
+ slave do
34
+ count_all(:permissions).must_equal(11)
35
+ end
36
+ end
37
+ end
38
+
39
+ describe 'when additional data is inserted' do
40
+
41
+ before do
42
+ execute('insert into tracks set id = 14, public = 0')
43
+ execute('insert into tracks set id = 15, public = 1')
44
+ execute('insert into permissions set track_id = 14')
45
+ execute('insert into permissions set track_id = 15')
46
+ end
47
+
48
+ it 'migrates all data' do
49
+ slave do
50
+ count_all(:permissions).must_equal(13)
51
+ end
52
+ end
53
+ end
16
54
  end
17
55
 
18
- it "should add a column" do
56
+ it 'should add a column' do
19
57
  Lhm.change_table(:users, :atomic_switch => false) do |t|
20
58
  t.add_column(:logins, "INT(12) DEFAULT '0'")
21
59
  end
22
60
 
23
61
  slave do
24
- table_read(:users).columns["logins"].must_equal({
25
- :type => "int(12)",
26
- :is_nullable => "YES",
62
+ table_read(:users).columns['logins'].must_equal({
63
+ :type => 'int(12)',
64
+ :is_nullable => 'YES',
27
65
  :column_default => '0'
28
66
  })
29
67
  end
30
68
  end
31
69
 
32
- it "should copy all rows" do
70
+ it 'should copy all rows' do
33
71
  23.times { |n| execute("insert into users set reference = '#{ n }'") }
34
72
 
35
73
  Lhm.change_table(:users, :atomic_switch => false) do |t|
@@ -41,17 +79,17 @@ describe Lhm do
41
79
  end
42
80
  end
43
81
 
44
- it "should remove a column" do
82
+ it 'should remove a column' do
45
83
  Lhm.change_table(:users, :atomic_switch => false) do |t|
46
84
  t.remove_column(:comment)
47
85
  end
48
86
 
49
87
  slave do
50
- table_read(:users).columns["comment"].must_equal nil
88
+ table_read(:users).columns['comment'].must_equal nil
51
89
  end
52
90
  end
53
91
 
54
- it "should add an index" do
92
+ it 'should add an index' do
55
93
  Lhm.change_table(:users, :atomic_switch => false) do |t|
56
94
  t.add_index([:comment, :created_at])
57
95
  end
@@ -61,7 +99,7 @@ describe Lhm do
61
99
  end
62
100
  end
63
101
 
64
- it "should add an index with a custom name" do
102
+ it 'should add an index with a custom name' do
65
103
  Lhm.change_table(:users, :atomic_switch => false) do |t|
66
104
  t.add_index([:comment, :created_at], :my_index_name)
67
105
  end
@@ -71,7 +109,7 @@ describe Lhm do
71
109
  end
72
110
  end
73
111
 
74
- it "should add an index on a column with a reserved name" do
112
+ it 'should add an index on a column with a reserved name' do
75
113
  Lhm.change_table(:users, :atomic_switch => false) do |t|
76
114
  t.add_index(:group)
77
115
  end
@@ -81,7 +119,7 @@ describe Lhm do
81
119
  end
82
120
  end
83
121
 
84
- it "should add a unqiue index" do
122
+ it 'should add a unqiue index' do
85
123
  Lhm.change_table(:users, :atomic_switch => false) do |t|
86
124
  t.add_unique_index(:comment)
87
125
  end
@@ -91,7 +129,7 @@ describe Lhm do
91
129
  end
92
130
  end
93
131
 
94
- it "should remove an index" do
132
+ it 'should remove an index' do
95
133
  Lhm.change_table(:users, :atomic_switch => false) do |t|
96
134
  t.remove_index([:username, :created_at])
97
135
  end
@@ -101,62 +139,144 @@ describe Lhm do
101
139
  end
102
140
  end
103
141
 
104
- it "should remove an index with a custom name" do
142
+ it 'should remove an index with a custom name' do
143
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
144
+ t.remove_index([:username, :group])
145
+ end
146
+
147
+ slave do
148
+ index?(:users, :index_with_a_custom_name).must_equal(false)
149
+ end
150
+ end
151
+
152
+ it 'should remove an index with a custom name by name' do
105
153
  Lhm.change_table(:users, :atomic_switch => false) do |t|
106
- t.remove_index(:reference, :index_users_on_reference)
154
+ t.remove_index(:irrelevant_column_name, :index_with_a_custom_name)
107
155
  end
108
156
 
109
157
  slave do
110
- index?(:users, :index_users_on_reference).must_equal(false)
158
+ index?(:users, :index_with_a_custom_name).must_equal(false)
111
159
  end
112
160
  end
113
161
 
114
- it "should apply a ddl statement" do
162
+ it 'should apply a ddl statement' do
115
163
  Lhm.change_table(:users, :atomic_switch => false) do |t|
116
- t.ddl("alter table %s add column flag tinyint(1)" % t.name)
164
+ t.ddl('alter table %s add column flag tinyint(1)' % t.name)
117
165
  end
118
166
 
119
167
  slave do
120
- table_read(:users).columns["flag"].must_equal({
121
- :type => "tinyint(1)",
122
- :is_nullable => "YES",
168
+ table_read(:users).columns['flag'].must_equal({
169
+ :type => 'tinyint(1)',
170
+ :is_nullable => 'YES',
123
171
  :column_default => nil
124
172
  })
125
173
  end
126
174
  end
127
175
 
128
- it "should change a column" do
176
+ it 'should change a column' do
129
177
  Lhm.change_table(:users, :atomic_switch => false) do |t|
130
178
  t.change_column(:comment, "varchar(20) DEFAULT 'none' NOT NULL")
131
179
  end
132
180
 
133
181
  slave do
134
- table_read(:users).columns["comment"].must_equal({
135
- :type => "varchar(20)",
136
- :is_nullable => "NO",
137
- :column_default => "none"
182
+ table_read(:users).columns['comment'].must_equal({
183
+ :type => 'varchar(20)',
184
+ :is_nullable => 'NO',
185
+ :column_default => 'none'
138
186
  })
139
187
  end
140
188
  end
141
189
 
142
- it "should change the last column in a table" do
190
+ it 'should change the last column in a table' do
143
191
  table_create(:small_table)
144
192
 
145
193
  Lhm.change_table(:small_table, :atomic_switch => false) do |t|
146
- t.change_column(:id, "int(5)")
194
+ t.change_column(:id, 'int(5)')
195
+ end
196
+
197
+ slave do
198
+ table_read(:small_table).columns['id'].must_equal({
199
+ :type => 'int(5)',
200
+ :is_nullable => 'NO',
201
+ :column_default => '0'
202
+ })
203
+ end
204
+ end
205
+
206
+ it 'should rename a column' do
207
+ table_create(:users)
208
+
209
+ execute("INSERT INTO users (username) VALUES ('a user')")
210
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
211
+ t.rename_column(:username, :login)
212
+ end
213
+
214
+ slave do
215
+ table_data = table_read(:users)
216
+ table_data.columns['username'].must_equal(nil)
217
+ table_read(:users).columns['login'].must_equal({
218
+ :type => 'varchar(255)',
219
+ :is_nullable => 'YES',
220
+ :column_default => nil
221
+ })
222
+
223
+ # DM & AR versions of select_one return different structures. The
224
+ # real test is whether the data was copied
225
+ result = select_one('SELECT login from users')
226
+ result = result['login'] if result.respond_to?(:has_key?)
227
+ result.must_equal('a user')
228
+ end
229
+ end
230
+
231
+ it 'should rename a column with a default' do
232
+ table_create(:users)
233
+
234
+ execute("INSERT INTO users (username) VALUES ('a user')")
235
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
236
+ t.rename_column(:group, :fnord)
147
237
  end
148
238
 
149
239
  slave do
150
- table_read(:small_table).columns["id"].must_equal({
151
- :type => "int(5)",
152
- :is_nullable => "NO",
153
- :column_default => "0"
240
+ table_data = table_read(:users)
241
+ table_data.columns['group'].must_equal(nil)
242
+ table_read(:users).columns['fnord'].must_equal({
243
+ :type => 'varchar(255)',
244
+ :is_nullable => 'YES',
245
+ :column_default => 'Superfriends'
154
246
  })
247
+
248
+ # DM & AR versions of select_one return different structures. The
249
+ # real test is whether the data was copied
250
+ result = select_one('SELECT `fnord` from users')
251
+ result = result['fnord'] if result.respond_to?(:has_key?)
252
+ result.must_equal('Superfriends')
253
+ end
254
+ end
255
+
256
+ it 'works when mysql reserved words are used' do
257
+ table_create(:lines)
258
+ execute("insert into `lines` set id = 1, `between` = 'foo'")
259
+ execute("insert into `lines` set id = 2, `between` = 'bar'")
260
+
261
+ Lhm.change_table(:lines) do |t|
262
+ t.add_column('by', 'varchar(10)')
263
+ t.remove_column('lines')
264
+ t.add_index('by')
265
+ t.add_unique_index('between')
266
+ t.remove_index('by')
267
+ end
268
+
269
+ slave do
270
+ table_read(:lines).columns.must_include 'by'
271
+ table_read(:lines).columns.wont_include 'lines'
272
+ index_on_columns?(:lines, ['between'], :unique).must_equal true
273
+ index_on_columns?(:lines, ['by']).must_equal false
274
+ count_all(:lines).must_equal(2)
155
275
  end
156
276
  end
157
277
 
158
- describe "parallel" do
159
- it "should perserve inserts during migration" do
278
+ describe 'parallel' do
279
+ it 'should perserve inserts during migration' do
160
280
  50.times { |n| execute("insert into users set reference = '#{ n }'") }
161
281
 
162
282
  insert = Thread.new do
@@ -180,7 +300,7 @@ describe Lhm do
180
300
  end
181
301
  end
182
302
 
183
- it "should perserve deletes during migration" do
303
+ it 'should perserve deletes during migration' do
184
304
  50.times { |n| execute("insert into users set reference = '#{ n }'") }
185
305
 
186
306
  delete = Thread.new do
@@ -12,30 +12,30 @@ describe Lhm::LockedSwitcher do
12
12
 
13
13
  before(:each) { connect_master! }
14
14
 
15
- describe "switching" do
15
+ describe 'switching' do
16
16
  before(:each) do
17
- @origin = table_create("origin")
18
- @destination = table_create("destination")
17
+ @origin = table_create('origin')
18
+ @destination = table_create('destination')
19
19
  @migration = Lhm::Migration.new(@origin, @destination)
20
20
  end
21
21
 
22
- it "rename origin to archive" do
22
+ it 'rename origin to archive' do
23
23
  switcher = Lhm::LockedSwitcher.new(@migration, connection)
24
24
  switcher.run
25
25
 
26
26
  slave do
27
27
  table_exists?(@origin).must_equal true
28
- table_read(@migration.archive_name).columns.keys.must_include "origin"
28
+ table_read(@migration.archive_name).columns.keys.must_include 'origin'
29
29
  end
30
30
  end
31
31
 
32
- it "rename destination to origin" do
32
+ it 'rename destination to origin' do
33
33
  switcher = Lhm::LockedSwitcher.new(@migration, connection)
34
34
  switcher.run
35
35
 
36
36
  slave do
37
37
  table_exists?(@destination).must_equal false
38
- table_read(@origin.name).columns.keys.must_include "destination"
38
+ table_read(@origin.name).columns.keys.must_include 'destination'
39
39
  end
40
40
  end
41
41
  end
@@ -2,46 +2,44 @@
2
2
  # Schmidt
3
3
 
4
4
  require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
-
6
- require 'lhm'
7
5
  require 'lhm/table'
8
6
 
9
7
  describe Lhm::Table do
10
8
  include IntegrationHelper
11
9
 
12
10
  describe Lhm::Table::Parser do
13
- describe "create table parsing" do
11
+ describe 'create table parsing' do
14
12
  before(:each) do
15
13
  connect_master!
16
14
  @table = table_create(:users)
17
15
  end
18
16
 
19
- it "should parse table name in show create table" do
20
- @table.name.must_equal("users")
17
+ it 'should parse table name in show create table' do
18
+ @table.name.must_equal('users')
21
19
  end
22
20
 
23
- it "should parse primary key" do
24
- @table.pk.must_equal("id")
21
+ it 'should parse primary key' do
22
+ @table.pk.must_equal('id')
25
23
  end
26
24
 
27
- it "should parse column type in show create table" do
28
- @table.columns["username"][:type].must_equal("varchar(255)")
25
+ it 'should parse column type in show create table' do
26
+ @table.columns['username'][:type].must_equal('varchar(255)')
29
27
  end
30
28
 
31
- it "should parse column metadata" do
32
- @table.columns["username"][:column_default].must_equal nil
29
+ it 'should parse column metadata' do
30
+ @table.columns['username'][:column_default].must_equal nil
33
31
  end
34
32
 
35
- it "should parse indices" do
33
+ it 'should parse indices' do
36
34
  @table.
37
- indices["index_users_on_username_and_created_at"].
38
- must_equal(["username", "created_at"])
35
+ indices['index_users_on_username_and_created_at'].
36
+ must_equal(['username', 'created_at'])
39
37
  end
40
38
 
41
- it "should parse index" do
39
+ it 'should parse index' do
42
40
  @table.
43
- indices["index_users_on_reference"].
44
- must_equal(["reference"])
41
+ indices['index_users_on_reference'].
42
+ must_equal(['reference'])
45
43
  end
46
44
  end
47
45
  end
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
+ # Schmidt
3
+
4
+ require 'minitest/autorun'
5
+ require 'minitest/spec'
6
+ require 'minitest/mock'
7
+ require 'pathname'
8
+ require 'lhm'
9
+
10
+ $project = Pathname.new(File.dirname(__FILE__) + '/..').cleanpath
11
+ $spec = $project.join('spec')
12
+ $fixtures = $spec.join('fixtures')
13
+
14
+ begin
15
+ require 'active_record'
16
+ begin
17
+ require 'mysql2'
18
+ rescue LoadError
19
+ require 'mysql'
20
+ end
21
+ rescue LoadError
22
+ require 'dm-core'
23
+ require 'dm-mysql-adapter'
24
+ end
25
+
26
+ logger = Logger.new STDOUT
27
+ logger.level = Logger::WARN
28
+ Lhm.logger = logger
@@ -12,19 +12,19 @@ describe Lhm::AtomicSwitcher do
12
12
 
13
13
  before(:each) do
14
14
  @start = Time.now
15
- @origin = Lhm::Table.new("origin")
16
- @destination = Lhm::Table.new("destination")
15
+ @origin = Lhm::Table.new('origin')
16
+ @destination = Lhm::Table.new('destination')
17
17
  @migration = Lhm::Migration.new(@origin, @destination, @start)
18
18
  @switcher = Lhm::AtomicSwitcher.new(@migration, nil)
19
19
  end
20
20
 
21
- describe "atomic switch" do
22
- it "should perform a single atomic rename" do
21
+ describe 'atomic switch' do
22
+ it 'should perform a single atomic rename' do
23
23
  @switcher.
24
24
  statements.
25
25
  must_equal([
26
- "rename table `origin` to `#{ @migration.archive_name }`, " +
27
- "`destination` to `origin`"
26
+ "rename table `origin` to `#{ @migration.archive_name }`, " \
27
+ '`destination` to `origin`'
28
28
  ])
29
29
  end
30
30
  end