lhm 2.0.0 → 2.1.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.
@@ -0,0 +1,56 @@
1
+ module Lhm
2
+ module Printer
3
+
4
+ class Output
5
+ def write(message)
6
+ print message
7
+ end
8
+ end
9
+
10
+ class Base
11
+
12
+ def initialize
13
+ @output = Output.new
14
+ end
15
+ end
16
+
17
+ class Percentage < Base
18
+
19
+ def initialize
20
+ super
21
+ @max_length = 0
22
+ end
23
+
24
+ def notify(lowest, highest)
25
+ return if !highest || highest == 0
26
+ message = "%.2f%% (#{lowest}/#{highest}) complete" % (lowest.to_f / highest * 100.0)
27
+ write(message)
28
+ end
29
+
30
+ def end
31
+ write("100% complete")
32
+ @output.write "\n"
33
+ end
34
+
35
+ private
36
+ def write(message)
37
+ if (extra = @max_length - message.length) < 0
38
+ @max_length = message.length
39
+ extra = 0
40
+ end
41
+
42
+ @output.write "\r#{message}" + (" " * extra)
43
+ end
44
+ end
45
+
46
+ class Dot < Base
47
+ def notify(lowest = nil, highest = nil)
48
+ @output.write "."
49
+ end
50
+
51
+ def end
52
+ @output.write "\n"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ require 'lhm/throttler/time'
2
+
3
+ module Lhm
4
+ module Throttler
5
+ CLASSES = { :time_throttler => Throttler::Time }
6
+
7
+ def throttler
8
+ @throttler ||= Throttler::Time.new
9
+ end
10
+
11
+ def setup_throttler(type, options = {})
12
+ @throttler = Factory.create_throttler(type, options)
13
+ end
14
+
15
+ class Factory
16
+ def self.create_throttler(type, options = {})
17
+ case type
18
+ when Lhm::Command
19
+ type
20
+ when Symbol
21
+ CLASSES[type].new(options)
22
+ when String
23
+ CLASSES[type.to_sym].new(options)
24
+ when Class
25
+ type.new(options)
26
+ else
27
+ raise ArgumentError, 'type argument must be a Symbol, String or Class'
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ module Lhm
2
+ module Throttler
3
+ class Time
4
+ include Command
5
+
6
+ DEFAULT_TIMEOUT = 0.1
7
+ DEFAULT_STRIDE = 40_000
8
+
9
+ attr_accessor :timeout_seconds
10
+ attr_accessor :stride
11
+
12
+ def initialize(options = {})
13
+ @timeout_seconds = options[:delay] || DEFAULT_TIMEOUT
14
+ @stride = options[:stride] || DEFAULT_STRIDE
15
+ end
16
+
17
+ def execute
18
+ sleep timeout_seconds
19
+ end
20
+ end
21
+
22
+ class LegacyTime < Time
23
+ def initialize(timeout, stride)
24
+ @timeout_seconds = timeout / 1000.0
25
+ @stride = stride
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,5 +2,5 @@
2
2
  # Schmidt
3
3
 
4
4
  module Lhm
5
- VERSION = "2.0.0"
5
+ VERSION = "2.1.0"
6
6
  end
@@ -1,4 +1,4 @@
1
1
  mysqldir=/usr/local/mysql
2
- basedir=/opt/lhm-cluster
2
+ basedir=~/lhm-cluster
3
3
  master_port=3306
4
4
  slave_port=3307
@@ -1,47 +1,54 @@
1
- Preparing for master slave integration tests
2
- --------------------------------------------
1
+ # Preparing for master slave integration tests
3
2
 
4
- # configuration
3
+ ## Configuration
5
4
 
6
5
  create ~/.lhm:
7
6
 
8
7
  mysqldir=/usr/local/mysql
9
- basedir=/opt/lhm-cluster
8
+ basedir=~/lhm-cluster
10
9
  master_port=3306
11
10
  slave_port=3307
12
11
 
13
12
  mysqldir specifies the location of your mysql install. basedir is the
14
13
  directory master and slave databases will get installed into.
15
14
 
16
- # setup
15
+ ## Automatic setup
16
+
17
+ ### Run
18
+
19
+ bin/lhm-spec-clobber.sh
17
20
 
18
21
  You can set the integration specs up to run against a master slave setup by
19
- running the included `bin/lhm-spec-clobber.sh` script. this deletes the configured
20
- lhm master slave setup and reinstalls and configures a master slave setup.
22
+ running the included that. This deletes the configured lhm master slave setup and reinstalls and configures a master slave setup.
21
23
 
