lhm 1.2.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -29,7 +29,7 @@ module Lhm
29
29
 
30
30
  def atomic_switch
31
31
  [
32
- "rename table `#{ @origin.name }` to `#{ @migration.archive_name }`, " +
32
+ "rename table `#{ @origin.name }` to `#{ @migration.archive_name }`, " \
33
33
  "`#{ @destination.name }` to `#{ @origin.name }`"
34
34
  ]
35
35
  end
@@ -42,6 +42,7 @@ module Lhm
42
42
  end
43
43
 
44
44
  private
45
+
45
46
  def execute
46
47
  @connection.sql(statements)
47
48
  end
data/lib/lhm/chunker.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
2
  # Schmidt
3
-
4
3
  require 'lhm/command'
5
4
  require 'lhm/sql_helper'
5
+ require 'lhm/printer'
6
6
 
7
7
  module Lhm
8
8
  class Chunker
@@ -16,53 +16,74 @@ module Lhm
16
16
  def initialize(migration, connection = nil, options = {})
17
17
  @migration = migration
18
18
  @connection = connection
19
- @stride = options[:stride] || 40_000
20
- @throttle = options[:throttle] || 100
19
+ @throttler = options[:throttler]
21
20
  @start = options[:start] || select_start
22
21
  @limit = options[:limit] || select_limit
22
+ @printer = options[:printer] || Printer::Percentage.new
23
23
  end
24
24
 
25
- # Copies chunks of size `stride`, starting from `start` up to id `limit`.
26
- def up_to(&block)
27
- 1.upto(traversable_chunks_size) do |n|
28
- yield(bottom(n), top(n))
25
+ def execute
26
+ return unless @start && @limit
27
+ @next_to_insert = @start
28
+ while @next_to_insert < @limit || (@next_to_insert == 1 && @start == 1)
29
+ stride = @throttler.stride
30
+ affected_rows = @connection.update(copy(bottom, top(stride)))
31
+
32
+ if @throttler && affected_rows > 0
33
+ @throttler.run
34
+ end
35
+
36
+ @printer.notify(bottom, @limit)
37
+ @next_to_insert = top(stride) + 1
29
38
  end
39
+ @printer.end
30
40
  end
31
41
 
32
- def traversable_chunks_size
33
- @limit && @start ? ((@limit - @start + 1) / @stride.to_f).ceil : 0
34
- end
42
+ private
35
43
 
36
- def bottom(chunk)
37
- (chunk - 1) * @stride + @start
44
+ def bottom
45
+ @next_to_insert
38
46
  end
39
47
 
40
- def top(chunk)
41
- [chunk * @stride + @start - 1, @limit].min
48
+ def top(stride)
49
+ [(@next_to_insert + stride - 1), @limit].min
42
50
  end
43
51
 
44
52
  def copy(lowest, highest)
45
- "insert ignore into `#{ destination_name }` (#{ columns }) " +
46
- "select #{ columns } from `#{ origin_name }` " +
47
- "where `id` between #{ lowest } and #{ highest }"
53
+ "insert ignore into `#{ destination_name }` (#{ destination_columns }) " \
54
+ "select #{ origin_columns } from `#{ origin_name }` " \
55
+ "#{ conditions } `#{ origin_name }`.`id` between #{ lowest } and #{ highest }"
48
56
  end
49
57
 
50
58
  def select_start
51
- start = connection.select_value("select min(id) from #{ origin_name }")
59
+ start = connection.select_value("select min(id) from `#{ origin_name }`")
52
60
  start ? start.to_i : nil
53
61
  end
54
62
 
55
63
  def select_limit
56
- limit = connection.select_value("select max(id) from #{ origin_name }")
64
+ limit = connection.select_value("select max(id) from `#{ origin_name }`")
57
65
  limit ? limit.to_i : nil
58
66
  end
59
67
 
