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
@@ -6,106 +6,128 @@ require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
6
6
  require 'lhm/table'
7
7
  require 'lhm/migration'
8
8
  require 'lhm/chunker'
9
+ require 'lhm/throttler'
9
10
 
10
11
  describe Lhm::Chunker do
11
12
  include UnitHelper
12
13
 
13
14
  before(:each) do
14
- @origin = Lhm::Table.new("origin")
15
- @destination = Lhm::Table.new("destination")
16
- @migration = Lhm::Migration.new(@origin, @destination)
17
- @chunker = Lhm::Chunker.new(@migration, nil, { :start => 1, :limit => 10 })
15
+ @origin = Lhm::Table.new('foo')
16
+ @destination = Lhm::Table.new('bar')
17
+ @migration = Lhm::Migration.new(@origin, @destination)
18
+ @connection = MiniTest::Mock.new
19
+ # This is a poor man's stub
20
+ @throttler = Object.new
21
+ def @throttler.run
22
+ # noop
23
+ end
24
+ def @throttler.stride
25
+ 1
26
+ end
27
+ @chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
28
+ :start => 1,
29
+ :limit => 10)
18
30
  end
19
31
 
20
- describe "copy into" do
21
- before(:each) do
22
- @origin.columns["secret"] = { :metadata => "VARCHAR(255)"}
23
- @destination.columns["secret"] = { :metadata => "VARCHAR(255)"}
24
- end
25
-
26
- it "should copy the correct range and column" do
27
- @chunker.copy(from = 1, to = 100).must_equal(
28
- "insert ignore into `destination` (`secret`) " +
29
- "select `secret` from `origin` " +
30
- "where `id` between 1 and 100"
31
- )
32
- end
33
- end
32
+ describe '#run' do
33
+ it 'chunks the result set according to the stride size' do
34
+ def @throttler.stride
35
+ 2
36
+ end
34
37
 
35
- describe "invalid" do
36
- before do
37
- @chunker = Lhm::Chunker.new(@migration, nil, { :start => 0, :limit => -1 })
38
- end
38
+ @connection.expect(:update, 2) do |stmt|
39
+ stmt.first =~ /between 1 and 2/
40
+ end
41
+ @connection.expect(:update, 2) do |stmt|
42
+ stmt.first =~ /between 3 and 4/
43
+ end
44
+ @connection.expect(:update, 2) do |stmt|
45
+ stmt.first =~ /between 5 and 6/
46
+ end
47
+ @connection.expect(:update, 2) do |stmt|
48
+ stmt.first =~ /between 7 and 8/
49
+ end
50
+ @connection.expect(:update, 2) do |stmt|
51
+ stmt.first =~ /between 9 and 10/
52
+ end
39
53
 
40
- it "should have zero chunks" do
41
- @chunker.traversable_chunks_size.must_equal 0
54
+ @chunker.run
55
+ @connection.verify
42
56
  end
43
57
 
44
- it "should not iterate" do
45
- @chunker.up_to do |bottom, top|
46
- raise "should not iterate"
58
+ it 'handles stride changes during execution' do
59
+ # roll our own stubbing
60
+ def @throttler.stride
61
+ @run_count ||= 0
62
+ @run_count = @run_count + 1
63
+ if @run_count > 1
64
+ 3
65
+ else
66
+ 2
67
+ end
47
68
  end
48
- end
49
- end
50
69
 
51
- describe "one" do
52
- before do
53
- @chunker = Lhm::Chunker.new(@migration, nil, {
54
- :stride => 100_000, :start => 1, :limit => 300_000
55
- })
56
- end
70
+ @connection.expect(:update, 2) do |stmt|
71
+ stmt.first =~ /between 1 and 2/
72
+ end
73
+ @connection.expect(:update, 2) do |stmt|
74
+ stmt.first =~ /between 3 and 5/
75
+ end
76
+ @connection.expect(:update, 2) do |stmt|
77
+ stmt.first =~ /between 6 and 8/
78
+ end
79
+ @connection.expect(:update, 2) do |stmt|
80
+ stmt.first =~ /between 9 and 10/
81
+ end
57
82
 
58
- it "should have one chunk" do
59
- @chunker.traversable_chunks_size.must_equal 3
83
+ @chunker.run
84
+ @connection.verify
60
85
  end
61
86
 