22
24
  Follow the manual instructions if you want more control over this process.
23
25
 
24
- # manual setup
26
+ ## Manual setup
25
27
 
26
- ## set up instances
28
+ ### set up instances
27
29
 
28
30
  bin/lhm-spec-setup-cluster.sh
29
31
 
30
- ## start instances
32
+ ### start instances
31
33
 
32
34
  basedir=/opt/lhm-luster
33
35
  mysqld --defaults-file="$basedir/master/my.cnf"
34
36
  mysqld --defaults-file="$basedir/slave/my.cnf"
35
37
 
36
- ## run the grants
38
+ ### run the grants
37
39
 
38
40
  bin/lhm-spec-grants.sh
39
41
 
40
42
  ## run specs
41
43
 
42
- To run specs in slave mode, set the SLAVE=1 when running tests:
44
+ Setup the dependency gems
45
+
46
+ export BUNDLE_GEMFILE=gemfiles/ar-3.2_mysql2.gemfile
47
+ bundle install
48
+
49
+ To run specs in slave mode, set the MASTER_SLAVE=1 when running tests:
43
50
 
44
- MASTER_SLAVE=1 rake specs
51
+ MASTER_SLAVE=1 bundle exec rake specs
45
52
 
46
53
  # connecting
47
54
 
@@ -0,0 +1,7 @@
1
+ CREATE TABLE `lines` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `between` varchar(10),
4
+ `lines` int(11),
5
+ `key` varchar(10),
6
+ PRIMARY KEY (`id`)
7
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -2,8 +2,6 @@
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
  require 'lhm/migration'
9
7
 
@@ -22,11 +20,19 @@ describe Lhm::Chunker do
22
20
  it "should copy 23 rows from origin to destination" do
23
21
  23.times { |n| execute("insert into origin set id = '#{ n * n + 23 }'") }
24
22
 
25
- Lhm::Chunker.new(@migration, connection, { :stride => 100 }).run
23
+ printer = MiniTest::Mock.new
24
+ 5.times { printer.expect(:notify, :return_value, [Fixnum, Fixnum]) }
25
+ printer.expect(:end, :return_value, [])
26
+
27
+ Lhm::Chunker.new(
28
+ @migration, connection, { :throttler => Lhm::Throttler::Time.new(:stride => 100), :printer => printer }
29
+ ).run
26
30
 
27
31
  slave do
28
32
  count_all(@destination.name).must_equal(23)
29
33
  end
34
+
35
+ printer.verify
30
36
  end
31
37
  end
32
38
  end
@@ -3,8 +3,6 @@
3
3
 
4
4
  require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
5
 
6
- require 'lhm'
7
-
8
6
  describe Lhm, "cleanup" do
9
7
  include IntegrationHelper
10
8
  before(:each) { connect_master! }
@@ -12,12 +10,18 @@ describe Lhm, "cleanup" do
12
10
  describe "changes" do
13
11
  before(:each) do
14
12
  table_create(:users)
15
- Lhm.change_table(:users, :atomic_switch => false) do |t|
16
- t.add_column(:logins, "INT(12) DEFAULT '0'")
17
- t.add_index(:logins)
13
+ simulate_failed_migration do
14
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
15
+ t.add_column(:logins, "INT(12) DEFAULT '0'")
16
+ t.add_index(:logins)
17
+ end
18
18
  end
19
19
  end
20
20
 
21
+ after(:each) do
22
+ Lhm.cleanup(true)
23
+ end
24
+
21
25
  it "should show temporary tables" do
22
26
  output = capture_stdout do
23
27
  Lhm.cleanup
@@ -26,6 +30,16 @@ describe Lhm, "cleanup" do
26
30
  output.must_match(/lhma_[0-9_]*_users/)
27
31
  end
28
32
 
33
+ it "should show temporary triggers" do
34
+ output = capture_stdout do
35
+ Lhm.cleanup
36
+ end
37
+ output.must_include("Existing LHM triggers")
38
+ output.must_include("lhmt_ins_users")
39
+ output.must_include("lhmt_del")
40
+ output.must_include("lhmt_upd_users")
41
+ end
42
+
29
43
  it "should delete temporary tables" do
30
44
  Lhm.cleanup(true).must_equal(true)
31
45
  Lhm.cleanup.must_equal(true)
@@ -1,19 +1,9 @@
1
1
  # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
2
  # Schmidt
3
+ require 'test_helper'
4
+ require 'yaml'
5
+ $password = YAML.load_file(File.expand_path(File.dirname(__FILE__)) + "/database.yml")["password"] rescue nil
3
6
 
