lhm-shopify 3.3.5
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/.github/workflows/test.yml +34 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +183 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +216 -0
- data/Gemfile +5 -0
- data/LICENSE +27 -0
- data/README.md +284 -0
- data/Rakefile +22 -0
- data/bin/.gitkeep +0 -0
- data/dbdeployer/config.json +32 -0
- data/dbdeployer/install.sh +64 -0
- data/dev.yml +20 -0
- data/gemfiles/ar-2.3_mysql.gemfile +6 -0
- data/gemfiles/ar-3.2_mysql.gemfile +5 -0
- data/gemfiles/ar-3.2_mysql2.gemfile +5 -0
- data/gemfiles/ar-4.0_mysql2.gemfile +5 -0
- data/gemfiles/ar-4.1_mysql2.gemfile +5 -0
- data/gemfiles/ar-4.2_mysql2.gemfile +5 -0
- data/gemfiles/ar-5.0_mysql2.gemfile +5 -0
- data/lhm.gemspec +34 -0
- data/lib/lhm.rb +131 -0
- data/lib/lhm/atomic_switcher.rb +52 -0
- data/lib/lhm/chunk_finder.rb +32 -0
- data/lib/lhm/chunk_insert.rb +51 -0
- data/lib/lhm/chunker.rb +87 -0
- data/lib/lhm/cleanup/current.rb +74 -0
- data/lib/lhm/command.rb +48 -0
- data/lib/lhm/entangler.rb +117 -0
- data/lib/lhm/intersection.rb +51 -0
- data/lib/lhm/invoker.rb +98 -0
- data/lib/lhm/locked_switcher.rb +74 -0
- data/lib/lhm/migration.rb +43 -0
- data/lib/lhm/migrator.rb +237 -0
- data/lib/lhm/printer.rb +59 -0
- data/lib/lhm/railtie.rb +9 -0
- data/lib/lhm/sql_helper.rb +77 -0
- data/lib/lhm/sql_retry.rb +61 -0
- data/lib/lhm/table.rb +121 -0
- data/lib/lhm/table_name.rb +23 -0
- data/lib/lhm/test_support.rb +35 -0
- data/lib/lhm/throttler.rb +36 -0
- data/lib/lhm/throttler/slave_lag.rb +145 -0
- data/lib/lhm/throttler/threads_running.rb +53 -0
- data/lib/lhm/throttler/time.rb +29 -0
- data/lib/lhm/timestamp.rb +11 -0
- data/lib/lhm/version.rb +6 -0
- data/shipit.rubygems.yml +0 -0
- data/spec/.lhm.example +4 -0
- data/spec/README.md +58 -0
- data/spec/fixtures/bigint_table.ddl +4 -0
- data/spec/fixtures/composite_primary_key.ddl +7 -0
- data/spec/fixtures/custom_primary_key.ddl +6 -0
- data/spec/fixtures/destination.ddl +6 -0
- data/spec/fixtures/lines.ddl +7 -0
- data/spec/fixtures/origin.ddl +6 -0
- data/spec/fixtures/permissions.ddl +5 -0
- data/spec/fixtures/small_table.ddl +4 -0
- data/spec/fixtures/tracks.ddl +5 -0
- data/spec/fixtures/users.ddl +14 -0
- data/spec/fixtures/wo_id_int_column.ddl +6 -0
- data/spec/integration/atomic_switcher_spec.rb +93 -0
- data/spec/integration/chunk_insert_spec.rb +29 -0
- data/spec/integration/chunker_spec.rb +185 -0
- data/spec/integration/cleanup_spec.rb +136 -0
- data/spec/integration/entangler_spec.rb +66 -0
- data/spec/integration/integration_helper.rb +237 -0
- data/spec/integration/invoker_spec.rb +33 -0
- data/spec/integration/lhm_spec.rb +585 -0
- data/spec/integration/lock_wait_timeout_spec.rb +30 -0
- data/spec/integration/locked_switcher_spec.rb +50 -0
- data/spec/integration/sql_retry/lock_wait_spec.rb +125 -0
- data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +101 -0
- data/spec/integration/table_spec.rb +91 -0
- data/spec/test_helper.rb +32 -0
- data/spec/unit/atomic_switcher_spec.rb +31 -0
- data/spec/unit/chunk_finder_spec.rb +73 -0
- data/spec/unit/chunk_insert_spec.rb +44 -0
- data/spec/unit/chunker_spec.rb +166 -0
- data/spec/unit/entangler_spec.rb +124 -0
- data/spec/unit/intersection_spec.rb +51 -0
- data/spec/unit/lhm_spec.rb +29 -0
- data/spec/unit/locked_switcher_spec.rb +51 -0
- data/spec/unit/migrator_spec.rb +146 -0
- data/spec/unit/printer_spec.rb +97 -0
- data/spec/unit/sql_helper_spec.rb +32 -0
- data/spec/unit/table_name_spec.rb +39 -0
- data/spec/unit/table_spec.rb +47 -0
- data/spec/unit/throttler/slave_lag_spec.rb +317 -0
- data/spec/unit/throttler/threads_running_spec.rb +64 -0
- data/spec/unit/throttler_spec.rb +124 -0
- data/spec/unit/unit_helper.rb +13 -0
- metadata +239 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'lhm/timestamp'
|
2
|
+
require 'lhm/sql_retry'
|
3
|
+
|
4
|
+
module Lhm
|
5
|
+
module Cleanup
|
6
|
+
class Current
|
7
|
+
def initialize(run, origin_table_name, connection, options = {})
|
8
|
+
@run = run
|
9
|
+
@table_name = TableName.new(origin_table_name)
|
10
|
+
@connection = connection
|
11
|
+
@ddls = []
|
12
|
+
@retry_helper = SqlRetry.new(
|
13
|
+
@connection,
|
14
|
+
{
|
15
|
+
log_prefix: "Cleanup::Current"
|
16
|
+
}.merge!(options.fetch(:retriable, {}))
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :run, :connection, :ddls
|
21
|
+
|
22
|
+
def execute
|
23
|
+
build_statements_for_drop_lhm_triggers_for_origin
|
24
|
+
build_statements_for_rename_lhmn_tables_for_origin
|
25
|
+
if run
|
26
|
+
execute_ddls
|
27
|
+
else
|
28
|
+
report_ddls
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def build_statements_for_drop_lhm_triggers_for_origin
|
35
|
+
lhm_triggers_for_origin.each do |trigger|
|
36
|
+
@ddls << "drop trigger if exists #{trigger}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def lhm_triggers_for_origin
|
41
|
+
@lhm_triggers_for_origin ||= all_triggers_for_origin.select { |name| name =~ /^lhmt/ }
|
42
|
+
end
|
43
|
+
|
44
|
+
def all_triggers_for_origin
|
45
|
+
@all_triggers_for_origin ||= connection.select_values("show triggers like '%#{@table_name.original}'").collect do |trigger|
|
46
|
+
trigger.respond_to?(:trigger) ? trigger.trigger : trigger
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_statements_for_rename_lhmn_tables_for_origin
|
51
|
+
lhmn_tables_for_origin.each do |table|
|
52
|
+
@ddls << "rename table #{table} to #{@table_name.failed}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def lhmn_tables_for_origin
|
57
|
+
@lhmn_tables_for_origin ||= connection.select_values("show tables like '#{@table_name.new}'")
|
58
|
+
end
|
59
|
+
|
60
|
+
def execute_ddls
|
61
|
+
ddls.each do |ddl|
|
62
|
+
@retry_helper.with_retries do |retriable_connection|
|
63
|
+
retriable_connection.execute(ddl)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def report_ddls
|
69
|
+
puts "The following DDLs would be executed:"
|
70
|
+
ddls.each { |ddl| puts ddl }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/lhm/command.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
module Lhm
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
module Command
|
9
|
+
def run(&block)
|
10
|
+
Lhm.logger.info "Starting run of class=#{self.class}"
|
11
|
+
validate
|
12
|
+
|
13
|
+
if block_given?
|
14
|
+
before
|
15
|
+
block.call(self)
|
16
|
+
after
|
17
|
+
else
|
18
|
+
execute
|
19
|
+
end
|
20
|
+
rescue => e
|
21
|
+
Lhm.logger.error "Error in class=#{self.class}, reverting. exception=#{e.class} message=#{e.message}"
|
22
|
+
revert
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def validate
|
29
|
+
end
|
30
|
+
|
31
|
+
def revert
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute
|
35
|
+
raise NotImplementedError.new(self.class.name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def before
|
39
|
+
end
|
40
|
+
|
41
|
+
def after
|
42
|
+
end
|
43
|
+
|
44
|
+
def error(msg)
|
45
|
+
raise Error.new(msg)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require 'lhm/command'
|
5
|
+
require 'lhm/sql_helper'
|
6
|
+
require 'lhm/sql_retry'
|
7
|
+
|
8
|
+
module Lhm
|
9
|
+
class Entangler
|
10
|
+
include Command
|
11
|
+
include SqlHelper
|
12
|
+
|
13
|
+
attr_reader :connection
|
14
|
+
|
15
|
+
# Creates entanglement between two tables. All creates, updates and deletes
|
16
|
+
# to origin will be repeated on the destination table.
|
17
|
+
def initialize(migration, connection = nil, options = {})
|
18
|
+
@intersection = migration.intersection
|
19
|
+
@origin = migration.origin
|
20
|
+
@destination = migration.destination
|
21
|
+
@connection = connection
|
22
|
+
@retry_helper = SqlRetry.new(
|
23
|
+
@connection,
|
24
|
+
{
|
25
|
+
log_prefix: "Entangler"
|
26
|
+
}.merge!(options.fetch(:retriable, {}))
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def entangle
|
31
|
+
[
|
32
|
+
create_delete_trigger,
|
33
|
+
create_insert_trigger,
|
34
|
+
create_update_trigger
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
def untangle
|
39
|
+
[
|
40
|
+
"drop trigger if exists `#{ trigger(:del) }`",
|
41
|
+
"drop trigger if exists `#{ trigger(:ins) }`",
|
42
|
+
"drop trigger if exists `#{ trigger(:upd) }`"
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_insert_trigger
|
47
|
+
strip %Q{
|
48
|
+
create trigger `#{ trigger(:ins) }`
|
49
|
+
after insert on `#{ @origin.name }` for each row
|
50
|
+
replace into `#{ @destination.name }` (#{ @intersection.destination.joined }) #{ SqlHelper.annotation }
|
51
|
+
values (#{ @intersection.origin.typed('NEW') })
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_update_trigger
|
56
|
+
strip %Q{
|
57
|
+
create trigger `#{ trigger(:upd) }`
|
58
|
+
after update on `#{ @origin.name }` for each row
|
59
|
+
replace into `#{ @destination.name }` (#{ @intersection.destination.joined }) #{ SqlHelper.annotation }
|
60
|
+
values (#{ @intersection.origin.typed('NEW') })
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_delete_trigger
|
65
|
+
strip %Q{
|
66
|
+
create trigger `#{ trigger(:del) }`
|
67
|
+
after delete on `#{ @origin.name }` for each row
|
68
|
+
delete ignore from `#{ @destination.name }` #{ SqlHelper.annotation }
|
69
|
+
where `#{ @destination.name }`.`id` = OLD.`id`
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def trigger(type)
|
74
|
+
"lhmt_#{ type }_#{ @origin.name }"[0...64]
|
75
|
+
end
|
76
|
+
|
77
|
+
def expected_triggers
|
78
|
+
[trigger(:ins), trigger(:upd), trigger(:del)]
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate
|
82
|
+
unless @connection.data_source_exists?(@origin.name)
|
83
|
+
error("#{ @origin.name } does not exist")
|
84
|
+
end
|
85
|
+
|
86
|
+
unless @connection.data_source_exists?(@destination.name)
|
87
|
+
error("#{ @destination.name } does not exist")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def before
|
92
|
+
entangle.each do |stmt|
|
93
|
+
@retry_helper.with_retries do |retriable_connection|
|
94
|
+
retriable_connection.execute(stmt)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def after
|
100
|
+
untangle.each do |stmt|
|
101
|
+
@retry_helper.with_retries do |retriable_connection|
|
102
|
+
retriable_connection.execute(stmt)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def revert
|
108
|
+
after
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def strip(sql)
|
114
|
+
sql.strip.gsub(/\n */, "\n")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
module Lhm
|
5
|
+
# Determine and format columns common to origin and destination.
|
6
|
+
class Intersection
|
7
|
+
def initialize(origin, destination, renames = {})
|
8
|
+
@origin = origin
|
9
|
+
@destination = destination
|
10
|
+
@renames = renames
|
11
|
+
end
|
12
|
+
|
13
|
+
def origin
|
14
|
+
(common + @renames.keys).extend(Joiners)
|
15
|
+
end
|
16
|
+
|
17
|
+
def destination
|
18
|
+
(common + @renames.values).extend(Joiners)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def common
|
24
|
+
(@origin.columns.keys & @destination.columns.keys).sort
|
25
|
+
end
|
26
|
+
|
27
|
+
module Joiners
|
28
|
+
def escaped
|
29
|
+
map { |name| tick(name) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def joined
|
33
|
+
escaped.join(', ')
|
34
|
+
end
|
35
|
+
|
36
|
+
def typed(type)
|
37
|
+
map { |name| qualified(name, type) }.join(', ')
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def qualified(name, type)
|
43
|
+
"`#{ type }`.`#{ name }`"
|
44
|
+
end
|
45
|
+
|
46
|
+
def tick(name)
|
47
|
+
"`#{ name }`"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/lhm/invoker.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require 'lhm/chunker'
|
5
|
+
require 'lhm/entangler'
|
6
|
+
require 'lhm/atomic_switcher'
|
7
|
+
require 'lhm/locked_switcher'
|
8
|
+
require 'lhm/migrator'
|
9
|
+
|
10
|
+
module Lhm
|
11
|
+
# Copies an origin table to an altered destination table. Live activity is
|
12
|
+
# synchronized into the destination table using triggers.
|
13
|
+
#
|
14
|
+
# Once the origin and destination tables have converged, origin is archived
|
15
|
+
# and replaced by destination.
|
16
|
+
class Invoker
|
17
|
+
include SqlHelper
|
18
|
+
LOCK_WAIT_TIMEOUT_DELTA = 10
|
19
|
+
INNODB_LOCK_WAIT_TIMEOUT_MAX=1073741824.freeze # https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_lock_wait_timeout
|
20
|
+
LOCK_WAIT_TIMEOUT_MAX=31536000.freeze # https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html
|
21
|
+
|
22
|
+
attr_reader :migrator, :connection
|
23
|
+
|
24
|
+
def initialize(origin, connection)
|
25
|
+
@connection = connection
|
26
|
+
@migrator = Migrator.new(origin, connection)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_session_lock_wait_timeouts
|
30
|
+
global_innodb_lock_wait_timeout = @connection.select_one("SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout'")
|
31
|
+
global_lock_wait_timeout = @connection.select_one("SHOW GLOBAL VARIABLES LIKE 'lock_wait_timeout'")
|
32
|
+
|
33
|
+
if global_innodb_lock_wait_timeout
|
34
|
+
desired_innodb_lock_wait_timeout = global_innodb_lock_wait_timeout['Value'].to_i + LOCK_WAIT_TIMEOUT_DELTA
|
35
|
+
if desired_innodb_lock_wait_timeout <= INNODB_LOCK_WAIT_TIMEOUT_MAX
|
36
|
+
@connection.execute("SET SESSION innodb_lock_wait_timeout=#{desired_innodb_lock_wait_timeout}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if global_lock_wait_timeout
|
41
|
+
desired_lock_wait_timeout = global_lock_wait_timeout['Value'].to_i + LOCK_WAIT_TIMEOUT_DELTA
|
42
|
+
if desired_lock_wait_timeout <= LOCK_WAIT_TIMEOUT_MAX
|
43
|
+
@connection.execute("SET SESSION lock_wait_timeout=#{desired_lock_wait_timeout}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def run(options = {})
|
49
|
+
normalize_options(options)
|
50
|
+
set_session_lock_wait_timeouts
|
51
|
+
migration = @migrator.run
|
52
|
+
entangler = Entangler.new(migration, @connection, options)
|
53
|
+
|
54
|
+
entangler.run do
|
55
|
+
options[:verifier] ||= Proc.new { |conn| triggers_still_exist?(conn, entangler) }
|
56
|
+
Chunker.new(migration, @connection, options).run
|
57
|
+
raise "Required triggers do not exist" unless triggers_still_exist?(@connection, entangler)
|
58
|
+
if options[:atomic_switch]
|
59
|
+
AtomicSwitcher.new(migration, @connection, options).run
|
60
|
+
else
|
61
|
+
LockedSwitcher.new(migration, @connection).run
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def triggers_still_exist?(conn, entangler)
|
67
|
+
triggers = conn.select_values("SHOW TRIGGERS LIKE '%#{migrator.origin.name}'").select { |name| name =~ /^lhmt/ }
|
68
|
+
triggers.sort == entangler.expected_triggers.sort
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def normalize_options(options)
|
74
|
+
Lhm.logger.info "Starting LHM run on table=#{@migrator.name}"
|
75
|
+
|
76
|
+
unless options.include?(:atomic_switch)
|
77
|
+
if supports_atomic_switch?
|
78
|
+
options[:atomic_switch] = true
|
79
|
+
else
|
80
|
+
raise Error.new(
|
81
|
+
"Using mysql #{version_string}. You must explicitly set " \
|
82
|
+
'options[:atomic_switch] (re SqlHelper#supports_atomic_switch?)')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
if options[:throttler]
|
87
|
+
throttler_options = options[:throttler_options] || {}
|
88
|
+
options[:throttler] = Throttler::Factory.create_throttler(options[:throttler], throttler_options)
|
89
|
+
else
|
90
|
+
options[:throttler] = Lhm.throttler
|
91
|
+
end
|
92
|
+
|
93
|
+
rescue => e
|
94
|
+
Lhm.logger.error "LHM run failed with exception=#{e.class} message=#{e.message}"
|
95
|
+
raise
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require 'lhm/command'
|
5
|
+
require 'lhm/migration'
|
6
|
+
require 'lhm/sql_helper'
|
7
|
+
|
8
|
+
module Lhm
|
9
|
+
# Switches origin with destination table nonatomically using a locked write.
|
10
|
+
# LockedSwitcher adopts the Facebook strategy, with the following caveat:
|
11
|
+
#
|
12
|
+
# "Since alter table causes an implicit commit in innodb, innodb locks get
|
13
|
+
# released after the first alter table. So any transaction that sneaks in
|
14
|
+
# after the first alter table and before the second alter table gets
|
15
|
+
# a 'table not found' error. The second alter table is expected to be very
|
16
|
+
# fast though because copytable is not visible to other transactions and so
|
17
|
+
# there is no need to wait."
|
18
|
+
#
|
19
|
+
class LockedSwitcher
|
20
|
+
include Command
|
21
|
+
include SqlHelper
|
22
|
+
|
23
|
+
attr_reader :connection
|
24
|
+
|
25
|
+
def initialize(migration, connection = nil)
|
26
|
+
@migration = migration
|
27
|
+
@connection = connection
|
28
|
+
@origin = migration.origin
|
29
|
+
@destination = migration.destination
|
30
|
+
end
|
31
|
+
|
32
|
+
def statements
|
33
|
+
uncommitted { switch }
|
34
|
+
end
|
35
|
+
|
36
|
+
def switch
|
37
|
+
[
|
38
|
+
"lock table `#{ @origin.name }` write, `#{ @destination.name }` write",
|
39
|
+
"alter table `#{ @origin.name }` rename `#{ @migration.archive_name }`",
|
40
|
+
"alter table `#{ @destination.name }` rename `#{ @origin.name }`",
|
41
|
+
'commit',
|
42
|
+
'unlock tables'
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
def uncommitted
|
47
|
+
[
|
48
|
+
'set @lhm_auto_commit = @@session.autocommit',
|
49
|
+
'set session autocommit = 0',
|
50
|
+
yield,
|
51
|
+
'set session autocommit = @lhm_auto_commit'
|
52
|
+
].flatten
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate
|
56
|
+
unless @connection.data_source_exists?(@origin.name) &&
|
57
|
+
@connection.data_source_exists?(@destination.name)
|
58
|
+
error "`#{ @origin.name }` and `#{ @destination.name }` must exist"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def revert
|
65
|
+
@connection.execute(tagged('unlock tables'))
|
66
|
+
end
|
67
|
+
|
68
|
+
def execute
|
69
|
+
statements.each do |stmt|
|
70
|
+
@connection.execute(tagged(stmt))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|