sbader-lhm 1.1.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.
- data/.gitignore +6 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +99 -0
- data/LICENSE +27 -0
- data/README.md +146 -0
- data/Rakefile +20 -0
- data/bin/lhm-kill-queue +172 -0
- data/bin/lhm-spec-clobber.sh +36 -0
- data/bin/lhm-spec-grants.sh +25 -0
- data/bin/lhm-spec-setup-cluster.sh +67 -0
- data/bin/lhm-test-all.sh +10 -0
- data/gemfiles/ar-2.3_mysql.gemfile +5 -0
- data/gemfiles/ar-3.2_mysql.gemfile +5 -0
- data/gemfiles/ar-3.2_mysql2.gemfile +5 -0
- data/lhm.gemspec +27 -0
- data/lib/lhm.rb +45 -0
- data/lib/lhm/atomic_switcher.rb +49 -0
- data/lib/lhm/chunker.rb +114 -0
- data/lib/lhm/command.rb +46 -0
- data/lib/lhm/entangler.rb +98 -0
- data/lib/lhm/intersection.rb +63 -0
- data/lib/lhm/invoker.rb +49 -0
- data/lib/lhm/locked_switcher.rb +71 -0
- data/lib/lhm/migration.rb +30 -0
- data/lib/lhm/migrator.rb +219 -0
- data/lib/lhm/sql_helper.rb +85 -0
- data/lib/lhm/table.rb +97 -0
- data/lib/lhm/version.rb +6 -0
- data/spec/.lhm.example +4 -0
- data/spec/README.md +51 -0
- data/spec/bootstrap.rb +13 -0
- data/spec/fixtures/destination.ddl +6 -0
- data/spec/fixtures/origin.ddl +6 -0
- data/spec/fixtures/small_table.ddl +4 -0
- data/spec/fixtures/users.ddl +12 -0
- data/spec/integration/atomic_switcher_spec.rb +42 -0
- data/spec/integration/chunker_spec.rb +32 -0
- data/spec/integration/entangler_spec.rb +66 -0
- data/spec/integration/integration_helper.rb +140 -0
- data/spec/integration/lhm_spec.rb +204 -0
- data/spec/integration/locked_switcher_spec.rb +42 -0
- data/spec/integration/table_spec.rb +48 -0
- data/spec/unit/atomic_switcher_spec.rb +31 -0
- data/spec/unit/chunker_spec.rb +111 -0
- data/spec/unit/entangler_spec.rb +76 -0
- data/spec/unit/intersection_spec.rb +39 -0
- data/spec/unit/locked_switcher_spec.rb +51 -0
- data/spec/unit/migration_spec.rb +23 -0
- data/spec/unit/migrator_spec.rb +134 -0
- data/spec/unit/sql_helper_spec.rb +32 -0
- data/spec/unit/table_spec.rb +34 -0
- data/spec/unit/unit_helper.rb +14 -0
- metadata +173 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require 'lhm/command'
|
5
|
+
require 'lhm/sql_helper'
|
6
|
+
|
7
|
+
module Lhm
|
8
|
+
class Entangler
|
9
|
+
include Command
|
10
|
+
include SqlHelper
|
11
|
+
|
12
|
+
attr_reader :connection
|
13
|
+
|
14
|
+
# Creates entanglement between two tables. All creates, updates and deletes
|
15
|
+
# to origin will be repeated on the destination table.
|
16
|
+
def initialize(migration, connection = nil)
|
17
|
+
@common = migration.intersection
|
18
|
+
@origin = migration.origin
|
19
|
+
@destination = migration.destination
|
20
|
+
@connection = connection
|
21
|
+
end
|
22
|
+
|
23
|
+
def entangle
|
24
|
+
[
|
25
|
+
create_delete_trigger,
|
26
|
+
create_insert_trigger,
|
27
|
+
create_update_trigger
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
def untangle
|
32
|
+
[
|
33
|
+
"drop trigger if exists `#{ trigger(:del) }`",
|
34
|
+
"drop trigger if exists `#{ trigger(:ins) }`",
|
35
|
+
"drop trigger if exists `#{ trigger(:upd) }`"
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_insert_trigger
|
40
|
+
strip %Q{
|
41
|
+
create trigger `#{ trigger(:ins) }`
|
42
|
+
after insert on `#{ @origin.name }` for each row
|
43
|
+
replace into `#{ @destination.name }` (#{ @common.combined_joined }) #{ SqlHelper.annotation }
|
44
|
+
values (#{ @common.combined_typed("NEW") })
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_update_trigger
|
49
|
+
strip %Q{
|
50
|
+
create trigger `#{ trigger(:upd) }`
|
51
|
+
after update on `#{ @origin.name }` for each row
|
52
|
+
replace into `#{ @destination.name }` (#{ @common.joined }) #{ SqlHelper.annotation }
|
53
|
+
values (#{ @common.typed("NEW") })
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_delete_trigger
|
58
|
+
strip %Q{
|
59
|
+
create trigger `#{ trigger(:del) }`
|
60
|
+
after delete on `#{ @origin.name }` for each row
|
61
|
+
delete ignore from `#{ @destination.name }` #{ SqlHelper.annotation }
|
62
|
+
where `#{ @destination.name }`.`id` = OLD.`id`
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def trigger(type)
|
67
|
+
"lhmt_#{ type }_#{ @origin.name }"
|
68
|
+
end
|
69
|
+
|
70
|
+
def validate
|
71
|
+
unless table?(@origin.name)
|
72
|
+
error("#{ @origin.name } does not exist")
|
73
|
+
end
|
74
|
+
|
75
|
+
unless table?(@destination.name)
|
76
|
+
error("#{ @destination.name } does not exist")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def before
|
81
|
+
sql(entangle)
|
82
|
+
end
|
83
|
+
|
84
|
+
def after
|
85
|
+
sql(untangle)
|
86
|
+
end
|
87
|
+
|
88
|
+
def revert
|
89
|
+
after
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def strip(sql)
|
95
|
+
sql.strip.gsub(/\n */, "\n")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright (c) 2011, 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, insert_trigger_additions)
|
8
|
+
@origin = origin
|
9
|
+
@destination = destination
|
10
|
+
@insert_trigger_additions = insert_trigger_additions
|
11
|
+
end
|
12
|
+
|
13
|
+
def common
|
14
|
+
(@origin.columns.keys & @destination.columns.keys).sort
|
15
|
+
end
|
16
|
+
|
17
|
+
def insert
|
18
|
+
@insert_trigger_additions.keys
|
19
|
+
end
|
20
|
+
|
21
|
+
def escaped_insert
|
22
|
+
insert.map { |name| tick(name) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def combined_joined
|
26
|
+
(escaped + escaped_insert).join(", ")
|
27
|
+
end
|
28
|
+
|
29
|
+
def combined_typed(type)
|
30
|
+
(common.map { |name| qualified(name, type) } + @insert_trigger_additions.values.map { |name| parenthesize(name) }).join(", ")
|
31
|
+
end
|
32
|
+
|
33
|
+
def escaped
|
34
|
+
common.map { |name| tick(name) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def joined
|
38
|
+
escaped.join(", ")
|
39
|
+
end
|
40
|
+
|
41
|
+
def typed_unjoined(type)
|
42
|
+
common.map { |name| qualified(name, type) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def typed(type)
|
46
|
+
common.map { |name| qualified(name, type) }.join(", ")
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def qualified(name, type)
|
52
|
+
"#{ type }.`#{ name }`"
|
53
|
+
end
|
54
|
+
|
55
|
+
def tick(name)
|
56
|
+
"`#{ name }`"
|
57
|
+
end
|
58
|
+
|
59
|
+
def parenthesize(name)
|
60
|
+
"(#{ name })"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/lhm/invoker.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Copyright (c) 2011, 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
|
+
|
19
|
+
attr_reader :migrator, :connection
|
20
|
+
|
21
|
+
def initialize(origin, connection)
|
22
|
+
@connection = connection
|
23
|
+
@migrator = Migrator.new(origin, connection)
|
24
|
+
end
|
25
|
+
|
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
|
+
|
37
|
+
migration = @migrator.run
|
38
|
+
|
39
|
+
Entangler.new(migration, @connection).run do
|
40
|
+
Chunker.new(migration, @connection, options).run
|
41
|
+
if options[:atomic_switch]
|
42
|
+
AtomicSwitcher.new(migration, @connection).run
|
43
|
+
else
|
44
|
+
LockedSwitcher.new(migration, @connection).run
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Copyright (c) 2011, 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(&block)
|
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 table?(@origin.name) && table?(@destination.name)
|
57
|
+
error "`#{ @origin.name }` and `#{ @destination.name }` must exist"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def revert
|
64
|
+
sql "unlock tables"
|
65
|
+
end
|
66
|
+
|
67
|
+
def execute
|
68
|
+
sql statements
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require 'lhm/intersection'
|
5
|
+
|
6
|
+
module Lhm
|
7
|
+
class Migration
|
8
|
+
attr_reader :origin, :destination, :insert_joins
|
9
|
+
|
10
|
+
def initialize(origin, destination, time = Time.now, insert_trigger_additions, insert_joins)
|
11
|
+
@origin = origin
|
12
|
+
@destination = destination
|
13
|
+
@start = time
|
14
|
+
@insert_trigger_additions = insert_trigger_additions
|
15
|
+
@insert_joins = insert_joins
|
16
|
+
end
|
17
|
+
|
18
|
+
def archive_name
|
19
|
+
"lhma_#{ startstamp }_#{ @origin.name }"
|
20
|
+
end
|
21
|
+
|
22
|
+
def intersection
|
23
|
+
Intersection.new(@origin, @destination, @insert_trigger_additions)
|
24
|
+
end
|
25
|
+
|
26
|
+
def startstamp
|
27
|
+
@start.strftime "%Y_%m_%d_%H_%M_%S_#{ "%03d" % (@start.usec / 1000) }"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/lhm/migrator.rb
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require 'lhm/command'
|
5
|
+
require 'lhm/migration'
|
6
|
+
require 'lhm/sql_helper'
|
7
|
+
require 'lhm/table'
|
8
|
+
|
9
|
+
module Lhm
|
10
|
+
# Copies existing schema and applies changes using alter on the empty table.
|
11
|
+
# `run` returns a Migration which can be used for the remaining process.
|
12
|
+
class Migrator
|
13
|
+
include Command
|
14
|
+
include SqlHelper
|
15
|
+
|
16
|
+
attr_reader :name, :statements, :connection
|
17
|
+
|
18
|
+
def initialize(table, connection = nil)
|
19
|
+
@connection = connection
|
20
|
+
@origin = table
|
21
|
+
@name = table.destination_name
|
22
|
+
@statements = []
|
23
|
+
@insert_trigger_additions = {}
|
24
|
+
@insert_joins = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# Alter a table with a custom statement
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
#
|
31
|
+
# Lhm.change_table(:users) do |m|
|
32
|
+
# m.ddl("ALTER TABLE #{m.name} ADD COLUMN age INT(11)")
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @param [String] statement SQL alter statement
|
36
|
+
# @note
|
37
|
+
#
|
38
|
+
# Don't write the table name directly into the statement. Use the #name
|
39
|
+
# getter instead, because the alter statement will be executed against a
|
40
|
+
# temporary table.
|
41
|
+
#
|
42
|
+
def ddl(statement)
|
43
|
+
statements << statement
|
44
|
+
end
|
45
|
+
|
46
|
+
# Adds joins to the chunked insert. Helpful if you would need to do an update
|
47
|
+
# after the change_table
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
#
|
51
|
+
# Lhm.change_table(:users) do |m|
|
52
|
+
# m.add_column(:comment, "VARCHAR(12) DEFAULT '0'")
|
53
|
+
# m.join_on_insert(:people, :description, :comment, "people.user_id = users.id")
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# @param [String] table Table to join to
|
57
|
+
# @param [String] origin_field Column in the origin (joined) table
|
58
|
+
# @param [String] destination_field Column in the destination table
|
59
|
+
# @param [String] statement Valid sql join statement
|
60
|
+
def join_on_insert(table, origin_field, destination_field, statement)
|
61
|
+
@insert_joins << { :table => table, :origin_field => origin_field, :destination_field => destination_field, :statement => statement }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Adds additional columns to the trigger that is created for inserts.
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
#
|
68
|
+
# Lhm.change_table(:users) do |m|
|
69
|
+
# m.add_column(:comment, "VARCHAR(12) DEFAULT '0'")
|
70
|
+
# m.insert_trigger(:comment, "SELECT comment FROM people WHERE NEW.id = people.id")
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# @param [String] key Column to insert value
|
74
|
+
# @param [String] statement Valid sql query, can use NEW to reference the row in trigger
|
75
|
+
def insert_trigger(key, statement)
|
76
|
+
@insert_trigger_additions[key] = statement
|
77
|
+
end
|
78
|
+
|
79
|
+
# Add a column to a table
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
#
|
83
|
+
# Lhm.change_table(:users) do |m|
|
84
|
+
# m.add_column(:comment, "VARCHAR(12) DEFAULT '0'")
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# @param [String] name Name of the column to add
|
88
|
+
# @param [String] definition Valid SQL column definition
|
89
|
+
def add_column(name, definition)
|
90
|
+
ddl("alter table `%s` add column `%s` %s" % [@name, name, definition])
|
91
|
+
end
|
92
|
+
|
93
|
+
# Change an existing column to a new definition
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
#
|
97
|
+
# Lhm.change_table(:users) do |m|
|
98
|
+
# m.change_column(:comment, "VARCHAR(12) DEFAULT '0' NOT NULL")
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# @param [String] name Name of the column to change
|
102
|
+
# @param [String] definition Valid SQL column definition
|
103
|
+
def change_column(name, definition)
|
104
|
+
ddl("alter table `%s` modify column `%s` %s" % [@name, name, definition])
|
105
|
+
end
|
106
|
+
|
107
|
+
# Remove a column from a table
|
108
|
+
#
|
109
|
+
# @example
|
110
|
+
#
|
111
|
+
# Lhm.change_table(:users) do |m|
|
112
|
+
# m.remove_column(:comment)
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# @param [String] name Name of the column to delete
|
116
|
+
def remove_column(name)
|
117
|
+
ddl("alter table `%s` drop `%s`" % [@name, name])
|
118
|
+
end
|
119
|
+
|
120
|
+
# Add an index to a table
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
#
|
124
|
+
# Lhm.change_table(:users) do |m|
|
125
|
+
# m.add_index(:comment)
|
126
|
+
# m.add_index([:username, :created_at])
|
127
|
+
# m.add_index("comment(10)")
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# @param [String, Symbol, Array<String, Symbol>] columns
|
131
|
+
# A column name given as String or Symbol. An Array of Strings or Symbols
|
132
|
+
# for compound indexes. It's possible to pass a length limit.
|
133
|
+
# @param [String, Symbol] index_name
|
134
|
+
# Optional name of the index to be created
|
135
|
+
def add_index(columns, index_name = nil)
|
136
|
+
ddl(index_ddl(columns, false, index_name))
|
137
|
+
end
|
138
|
+
|
139
|
+
# Add a unique index to a table
|
140
|
+
#
|
141
|
+
# @example
|
142
|
+
#
|
143
|
+
# Lhm.change_table(:users) do |m|
|
144
|
+
# m.add_unique_index(:comment)
|
145
|
+
# m.add_unique_index([:username, :created_at])
|
146
|
+
# m.add_unique_index("comment(10)")
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# @param [String, Symbol, Array<String, Symbol>] columns
|
150
|
+
# A column name given as String or Symbol. An Array of Strings or Symbols
|
151
|
+
# for compound indexes. It's possible to pass a length limit.
|
152
|
+
# @param [String, Symbol] index_name
|
153
|
+
# Optional name of the index to be created
|
154
|
+
def add_unique_index(columns, index_name = nil)
|
155
|
+
ddl(index_ddl(columns, true, index_name))
|
156
|
+
end
|
157
|
+
|
158
|
+
# Remove an index from a table
|
159
|
+
#
|
160
|
+
# @example
|
161
|
+
#
|
162
|
+
# Lhm.change_table(:users) do |m|
|
163
|
+
# m.remove_index(:comment)
|
164
|
+
# m.remove_index([:username, :created_at])
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
# @param [String, Symbol, Array<String, Symbol>] columns
|
168
|
+
# A column name given as String or Symbol. An Array of Strings or Symbols
|
169
|
+
# for compound indexes.
|
170
|
+
# @param [String, Symbol] index_name
|
171
|
+
# Optional name of the index to be removed
|
172
|
+
def remove_index(columns, index_name = nil)
|
173
|
+
index_name ||= idx_name(@origin.name, columns)
|
174
|
+
ddl("drop index `%s` on `%s`" % [index_name, @name])
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def validate
|
180
|
+
unless table?(@origin.name)
|
181
|
+
error("could not find origin table #{ @origin.name }")
|
182
|
+
end
|
183
|
+
|
184
|
+
unless @origin.satisfies_primary_key?
|
185
|
+
error("origin does not satisfy primary key requirements")
|
186
|
+
end
|
187
|
+
|
188
|
+
dest = @origin.destination_name
|
189
|
+
|
190
|
+
if table?(dest)
|
191
|
+
error("#{ dest } should not exist; not cleaned up from previous run?")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def execute
|
196
|
+
destination_create
|
197
|
+
sql(@statements)
|
198
|
+
Migration.new(@origin, destination_read, @insert_trigger_additions, @insert_joins)
|
199
|
+
end
|
200
|
+
|
201
|
+
def destination_create
|
202
|
+
original = "CREATE TABLE `#{ @origin.name }`"
|
203
|
+
replacement = "CREATE TABLE `#{ @origin.destination_name }`"
|
204
|
+
|
205
|
+
sql(@origin.ddl.gsub(original, replacement))
|
206
|
+
end
|
207
|
+
|
208
|
+
def destination_read
|
209
|
+
Table.parse(@origin.destination_name, connection)
|
210
|
+
end
|
211
|
+
|
212
|
+
def index_ddl(cols, unique = nil, index_name = nil)
|
213
|
+
type = unique ? "unique index" : "index"
|
214
|
+
index_name ||= idx_name(@origin.name, cols)
|
215
|
+
parts = [type, index_name, @name, idx_spec(cols)]
|
216
|
+
"create %s `%s` on `%s` (%s)" % parts
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|