4
- require File.expand_path(File.dirname(__FILE__)) + "/../bootstrap"
5
-
6
- begin
7
- require 'active_record'
8
- begin
9
- require 'mysql2'
10
- rescue LoadError
11
- require 'mysql'
12
- end
13
- rescue LoadError
14
- require 'dm-core'
15
- require 'dm-mysql-adapter'
16
- end
17
7
  require 'lhm/table'
18
8
  require 'lhm/sql_helper'
19
9
  require 'lhm/connection'
@@ -42,11 +32,12 @@ module IntegrationHelper
42
32
  :host => '127.0.0.1',
43
33
  :database => 'lhm',
44
34
  :username => 'root',
45
- :port => port
35
+ :port => port,
36
+ :password => $password
46
37
  )
47
38
  adapter = ActiveRecord::Base.connection
48
39
  elsif defined?(DataMapper)
49
- adapter = DataMapper.setup(:default, "mysql://root@localhost:#{port}/lhm")
40
+ adapter = DataMapper.setup(:default, "mysql://root:#{$password}@localhost:#{port}/lhm")
50
41
  end
51
42
 
52
43
  Lhm.setup(adapter)
@@ -131,7 +122,7 @@ module IntegrationHelper
131
122
  end
132
123
 
133
124
  def count_all(table)
134
- query = "select count(*) from #{ table }"
125
+ query = "select count(*) from `#{ table }`"
135
126
  select_value(query).to_i
136
127
  end
137
128
 
@@ -145,7 +136,7 @@ module IntegrationHelper
145
136
  non_unique = type == :non_unique ? 1 : 0
146
137
 
147
138
  !!select_one(%Q<
148
- show indexes in #{ table_name }
139
+ show indexes in `#{ table_name }`
149
140
  where key_name = '#{ key_name }'
150
141
  and non_unique = #{ non_unique }
151
142
  >)
@@ -162,7 +153,7 @@ module IntegrationHelper
162
153
  #
163
154
  # Misc
164
155
  #
165
-
156
+
166
157
  def capture_stdout
167
158
  out = StringIO.new
168
159
  $stdout = out
@@ -171,4 +162,21 @@ module IntegrationHelper
171
162
  ensure
172
163
  $stdout = ::STDOUT
173
164
  end
165
+
166
+ def simulate_failed_migration
167
+ Lhm::Entangler.class_eval do
168
+ alias_method :old_after, :after
169
+ def after
170
+ true
171
+ end
172
+ end
173
+
174
+ yield
175
+ ensure
176
+ Lhm::Entangler.class_eval do
177
+ undef_method :after
178
+ alias_method :after, :old_after
179
+ undef_method :old_after
180
+ end
181
+ end
174
182
  end
@@ -3,8 +3,6 @@
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
 
@@ -205,6 +203,28 @@ describe Lhm do
205
203
  end
206
204
  end
207
205
 
206
+ it "works when mysql reserved words are used" do
207
+ table_create(:lines)
208
+ execute("insert into `lines` set id = 1, `between` = 'foo'")
209
+ execute("insert into `lines` set id = 2, `between` = 'bar'")
210
+
211
+ Lhm.change_table(:lines) do |t|
212
+ t.add_column('by', 'varchar(10)')
213
+ t.remove_column('lines')
214
+ t.add_index('by')
215
+ t.add_unique_index('between')
216
+ t.remove_index('by')
217
+ end
218
+
219
+ slave do
220
+ table_read(:lines).columns.must_include 'by'
221
+ table_read(:lines).columns.wont_include 'lines'
222
+ index_on_columns?(:lines, ['between'], :unique).must_equal true
223
+ index_on_columns?(:lines, ['by']).must_equal false
224
+ count_all(:lines).must_equal(2)
225
+ end
226
+ end
227
+
208
228
  describe "parallel" do
209
229
  it "should perserve inserts during migration" do
210
230
  50.times { |n| execute("insert into users set reference = '#{ n }'") }
@@ -2,8 +2,6 @@
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
@@ -1,13 +1,28 @@
1
1
  # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
2
  # Schmidt
3
3
 
4
- require 'minitest/spec'
5
4
  require 'minitest/autorun'
5
+ require 'minitest/spec'
6
6
  require 'minitest/mock'
7
7
  require "pathname"
8
+ require "lhm"
8
9
 
9
10
  $project = Pathname.new(File.dirname(__FILE__) + '/..').cleanpath
10
11
  $spec = $project.join("spec")
11
12
  $fixtures = $spec.join("fixtures")
12
13
 
13
- $: << $project.join("lib").to_s
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