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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rubocop.yml +256 -0
- data/.travis.yml +5 -1
- data/CHANGELOG.md +26 -0
- data/README.md +87 -8
- data/Rakefile +6 -4
- data/bin/lhm-config.sh +7 -0
- data/bin/lhm-kill-queue +13 -15
- data/bin/lhm-spec-clobber.sh +5 -4
- data/bin/lhm-spec-grants.sh +2 -2
- data/bin/lhm-spec-setup-cluster.sh +2 -3
- data/gemfiles/ar-2.3_mysql.gemfile +2 -1
- data/gemfiles/ar-3.2_mysql.gemfile +1 -1
- data/gemfiles/ar-3.2_mysql2.gemfile +1 -1
- data/gemfiles/dm_mysql.gemfile +1 -1
- data/lhm.gemspec +7 -8
- data/lib/lhm/atomic_switcher.rb +2 -1
- data/lib/lhm/chunker.rb +51 -39
- data/lib/lhm/command.rb +4 -2
- data/lib/lhm/connection.rb +14 -2
- data/lib/lhm/entangler.rb +5 -5
- data/lib/lhm/intersection.rb +29 -16
- data/lib/lhm/invoker.rb +31 -10
- data/lib/lhm/locked_switcher.rb +6 -6
- data/lib/lhm/migration.rb +7 -5
- data/lib/lhm/migrator.rb +57 -9
- data/lib/lhm/printer.rb +54 -0
- data/lib/lhm/sql_helper.rb +4 -4
- data/lib/lhm/table.rb +12 -12
- data/lib/lhm/throttler/time.rb +29 -0
- data/lib/lhm/throttler.rb +32 -0
- data/lib/lhm/version.rb +1 -1
- data/lib/lhm.rb +71 -6
- data/spec/.lhm.example +1 -1
- data/spec/README.md +20 -13
- data/spec/fixtures/lines.ddl +7 -0
- data/spec/fixtures/permissions.ddl +5 -0
- data/spec/fixtures/tracks.ddl +5 -0
- data/spec/fixtures/users.ddl +4 -2
- data/spec/integration/atomic_switcher_spec.rb +7 -7
- data/spec/integration/chunker_spec.rb +11 -5
- data/spec/integration/cleanup_spec.rb +72 -0
- data/spec/integration/entangler_spec.rb +11 -11
- data/spec/integration/integration_helper.rb +49 -17
- data/spec/integration/lhm_spec.rb +157 -37
- data/spec/integration/locked_switcher_spec.rb +7 -7
- data/spec/integration/table_spec.rb +15 -17
- data/spec/test_helper.rb +28 -0
- data/spec/unit/atomic_switcher_spec.rb +6 -6
- data/spec/unit/chunker_spec.rb +95 -73
- data/spec/unit/datamapper_connection_spec.rb +1 -0
- data/spec/unit/entangler_spec.rb +19 -19
- data/spec/unit/intersection_spec.rb +27 -15
- data/spec/unit/lhm_spec.rb +29 -0
- data/spec/unit/locked_switcher_spec.rb +14 -14
- data/spec/unit/migration_spec.rb +10 -5
- data/spec/unit/migrator_spec.rb +53 -41
- data/spec/unit/printer_spec.rb +79 -0
- data/spec/unit/sql_helper_spec.rb +10 -10
- data/spec/unit/table_spec.rb +11 -11
- data/spec/unit/throttler_spec.rb +73 -0
- data/spec/unit/unit_helper.rb +1 -13
- metadata +63 -24
- data/spec/bootstrap.rb +0 -13
data/lib/lhm/atomic_switcher.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
33
|
-
@limit && @start ? ((@limit - @start + 1) / @stride.to_f).ceil : 0
|
34
|
-
end
|
42
|
+
private
|
35
43
|
|
36
|
-
def bottom
|
37
|
-
|
44
|
+
def bottom
|
45
|
+
@next_to_insert
|
38
46
|
end
|
39
47
|
|
40
|
-
def top(
|
41
|
-
[
|
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 }` (#{
|
46
|
-
"select #{
|
47
|
-
"
|
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
|
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
|
64
|
+
limit = connection.select_value("select max(id) from `#{ origin_name }`")
|
57
65
|
limit ? limit.to_i : nil
|
58
66
|
end
|
59
67
|
|
60
|
-
|
61
|
-
|
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
|
75
|
-
@
|
95
|
+
def origin_columns
|
96
|
+
@origin_columns ||= @migration.intersection.origin.typed(origin_name)
|
76
97
|
end
|
77
98
|
|
78
|
-
def
|
79
|
-
|
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
|
85
|
-
|
86
|
-
|
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
|
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
|
data/lib/lhm/connection.rb
CHANGED
@@ -28,7 +28,7 @@ module Lhm
|
|
28
28
|
|
29
29
|
def show_create(table_name)
|
30
30
|
sql = "show create table `#{ table_name }`"
|
31
|
-
|
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
|
-
|
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
|
-
@
|
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 }` (#{ @
|
44
|
-
values (#{ @
|
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 }` (#{ @
|
53
|
-
values (#{ @
|
52
|
+
replace into `#{ @destination.name }` (#{ @intersection.destination.joined }) #{ SqlHelper.annotation }
|
53
|
+
values (#{ @intersection.origin.typed('NEW') })
|
54
54
|
}
|
55
55
|
end
|
56
56
|
|
data/lib/lhm/intersection.rb
CHANGED
@@ -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
|
13
|
-
(
|
13
|
+
def origin
|
14
|
+
(common + @renames.keys).extend(Joiners)
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
-
common
|
17
|
+
def destination
|
18
|
+
(common + @renames.values).extend(Joiners)
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
-
escaped.join(", ")
|
22
|
-
end
|
21
|
+
private
|
23
22
|
|
24
|
-
def
|
25
|
-
|
23
|
+
def common
|
24
|
+
(@origin.columns.keys & @destination.columns.keys).sort
|
26
25
|
end
|
27
26
|
|
28
|
-
|
27
|
+
module Joiners
|
28
|
+
def escaped
|
29
|
+
self.map { |name| tick(name) }
|
30
|
+
end
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
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
|
-
|
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
|
data/lib/lhm/locked_switcher.rb
CHANGED
@@ -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
|
-
|
42
|
-
|
41
|
+
'commit',
|
42
|
+
'unlock tables'
|
43
43
|
]
|
44
44
|
end
|
45
45
|
|
46
46
|
def uncommitted(&block)
|
47
47
|
[
|
48
|
-
|
49
|
-
|
48
|
+
'set @lhm_auto_commit = @@session.autocommit',
|
49
|
+
'set session autocommit = 0',
|
50
50
|
yield,
|
51
|
-
|
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(
|
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_#{
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
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
|
-
|
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
|
data/lib/lhm/printer.rb
ADDED
@@ -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
|
data/lib/lhm/sql_helper.rb
CHANGED
@@ -6,12 +6,12 @@ module Lhm
|
|
6
6
|
extend self
|
7
7
|
|
8
8
|
def annotation
|
9
|
-
|
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(
|
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,
|
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
|