60
- def throttle_seconds
61
- @throttle / 1000.0
68
+ # XXX this is extremely brittle and doesn't work when filter contains more
69
+ # than one SQL clause, e.g. "where ... group by foo". Before making any
70
+ # more changes here, please consider either:
71
+ #
72
+ # 1. Letting users only specify part of defined clauses (i.e. don't allow
73
+ # `filter` on Migrator to accept both WHERE and INNER JOIN
74
+ # 2. Changing query building so that it uses structured data rather than
75
+ # strings until the last possible moment.
76
+ def conditions
77
+ if @migration.conditions
78
+ @migration.conditions.
79
+ sub(/\)\Z/, '').
80
+ # put any where conditions in parens
81
+ sub(/where\s(\w.*)\Z/, 'where (\\1)') + ' and'
82
+ else
83
+ 'where'
84
+ end
62
85
  end
63
86
 
64
- private
65
-
66
87
  def destination_name
67
88
  @migration.destination.name
68
89
  end
@@ -71,27 +92,18 @@ module Lhm
71
92
  @migration.origin.name
72
93
  end
73
94
 
74
- def columns
75
- @columns ||= @migration.intersection.joined
95
+ def origin_columns
96
+ @origin_columns ||= @migration.intersection.origin.typed(origin_name)
76
97
  end
77
98
 
78
- def validate
79
- if @start && @limit && @start > @limit
80
- error("impossible chunk options (limit must be greater than start)")
81
- end
99
+ def destination_columns
100
+ @destination_columns ||= @migration.intersection.destination.joined
82
101
  end
83
102
 
84
- def execute
85
- up_to do |lowest, highest|
86
- affected_rows = @connection.update(copy(lowest, highest))
87
-
88
- if affected_rows > 0
89
- sleep(throttle_seconds)
90
- end
91
-
92
- print "."
103
+ def validate
104
+ if @start && @limit && @start > @limit
105
+ error('impossible chunk options (limit must be greater than start)')
93
106
  end
94
- print "\n"
95
107
  end
96
108
  end
97
109
  end
data/lib/lhm/command.rb CHANGED
@@ -7,16 +7,18 @@ module Lhm
7
7
 
8
8
  module Command
9
9
  def run(&block)
10
+ Lhm.logger.info "Starting run of class=#{self.class}"
10
11
  validate
11
12
 
12
- if(block_given?)
13
+ if block_given?
13
14
  before
14
15
  block.call(self)
15
16
  after
16
17
  else
17
18
  execute
18
19
  end
19
- rescue
20
+ rescue => e
21
+ Lhm.logger.error "Error in class=#{self.class}, reverting. exception=#{e.class} message=#{e.message}"
20
22
  revert
21
23
  raise
22
24
  end
@@ -28,7 +28,7 @@ module Lhm
28
28
 
29
29
  def show_create(table_name)
30
30
  sql = "show create table `#{ table_name }`"
31
- select_values(sql).last
31
+ select_one(sql).values.last
32
32
  end
33
33
 
34
34
  def current_database
@@ -51,7 +51,7 @@ module Lhm
51
51
  end
52
52
 
53
53
  def select_values(sql)
54
- select_one(sql).values
54
+ select_all(sql)
55
55
  end
56
56
 
57
57
  def select_value(sql)
@@ -77,6 +77,14 @@ module Lhm
77
77
  and table_name = '#{ table_name }'
78
78
  })
79
79
  end
80
+
81
+ def quote_value(value)
82
+ quoter.quote_value(value)
83
+ end
84
+
85
+ def quoter
86
+ @quoter ||= Object.new.tap { |o| o.extend(DataObjects::Quoting) }
87
+ end
80
88
  end
81
89
 
82
90
  class ActiveRecordConnection
@@ -138,6 +146,10 @@ module Lhm
138
146
  def table_exists?(table_name)
139
147
  @adapter.table_exists?(table_name)
140
148
  end
149
+
150
+ def quote_value(value)
151
+ @adapter.quote(value)
152
+ end
141
153
  end
142
154
  end
143
155
  end
data/lib/lhm/entangler.rb CHANGED
@@ -14,7 +14,7 @@ module Lhm
14
14
  # Creates entanglement between two tables. All creates, updates and deletes
