forked from soundcloud/lhm
/
migrator.rb
177 lines (156 loc) · 4.78 KB
/
migrator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
# Schmidt
require 'lhm/command'
require 'lhm/migration'
require 'lhm/sql_helper'
require 'lhm/table'
module Lhm
# Copies existing schema and applies changes using alter on the empty table.
# `run` returns a Migration which can be used for the remaining process.
class Migrator
include Command
include SqlHelper
attr_reader :name, :statements, :connection
def initialize(table, connection = nil)
@connection = connection
@origin = table
@name = table.destination_name
@statements = []
end
# Alter a table with a custom statement
#
# @example
#
# Lhm.change_table(:users) do |m|
# m.ddl("ALTER TABLE #{m.name} ADD COLUMN age INT(11)")
# end
#
# @param [String] statement SQL alter statement
# @note
#
# Don't write the table name directly into the statement. Use the #name
# getter instead, because the alter statement will be executed against a
# temporary table.
#
def ddl(statement)
statements << statement
end
# Add a column to a table
#
# @example
#
# Lhm.change_table(:users) do |m|
# m.add_column(:comment, "VARCHAR(12) DEFAULT '0'")
# end
#
# @param [String] name Name of the column to add
# @param [String] definition Valid SQL column definition
def add_column(name, definition)
ddl("alter table `%s` add column `%s` %s" % [@name, name, definition])
end
# Change an existing column to a new definition
#
# @example
#
# Lhm.change_table(:users) do |m|
# m.change_column(:comment, "VARCHAR(12) DEFAULT '0' NOT NULL")
# end
#
# @param [String] name Name of the column to change
# @param [String] definition Valid SQL column definition
def change_column(name, definition)
remove_column(name)
add_column(name, definition)
end
# Remove a column from a table
#
# @example
#
# Lhm.change_table(:users) do |m|
# m.remove_column(:comment)
# end
#
# @param [String] name Name of the column to delete
def remove_column(name)
ddl("alter table `%s` drop `%s`" % [@name, name])
end
# Add an index to a table
#
# @example
#
# Lhm.change_table(:users) do |m|
# m.add_index(:comment)
# m.add_index([:username, :created_at])
# m.add_index("comment(10)")
# end
#
# @param [String, Symbol, Array<String, Symbol>] columns
# A column name given as String or Symbol. An Array of Strings or Symbols
# for compound indexes. It's possible to pass a length limit.
def add_index(columns)
ddl(index_ddl(columns))
end
# Add a unique index to a table
#
# @example
#
# Lhm.change_table(:users) do |m|
# m.add_unique_index(:comment)
# m.add_unique_index([:username, :created_at])
# m.add_unique_index("comment(10)")
# end
#
# @param [String, Symbol, Array<String, Symbol>] columns
# A column name given as String or Symbol. An Array of Strings or Symbols
# for compound indexes. It's possible to pass a length limit.
def add_unique_index(columns)
ddl(index_ddl(columns, :unique))
end
# Remove an index from a table
#
# @example
#
# Lhm.change_table(:users) do |m|
# m.remove_index(:comment)
# m.remove_index([:username, :created_at])
# end
#
# @param [String, Symbol, Array<String, Symbol>] columns
# A column name given as String or Symbol. An Array of Strings or Symbols
# for compound indexes.
def remove_index(columns)
ddl("drop index `%s` on `%s`" % [idx_name(@origin.name, columns), @name])
end
private
def validate
unless table?(@origin.name)
error("could not find origin table #{ @origin.name }")
end
unless @origin.satisfies_primary_key?
error("origin does not satisfy primary key requirements")
end
dest = @origin.destination_name
if table?(dest)
error("#{ dest } should not exist; not cleaned up from previous run?")
end
end
def execute
destination_create
sql(@statements)
Migration.new(@origin, destination_read)
end
def destination_create
original = "CREATE TABLE `#{ @origin.name }`"
replacement = "CREATE TABLE `#{ @origin.destination_name }`"
sql(@origin.ddl.gsub(original, replacement))
end
def destination_read
Table.parse(@origin.destination_name, connection)
end
def index_ddl(cols, unique = nil)
type = unique ? "unique index" : "index"
parts = [type, idx_name(@origin.name, cols), @name, idx_spec(cols)]
"create %s `%s` on `%s` (%s)" % parts
end
end
end