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
@@ -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