lhm 1.2.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|