62
- it "should lower bound chunk on 1" do
63
- @chunker.bottom(chunk = 1).must_equal 1
64
- end
87
+ it 'correctly copies single record tables' do
88
+ @chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
89
+ :start => 1,
90
+ :limit => 1)
65
91
 
66
- it "should upper bound chunk on 100" do
67
- @chunker.top(chunk = 1).must_equal 100_000
68
- end
69
- end
92
+ @connection.expect(:update, 1) do |stmt|
93
+ stmt.first =~ /between 1 and 1/
94
+ end
70
95
 
71
- describe "two" do
72
- before do
73
- @chunker = Lhm::Chunker.new(@migration, nil, {
74
- :stride => 100_000, :start => 2, :limit => 150_000
75
- })
96
+ @chunker.run
97
+ @connection.verify
76
98
  end
77
99
 
78
- it "should have two chunks" do
79
- @chunker.traversable_chunks_size.must_equal 2
80
- end
100
+ it 'separates filter conditions from chunking conditions' do
101
+ @chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
102
+ :start => 1,
103
+ :limit => 2)
104
+ @connection.expect(:update, 1) do |stmt|
105
+ stmt.first =~ /where \(foo.created_at > '2013-07-10' or foo.baz = 'quux'\) and `foo`/
106
+ end
81
107
 
82
- it "should lower bound second chunk on 100_000" do
83
- @chunker.bottom(chunk = 2).must_equal 100_002
84
- end
108
+ def @migration.conditions
109
+ "where foo.created_at > '2013-07-10' or foo.baz = 'quux'"
110
+ end
85
111
 
86
- it "should upper bound second chunk on 150_000" do
87
- @chunker.top(chunk = 2).must_equal 150_000
112
+ @chunker.run
113
+ @connection.verify
88
114
  end
89
- end
90
115
 
91
- describe "iterating" do
92
- before do
93
- @chunker = Lhm::Chunker.new(@migration, nil, {
94
- :stride => 100, :start => 53, :limit => 121
95
- })
96
- end
116
+ it "doesn't mess with inner join filters" do
117
+ @chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
118
+ :start => 1,
119
+ :limit => 2)
120
+ @connection.expect(:update, 1) do |stmt|
121
+ puts stmt
122
+ stmt.first =~ /inner join bar on foo.id = bar.foo_id and/
123
+ end
97
124
 
98
- it "should iterate" do
99
- @chunker.up_to do |bottom, top|
100
- bottom.must_equal 53
101
- top.must_equal 121
125
+ def @migration.conditions
126
+ 'inner join bar on foo.id = bar.foo_id'
102
127
  end
103
- end
104
- end
105
128
 
106
- describe "throttling" do
107
- it "should default to 100 milliseconds" do
108
- @chunker.throttle_seconds.must_equal 0.1
129
+ @chunker.run
130
+ @connection.verify
109
131
  end
110
132
  end
111
133
  end
@@ -11,6 +11,7 @@ if defined?(DataMapper)
11
11
  before do
12
12
  data_mapper.expect :is_a?, true, [DataMapper::Adapters::AbstractAdapter]
13
13
  data_mapper.expect :options, options
14
+ data_mapper.expect :options, options
14
15
  end
15
16
 
16
17
  after do
@@ -11,44 +11,44 @@ describe Lhm::Entangler do
11
11
  include UnitHelper
12
12
 
13
13
  before(:each) do
14
- @origin = Lhm::Table.new("origin")
15
- @destination = Lhm::Table.new("destination")
14
+ @origin = Lhm::Table.new('origin')
15
+ @destination = Lhm::Table.new('destination')
16
16
  @migration = Lhm::Migration.new(@origin, @destination)
17
17
  @entangler = Lhm::Entangler.new(@migration)
18
18
  end
19
19
 
20
- describe "activation" do
20
+ describe 'activation' do
21
21
  before(:each) do
22
- @origin.columns["info"] = { :type => "varchar(255)" }
23
- @origin.columns["tags"] = { :type => "varchar(255)" }
22
+ @origin.columns['info'] = { :type => 'varchar(255)' }
23
+ @origin.columns['tags'] = { :type => 'varchar(255)' }
24
24
 