15
15
  # to origin will be repeated on the destination table.
16
16
  def initialize(migration, connection = nil)
17
- @common = migration.intersection
17
+ @intersection = migration.intersection
18
18
  @origin = migration.origin
19
19
  @destination = migration.destination
20
20
  @connection = connection
@@ -40,8 +40,8 @@ module Lhm
40
40
  strip %Q{
41
41
  create trigger `#{ trigger(:ins) }`
42
42
  after insert on `#{ @origin.name }` for each row
43
- replace into `#{ @destination.name }` (#{ @common.joined }) #{ SqlHelper.annotation }
44
- values (#{ @common.typed("NEW") })
43
+ replace into `#{ @destination.name }` (#{ @intersection.destination.joined }) #{ SqlHelper.annotation }
44
+ values (#{ @intersection.origin.typed('NEW') })
45
45
  }
46
46
  end
47
47
 
@@ -49,8 +49,8 @@ module Lhm
49
49
  strip %Q{
50
50
  create trigger `#{ trigger(:upd) }`
51
51
  after update on `#{ @origin.name }` for each row
52
- replace into `#{ @destination.name }` (#{ @common.joined }) #{ SqlHelper.annotation }
53
- values (#{ @common.typed("NEW") })
52
+ replace into `#{ @destination.name }` (#{ @intersection.destination.joined }) #{ SqlHelper.annotation }
53
+ values (#{ @intersection.origin.typed('NEW') })
54
54
  }
55
55
  end
56
56
 
@@ -4,35 +4,48 @@
4
4
  module Lhm
5
5
  # Determine and format columns common to origin and destination.
6
6
  class Intersection
7
- def initialize(origin, destination)
7
+ def initialize(origin, destination, renames = {})
8
8
  @origin = origin
9
9
  @destination = destination
10
+ @renames = renames
10
11
  end
11
12
 
12
- def common
13
- (@origin.columns.keys & @destination.columns.keys).sort
13
+ def origin
14
+ (common + @renames.keys).extend(Joiners)
14
15
  end
15
16
 
16
- def escaped
17
- common.map { |name| tick(name) }
17
+ def destination
18
+ (common + @renames.values).extend(Joiners)
18
19
  end
19
20
 
20
- def joined
21
- escaped.join(", ")
22
- end
21
+ private
23
22
 
24
- def typed(type)
25
- common.map { |name| qualified(name, type) }.join(", ")
23
+ def common
24
+ (@origin.columns.keys & @destination.columns.keys).sort
26
25
  end
27
26
 
28
- private
27
+ module Joiners
28
+ def escaped
29
+ self.map { |name| tick(name) }
30
+ end
29
31
 
30
- def qualified(name, type)
31
- "#{ type }.`#{ name }`"
32
- end
32
+ def joined
33
+ escaped.join(', ')
34
+ end
35
+
36
+ def typed(type)
37
+ self.map { |name| qualified(name, type) }.join(', ')
38
+ end
39
+
40
+ private
41
+
42
+ def qualified(name, type)
43
+ "`#{ type }`.`#{ name }`"
44
+ end
33
45
 
34
- def tick(name)
35
- "`#{ name }`"
46
+ def tick(name)
47
+ "`#{ name }`"
48
+ end
36
49
  end
37
50
  end
38
51
  end
data/lib/lhm/invoker.rb CHANGED
@@ -24,16 +24,7 @@ module Lhm
24
24
  end
25
25
 
26
26
  def run(options = {})
27
- if !options.include?(:atomic_switch)
28
- if supports_atomic_switch?
29
- options[:atomic_switch] = true
30
- else
31
- raise Error.new(
32
- "Using mysql #{version_string}. You must explicitly set " +
33
- "options[:atomic_switch] (re SqlHelper#supports_atomic_switch?)")
34
- end
35
- end
36
-
27
+ normalize_options(options)
37
28
  migration = @migrator.run
38
29
 
39
30
  Entangler.new(migration, @connection).run do
@@ -45,5 +36,35 @@ module Lhm
45
36
  end
46
37
  end
47
38
  end
39
+
40
+ private
41
+
42
+ def normalize_options(options)
43
+ Lhm.logger.info "Starting LHM run on table=#{@migrator.name}"
44
+
45
+ if !options.include?(:atomic_switch)
46
+ if supports_atomic_switch?
47
+ options[:atomic_switch] = true
48
+ else
49
+ raise Error.new(
50
+ "Using mysql #{version_string}. You must explicitly set " \
51
+ 'options[:atomic_switch] (re SqlHelper#supports_atomic_switch?)')
52
+ end
53
+ end
54
+
55
+ if options[:throttler]
56
+ options[:throttler] = Throttler::Factory.create_throttler(*options[:throttler])
57
+ elsif options[:throttle] || options[:stride]
58
+ # we still support the throttle and stride as a Fixnum input
59
+ warn 'throttle option will no longer accept a Fixnum in the next versions.'
60
+ options[:throttler] = Throttler::LegacyTime.new(options[:throttle], options[:stride])
61
+ else
62
+ options[:throttler] = Lhm.throttler
63
+ end
64
+
65
+ rescue => e
66
+ Lhm.logger.error "LHM run failed with exception=#{e.class} message=#{e.message}"
67
+ raise
68
+ end
48
69
  end
49
70
  end
@@ -38,17 +38,17 @@ module Lhm
38
38
  "lock table `#{ @origin.name }` write, `#{ @destination.name }` write",
39
39
  "alter table `#{ @origin.name }` rename `#{ @migration.archive_name }`",
40
40
  "alter table `#{ @destination.name }` rename `#{ @origin.name }`",
41
- "commit",
42
- "unlock tables"
41
+ 'commit',
42
+ 'unlock tables'
43
43
  ]
44
44
  end
45
45
 
46
46
  def uncommitted(&block)
47
47
  [
48
- "set @lhm_auto_commit = @@session.autocommit",
49
- "set session autocommit = 0",
48
+ 'set @lhm_auto_commit = @@session.autocommit',
49
+ 'set session autocommit = 0',
50
50
  yield,
51
- "set session autocommit = @lhm_auto_commit"
51
+ 'set session autocommit = @lhm_auto_commit'
52
52
  ].flatten
53
53
  end
54
54
 
@@ -62,7 +62,7 @@ module Lhm
62
62
  private
63
63
 
64
64
  def revert
65
- @connection.sql("unlock tables")
65
+ @connection.sql('unlock tables')
66
66
  end
67
67
 
68
68
  def execute
data/lib/lhm/migration.rb CHANGED
@@ -5,24 +5,26 @@ require 'lhm/intersection'
5
5
 
6
6
  module Lhm
7
7
  class Migration
8
- attr_reader :origin, :destination
8
+ attr_reader :origin, :destination, :conditions, :renames
9
9
 
10
- def initialize(origin, destination, time = Time.now)
10
+ def initialize(origin, destination, conditions = nil, renames = {}, time = Time.now)
11
11
  @origin = origin
12
12
  @destination = destination
13
+ @conditions = conditions
13
14
  @start = time
15
+ @renames = renames
14
16
  end
15
17
 
16
18
  def archive_name
17
- "lhma_#{ startstamp }_#{ @origin.name }"
19
+ "lhma_#{ startstamp }_#{ @origin.name }"[0...64]
18
20
  end
19
21
 
20
22
  def intersection
21
- Intersection.new(@origin, @destination)
23
+ Intersection.new(@origin, @destination, @renames)
22
24
  end
23
25
 
24
26
  def startstamp
25
- @start.strftime "%Y_%m_%d_%H_%M_%S_#{ "%03d" % (@start.usec / 1000) }"
27
+ @start.strftime "%Y_%m_%d_%H_%M_%S_#{ '%03d' % (@start.usec / 1000) }"
26
28
  end
27
29
  end
28
30
  end
data/lib/lhm/migrator.rb CHANGED
@@ -13,13 +13,14 @@ module Lhm
13
13
  include Command
14
14
  include SqlHelper
15
15
 
16
- attr_reader :name, :statements, :connection
16
+ attr_reader :name, :statements, :connection, :conditions, :renames
17
17
 
18
18
  def initialize(table, connection = nil)
19
19
  @connection = connection
20
20
  @origin = table
21
21
  @name = table.destination_name
22
22
  @statements = []
23
+ @renames = {}
23
24
  end
24
25
 
25
26
  # Alter a table with a custom statement
@@ -52,7 +53,7 @@ module Lhm
52
53
  # @param [String] name Name of the column to add
53
54
  # @param [String] definition Valid SQL column definition
54
55
  def add_column(name, definition)
55
- ddl("alter table `%s` add column `%s` %s" % [@name, name, definition])
56
+ ddl('alter table `%s` add column `%s` %s' % [@name, name, definition])
56
57
  end
57
58
 
58
59
  # Change an existing column to a new definition
@@ -66,7 +67,28 @@ module Lhm
66
67
  # @param [String] name Name of the column to change
67
68
  # @param [String] definition Valid SQL column definition
68
69
  def change_column(name, definition)
69
- ddl("alter table `%s` modify column `%s` %s" % [@name, name, definition])
70
+ ddl('alter table `%s` modify column `%s` %s' % [@name, name, definition])
71
+ end
72
+
73
+ # Rename an existing column.
74
+ #
75
+ # @example
76
+ #
77
+ # Lhm.change_table(:users) do |m|
78
+ # m.rename_column(:login, :username)
79
+ # end
80
+ #
81
+ # @param [String] old Name of the column to change
82
+ # @param [String] nu New name to use for the column
83
+ def rename_column(old, nu)
84
+ col = @origin.columns[old.to_s]
85
+
86
+ definition = col[:type]
87
+ definition += ' NOT NULL' unless col[:is_nullable]
88
+ definition += " DEFAULT #{@connection.quote_value(col[:column_default])}" if col[:column_default]
89
+
90
+ ddl('alter table `%s` change column `%s` `%s` %s' % [@name, old, nu, definition])
91
+ @renames[old.to_s] = nu.to_s
70
92
  end
71
93
 
72
94
  # Remove a column from a table
@@ -79,7 +101,7 @@ module Lhm
79
101
  #
80
102
  # @param [String] name Name of the column to delete
81
103
  def remove_column(name)
82
- ddl("alter table `%s` drop `%s`" % [@name, name])
104
+ ddl('alter table `%s` drop `%s`' % [@name, name])
83
105
  end
84
106
 
85
107
  # Add an index to a table
@@ -135,8 +157,27 @@ module Lhm
135
157
  # @param [String, Symbol] index_name
136
158
  # Optional name of the index to be removed
137
159
  def remove_index(columns, index_name = nil)
160
+ columns = [columns].flatten.map(&:to_sym)
161
+ from_origin = @origin.indices.find { |name, cols| cols.map(&:to_sym) == columns }
162
+ index_name ||= from_origin[0] unless from_origin.nil?
138
163
  index_name ||= idx_name(@origin.name, columns)
139
- ddl("drop index `%s` on `%s`" % [index_name, @name])
164
+ ddl('drop index `%s` on `%s`' % [index_name, @name])
165
+ end
166
+
167
+ # Filter the data that is copied into the new table by the provided SQL.
168
+ # This SQL will be inserted into the copy directly after the "from"
169
+ # statement - so be sure to use inner/outer join syntax and not cross joins.
170
+ #
171
+ # @example Add a conditions filter to the migration.
172
+ # Lhm.change_table(:sounds) do |m|
173
+ # m.filter("inner join users on users.`id` = sounds.`user_id` and sounds.`public` = 1")
174
+ # end
175
+ #
176
+ # @param [ String ] sql The sql filter.
177
+ #
178
+ # @return [ String ] The sql filter.
179
+ def filter(sql)
180
+ @conditions = sql
140
181
  end
141
182
 
142
183
  private
@@ -147,7 +188,7 @@ module Lhm
147
188
  end
148
189
 
149
190
  unless @origin.satisfies_primary_key?
150
- error("origin does not satisfy primary key requirements")
191
+ error('origin does not satisfy primary key requirements')
151
192
  end
152
193
 
153
194
  dest = @origin.destination_name
@@ -160,7 +201,7 @@ module Lhm
160
201
  def execute
161
202
  destination_create
162
203
  @connection.sql(@statements)
163
- Migration.new(@origin, destination_read)
204
+ Migration.new(@origin, destination_read, conditions, renames)
164
205
  end
165
206
 
166
207
  def destination_create
@@ -172,10 +213,17 @@ module Lhm
172
213
  end
173
214
 
174
215
  def index_ddl(cols, unique = nil, index_name = nil)
175
- type = unique ? "unique index" : "index"
216
+ assert_valid_idx_name(index_name)
217
+ type = unique ? 'unique index' : 'index'
176
218
  index_name ||= idx_name(@origin.name, cols)
177
219
  parts = [type, index_name, @name, idx_spec(cols)]
178
- "create %s `%s` on `%s` (%s)" % parts
220
+ 'create %s `%s` on `%s` (%s)' % parts
221
+ end
222
+
223
+ def assert_valid_idx_name(index_name)
224
+ if index_name && !(index_name.is_a?(String) || index_name.is_a?(Symbol))
225
+ raise ArgumentError, 'index_name must be a string or symbol'
226
+ end
179
227
  end
180
228
  end
181
229
  end
@@ -0,0 +1,54 @@
1
+ module Lhm
2
+ module Printer
3
+ class Output
4
+ def write(message)
5
+ print message
6
+ end
7
+ end
8
+
9
+ class Base
10
+ def initialize
11
+ @output = Output.new
12
+ end
13
+ end
14
+
15
+ class Percentage < Base
16
+ def initialize
17
+ super
18
+ @max_length = 0
19
+ end
20
+
21
+ def notify(lowest, highest)
22
+ return if !highest || highest == 0
23
+ message = "%.2f%% (#{lowest}/#{highest}) complete" % (lowest.to_f / highest * 100.0)
24
+ write(message)
25
+ end
26
+
27
+ def end
28
+ write('100% complete')
29
+ @output.write "\n"
30
+ end
31
+
32
+ private
33
+
34
+ def write(message)
35
+ if (extra = @max_length - message.length) < 0
36
+ @max_length = message.length
37
+ extra = 0
38
+ end
39
+
40
+ @output.write "\r#{message}" + (' ' * extra)
41
+ end
42
+ end
43
+
44
+ class Dot < Base
45
+ def notify(lowest = nil, highest = nil)
46
+ @output.write '.'
47
+ end
48
+
49
+ def end
50
+ @output.write "\n"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -6,12 +6,12 @@ module Lhm
6
6
  extend self
7
7
 
8
8
  def annotation
9
- "/* large hadron migration */"
9
+ '/* large hadron migration */'
10
10
  end
11
11
 
12
12
  def idx_name(table_name, cols)
13
13
  column_names = column_definition(cols).map(&:first)
14
- "index_#{ table_name }_on_#{ column_names.join("_and_") }"
14
+ "index_#{ table_name }_on_#{ column_names.join('_and_') }"
15
15
  end
16
16
 
17
17
  def idx_spec(cols)
@@ -22,7 +22,7 @@ module Lhm
22
22
 
23
23
  def version_string
24
24
  row = connection.select_one("show variables like 'version'")
25
- value = struct_key(row, "Value")
25
+ value = struct_key(row, 'Value')
26
26
  row[value]
27
27
  end
28
28
 
@@ -71,7 +71,7 @@ module Lhm
71
71
  struct.members
72
72
  end
73
73
 
74
- keys.find {|k| k.to_s.downcase == key.to_s.downcase }
74
+ keys.find { |k| k.to_s.downcase == key.to_s.downcase }
75
75
  end
76
76
  end
77
77
  end