25
- @destination.columns["info"] = { :type => "varchar(255)" }
26
- @destination.columns["tags"] = { :type => "varchar(255)" }
25
+ @destination.columns['info'] = { :type => 'varchar(255)' }
26
+ @destination.columns['tags'] = { :type => 'varchar(255)' }
27
27
  end
28
28
 
29
- it "should create insert trigger to destination table" do
29
+ it 'should create insert trigger to destination table' do
30
30
  ddl = %Q{
31
31
  create trigger `lhmt_ins_origin`
32
32
  after insert on `origin` for each row
33
33
  replace into `destination` (`info`, `tags`) /* large hadron migration */
34
- values (NEW.`info`, NEW.`tags`)
34
+ values (`NEW`.`info`, `NEW`.`tags`)
35
35
  }
36
36
 
37
37
  @entangler.entangle.must_include strip(ddl)
38
38
  end
39
39
 
40
- it "should create an update trigger to the destination table" do
40
+ it 'should create an update trigger to the destination table' do
41
41
  ddl = %Q{
42
42
  create trigger `lhmt_upd_origin`
43
43
  after update on `origin` for each row
44
44
  replace into `destination` (`info`, `tags`) /* large hadron migration */
45
- values (NEW.`info`, NEW.`tags`)
45
+ values (`NEW`.`info`, `NEW`.`tags`)
46
46
  }
47
47
 
48
48
  @entangler.entangle.must_include strip(ddl)
49
49
  end
50
50
 
51
- it "should create a delete trigger to the destination table" do
51
+ it 'should create a delete trigger to the destination table' do
52
52
  ddl = %Q{
53
53
  create trigger `lhmt_del_origin`
54
54
  after delete on `origin` for each row
@@ -60,17 +60,17 @@ describe Lhm::Entangler do
60
60
  end
61
61
  end
62
62
 
63
- describe "removal" do
64
- it "should remove insert trigger" do
65
- @entangler.untangle.must_include("drop trigger if exists `lhmt_ins_origin`")
63
+ describe 'removal' do
64
+ it 'should remove insert trigger' do
65
+ @entangler.untangle.must_include('drop trigger if exists `lhmt_ins_origin`')
66
66
  end
67
67
 
68
- it "should remove update trigger" do
69
- @entangler.untangle.must_include("drop trigger if exists `lhmt_upd_origin`")
68
+ it 'should remove update trigger' do
69
+ @entangler.untangle.must_include('drop trigger if exists `lhmt_upd_origin`')
70
70
  end
71
71
 
72
- it "should remove delete trigger" do
73
- @entangler.untangle.must_include("drop trigger if exists `lhmt_del_origin`")
72
+ it 'should remove delete trigger' do
73
+ @entangler.untangle.must_include('drop trigger if exists `lhmt_del_origin`')
74
74
  end
75
75
  end
76
76
  end
@@ -9,31 +9,43 @@ require 'lhm/migrator'
9
9
  describe Lhm::Intersection do
10
10
  include UnitHelper
11
11
 
12
- it "should not have dropped changes" do
13
- origin = Lhm::Table.new("origin")
14
- origin.columns["dropped"] = varchar
15
- origin.columns["retained"] = varchar
12
+ it 'should not have dropped changes' do
13
+ origin = Lhm::Table.new('origin')
14
+ origin.columns['dropped'] = varchar
15
+ origin.columns['retained'] = varchar
16
16
 
17
- destination = Lhm::Table.new("destination")
18
- destination.columns["retained"] = varchar
17
+ destination = Lhm::Table.new('destination')
18
+ destination.columns['retained'] = varchar
19
19
 
20
20
  intersection = Lhm::Intersection.new(origin, destination)
21
- intersection.common.include?("dropped").must_equal(false)
21
+ intersection.destination.include?('dropped').must_equal(false)
22
22
  end
23
23
 
24
- it "should have unchanged columns" do
25
- origin = Lhm::Table.new("origin")
26
- origin.columns["dropped"] = varchar
27
- origin.columns["retained"] = varchar
24
+ it 'should have unchanged columns' do
25
+ origin = Lhm::Table.new('origin')
26
+ origin.columns['dropped'] = varchar
27
+ origin.columns['retained'] = varchar
28
28
 
29
- destination = Lhm::Table.new("destination")
30
- destination.columns["retained"] = varchar
29
+ destination = Lhm::Table.new('destination')
30
+ destination.columns['retained'] = varchar
31
31
 
32
32
  intersection = Lhm::Intersection.new(origin, destination)
33
- intersection.common.must_equal(["retained"])
33
+ intersection.destination.must_equal(['retained'])
34
+ end
35
+
36
+ it 'should have renamed columns' do
37
+ origin = Lhm::Table.new('origin')
38
+ origin.columns['old_name'] = varchar
39
+
40
+ destination = Lhm::Table.new('destination')
41
+ destination.columns['new_name'] = varchar
42
+
43
+ intersection = Lhm::Intersection.new(origin, destination, { 'old_name' => 'new_name' })
44
+ intersection.origin.must_equal(['old_name'])
45
+ intersection.destination.must_equal(['new_name'])
34
46
  end
35
47
 
36
48
  def varchar
37
- { :metadata => "VARCHAR(255)"}
49
+ { :metadata => 'VARCHAR(255)' }
38
50
  end
39
51
  end
@@ -0,0 +1,29 @@
1
+ # Copyright (c) 2011 - 2013, SoundCloud Ltd.
2
+
3
+ require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
4
+
5
+ describe Lhm do
6
+
7
+ before(:each) do
8
+ Lhm.remove_class_variable :@@logger if Lhm.class_variable_defined? :@@logger
9
+ end
10
+
11
+ describe 'logger' do
12
+
13
+ it 'should use the default parameters if no logger explicitly set' do
14
+ Lhm.logger.must_be_kind_of Logger
15
+ Lhm.logger.level.must_equal Logger::INFO
16
+ Lhm.logger.instance_eval { @logdev }.dev.must_equal STDOUT
17
+ end
18
+
19
+ it 'should use s new logger if set' do
20
+ l = Logger.new('omg.ponies')
21
+ l.level = Logger::ERROR
22
+ Lhm.logger = l
23
+
24
+ Lhm.logger.level.must_equal Logger::ERROR
25
+ Lhm.logger.instance_eval { @logdev }.dev.must_be_kind_of File
26
+ Lhm.logger.instance_eval { @logdev }.dev.path.must_equal 'omg.ponies'
27
+ end
28
+ end
29
+ end
@@ -12,39 +12,39 @@ describe Lhm::LockedSwitcher 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::LockedSwitcher.new(@migration, nil)
19
19
  end
20
20
 
21
- describe "uncommitted" do
22
- it "should disable autocommit first" do
21
+ describe 'uncommitted' do
22
+ it 'should disable autocommit first' do
23
23
  @switcher.
24
24
  statements[0..1].
25
25
  must_equal([
26
- "set @lhm_auto_commit = @@session.autocommit",
27
- "set session autocommit = 0"
26
+ 'set @lhm_auto_commit = @@session.autocommit',
27
+ 'set session autocommit = 0'
28
28
  ])
29
29
  end
30
30
 
31
- it "should reapply original autocommit settings at the end" do
31
+ it 'should reapply original autocommit settings at the end' do
32
32
  @switcher.
33
33
  statements[-1].
34
- must_equal("set session autocommit = @lhm_auto_commit")
34
+ must_equal('set session autocommit = @lhm_auto_commit')
35
35
  end
36
36
  end
37
37
 
38
- describe "switch" do
39
- it "should lock origin and destination table, switch, commit and unlock" do
38
+ describe 'switch' do
39
+ it 'should lock origin and destination table, switch, commit and unlock' do
40
40
  @switcher.
41
41
  switch.
42
42
  must_equal([
43
- "lock table `origin` write, `destination` write",
43
+ 'lock table `origin` write, `destination` write',
44
44
  "alter table `origin` rename `#{ @migration.archive_name }`",
45
- "alter table `destination` rename `origin`",
46
- "commit",
47
- "unlock tables"
45
+ 'alter table `destination` rename `origin`',
46
+ 'commit',
47
+ 'unlock tables'
48
48
  ])
49
49
  end
50
50
  end
@@ -11,13 +11,18 @@ describe Lhm::Migration do
11
11
 
12
12
  before(:each) do
13
13
  @start = Time.now
14
- @origin = Lhm::Table.new("origin")
15
- @destination = Lhm::Table.new("destination")
16
- @migration = Lhm::Migration.new(@origin, @destination, @start)
14
+ @origin = Lhm::Table.new('origin')
15
+ @destination = Lhm::Table.new('destination')
16
+ @migration = Lhm::Migration.new(@origin, @destination, nil, @start)
17
17
  end
18
18
 
19
- it "should name archive" do
20
- stamp = "%Y_%m_%d_%H_%M_%S_#{ "%03d" % (@start.usec / 1000) }"
19
+ it 'should name archive' do
20
+ stamp = "%Y_%m_%d_%H_%M_%S_#{ '%03d' % (@start.usec / 1000) }"
21
21
  @migration.archive_name.must_equal "lhma_#{ @start.strftime(stamp) }_origin"
22
22
  end
23
+
24
+ it 'should limit table name to 64 characters' do
25
+ migration = Lhm::Migration.new(OpenStruct.new(:name => 'a_very_very_long_table_name_that_should_make_the_LHMA_table_go_over_64_chars'), nil)
26
+ migration.archive_name.size == 64
27
+ end
23
28
  end
@@ -10,125 +10,137 @@ describe Lhm::Migrator do
10
10
  include UnitHelper
11
11
 
12
12
  before(:each) do
13
- @table = Lhm::Table.new("alt")
13
+ @table = Lhm::Table.new('alt')
14
14
  @creator = Lhm::Migrator.new(@table)
15
15
  end
16
16
 
17
- describe "index changes" do
18
- it "should add an index" do
17
+ describe 'index changes' do
18
+ it 'should add an index' do
19
19
  @creator.add_index(:a)
20
20
 
21
21
  @creator.statements.must_equal([
22
- "create index `index_alt_on_a` on `lhmn_alt` (`a`)"
22
+ 'create index `index_alt_on_a` on `lhmn_alt` (`a`)'
23
23
  ])
24
24
  end
25
25
 
26
- it "should add a composite index" do
26
+ it 'should add a composite index' do
27
27
  @creator.add_index([:a, :b])
28
28
 
29
29
  @creator.statements.must_equal([
30
- "create index `index_alt_on_a_and_b` on `lhmn_alt` (`a`, `b`)"
30
+ 'create index `index_alt_on_a_and_b` on `lhmn_alt` (`a`, `b`)'
31
31
  ])
32
32
  end
33
33
 
34
- it "should add an index with prefix length" do
35
- @creator.add_index(["a(10)", "b"])
34
+ it 'should add an index with prefix length' do
35
+ @creator.add_index(['a(10)', 'b'])
36
36
 
37
37
  @creator.statements.must_equal([
38
- "create index `index_alt_on_a_and_b` on `lhmn_alt` (`a`(10), `b`)"
38
+ 'create index `index_alt_on_a_and_b` on `lhmn_alt` (`a`(10), `b`)'
39
39
  ])
40
40
  end
41
41
 
42
- it "should add an index with a custom name" do
42
+ it 'should add an index with a custom name' do
43
43
  @creator.add_index([:a, :b], :custom_index_name)
44
44
 
45
45
  @creator.statements.must_equal([
46
- "create index `custom_index_name` on `lhmn_alt` (`a`, `b`)"
46
+ 'create index `custom_index_name` on `lhmn_alt` (`a`, `b`)'
47
47
  ])
48
48
  end
49
49
 
50
- it "should add a unique index" do
51
- @creator.add_unique_index(["a(5)", :b])
50
+ it 'should raise an error when the index name is not a string or symbol' do
51
+ assert_raises ArgumentError do
52
+ @creator.add_index([:a, :b], :name => :custom_index_name)
53
+ end
54
+ end
55
+
56
+ it 'should add a unique index' do
57
+ @creator.add_unique_index(['a(5)', :b])
52
58
 
53
59
  @creator.statements.must_equal([
54
- "create unique index `index_alt_on_a_and_b` on `lhmn_alt` (`a`(5), `b`)"
60
+ 'create unique index `index_alt_on_a_and_b` on `lhmn_alt` (`a`(5), `b`)'
55
61
  ])
56
62
  end
57
63
 
58
- it "should add a unique index with a custom name" do
64
+ it 'should add a unique index with a custom name' do
59
65
  @creator.add_unique_index([:a, :b], :custom_index_name)
60
66
 
61
67
  @creator.statements.must_equal([
62
- "create unique index `custom_index_name` on `lhmn_alt` (`a`, `b`)"
68
+ 'create unique index `custom_index_name` on `lhmn_alt` (`a`, `b`)'
63
69
  ])
64
70
  end
65
71
 
66
- it "should remove an index" do
67
- @creator.remove_index(["b", "a"])
72
+ it 'should raise an error when the unique index name is not a string or symbol' do
73
+ assert_raises ArgumentError do
74
+ @creator.add_unique_index([:a, :b], :name => :custom_index_name)
75
+ end
76
+ end
77
+
78
+ it 'should remove an index' do
79
+ @creator.remove_index(['b', 'a'])
68
80
 
69
81
  @creator.statements.must_equal([
70
- "drop index `index_alt_on_b_and_a` on `lhmn_alt`"
82
+ 'drop index `index_alt_on_b_and_a` on `lhmn_alt`'
71
83
  ])
72
84
  end
73
85
 
74
- it "should remove an index with a custom name" do
86
+ it 'should remove an index with a custom name' do
75
87
  @creator.remove_index([:a, :b], :custom_index_name)
76
88
 
77
89
  @creator.statements.must_equal([
78
- "drop index `custom_index_name` on `lhmn_alt`"
90
+ 'drop index `custom_index_name` on `lhmn_alt`'
79
91
  ])
80
92
  end
81
93
  end
82
94
 
83
- describe "column changes" do
84
- it "should add a column" do
85
- @creator.add_column("logins", "INT(12)")
95
+ describe 'column changes' do
96
+ it 'should add a column' do
97
+ @creator.add_column('logins', 'INT(12)')
86
98
 
87
99
  @creator.statements.must_equal([
88
- "alter table `lhmn_alt` add column `logins` INT(12)"
100
+ 'alter table `lhmn_alt` add column `logins` INT(12)'
89
101
  ])
90
102
  end
91
103
 
92
- it "should remove a column" do
93
- @creator.remove_column("logins")
104
+ it 'should remove a column' do
105
+ @creator.remove_column('logins')
94
106
 
95
107
  @creator.statements.must_equal([
96
- "alter table `lhmn_alt` drop `logins`"
108
+ 'alter table `lhmn_alt` drop `logins`'
97
109
  ])
98
110
  end
99
111
 
100
- it "should change a column" do
101
- @creator.change_column("logins", "INT(11)")
112
+ it 'should change a column' do
113
+ @creator.change_column('logins', 'INT(11)')
102
114
 
103
115
  @creator.statements.must_equal([
104
- "alter table `lhmn_alt` modify column `logins` INT(11)"
116
+ 'alter table `lhmn_alt` modify column `logins` INT(11)'
105
117
  ])
106
118
  end
107
119
  end
108
120
 
109
- describe "direct changes" do
110
- it "should accept a ddl statement" do
111
- ddl = @creator.ddl("alter table `%s` add column `f` tinyint(1)" % @creator.name)
121
+ describe 'direct changes' do
122
+ it 'should accept a ddl statement' do
123
+ ddl = @creator.ddl('alter table `%s` add column `f` tinyint(1)' % @creator.name)
112
124
 
113
125
  @creator.statements.must_equal([
114
- "alter table `lhmn_alt` add column `f` tinyint(1)"
126
+ 'alter table `lhmn_alt` add column `f` tinyint(1)'
115
127
  ])
116
128
  end
117
129
  end
118
130
 
119
- describe "multiple changes" do
120
- it "should add two columns" do
121
- @creator.add_column("first", "VARCHAR(64)")
122
- @creator.add_column("last", "VARCHAR(64)")
131
+ describe 'multiple changes' do
132
+ it 'should add two columns' do
133
+ @creator.add_column('first', 'VARCHAR(64)')
134
+ @creator.add_column('last', 'VARCHAR(64)')
123
135
  @creator.statements.length.must_equal(2)
124
136
 
125
137
  @creator.
126
138
  statements[0].
127
- must_equal("alter table `lhmn_alt` add column `first` VARCHAR(64)")
139
+ must_equal('alter table `lhmn_alt` add column `first` VARCHAR(64)')
128
140
 
129
141
  @creator.
130
142
  statements[1].
131
- must_equal("alter table `lhmn_alt` add column `last` VARCHAR(64)")
143
+ must_equal('alter table `lhmn_alt` add column `last` VARCHAR(64)')
132
144
  end
133
145
  end
134
146
  end