miguel 0.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Rakefile +10 -0
- data/TODO +2 -0
- data/bin/miguel +9 -0
- data/lib/miguel/command.rb +242 -0
- data/lib/miguel/dumper.rb +45 -0
- data/lib/miguel/importer.rb +232 -0
- data/lib/miguel/migrator.rb +242 -0
- data/lib/miguel/schema.rb +585 -0
- data/lib/miguel/version.rb +10 -0
- data/lib/miguel.rb +3 -0
- data/miguel.gemspec +26 -0
- data/test/test_dumper.rb +90 -0
- metadata +87 -0
@@ -0,0 +1,242 @@
|
|
1
|
+
# Schema migrator.
|
2
|
+
|
3
|
+
require 'miguel/schema'
|
4
|
+
require 'miguel/dumper'
|
5
|
+
|
6
|
+
module Miguel
|
7
|
+
|
8
|
+
# Class for generating database migration from one schema to another.
|
9
|
+
class Migrator
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# Separate items in before and after arrays into old items, same items and new items.
|
14
|
+
def separate( before, after )
|
15
|
+
# Note that we have to use ==, so we can't use & and - operators which use eql?.
|
16
|
+
same = after.select{ |x| before.find{ |y| x == y } }
|
17
|
+
[
|
18
|
+
before.reject{ |x| same.find{ |y| x == y } },
|
19
|
+
same,
|
20
|
+
after.reject{ |x| same.find{ |y| x == y } },
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Convert foreign keys from given tables into [ table name, foreign key ] pairs for easier comparison.
|
25
|
+
def prepare_keys( tables )
|
26
|
+
result = []
|
27
|
+
for table in tables
|
28
|
+
for key in table.foreign_keys
|
29
|
+
result << [ table.name, key ]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
# Convert [ table name, foreign key ] pairs into hash of foreign keys per table.
|
36
|
+
def split_keys( table_keys )
|
37
|
+
result = {}
|
38
|
+
for name, key in table_keys
|
39
|
+
( result[ name ] ||= [] ) << key
|
40
|
+
end
|
41
|
+
result
|
42
|
+
end
|
43
|
+
|
44
|
+
# Generate code for adding given foreign keys.
|
45
|
+
def dump_add_foreign_keys( out, table_keys )
|
46
|
+
for name, keys in split_keys( table_keys )
|
47
|
+
out.dump "alter_table #{name.inspect}" do
|
48
|
+
for key in keys
|
49
|
+
out << "add_foreign_key #{key.out_columns}, #{key.out_table_name}#{key.out_canonic_opts}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Generate code for dropping given foreign keys.
|
56
|
+
def dump_drop_foreign_keys( out, table_keys )
|
57
|
+
for name, keys in split_keys( table_keys )
|
58
|
+
out.dump "alter_table #{name.inspect}" do
|
59
|
+
for key in keys
|
60
|
+
out << "drop_foreign_key #{key.out_columns} # #{key.out_table_name}#{key.out_canonic_opts}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Generate code for adding given tables.
|
67
|
+
def dump_add_tables( out, tables )
|
68
|
+
for table in tables
|
69
|
+
out.dump "create_table #{table.out_name}" do
|
70
|
+
for column in table.columns
|
71
|
+
column.dump( out )
|
72
|
+
end
|
73
|
+
for index in table.indexes
|
74
|
+
index.dump( out )
|
75
|
+
end
|
76
|
+
# No foreign keys here - those are added in a separate pass later.
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Generate code for dropping given tables.
|
82
|
+
def dump_drop_tables( out, tables )
|
83
|
+
for table in tables
|
84
|
+
out << "drop_table #{table.out_name}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Generate code for dropping given indexes.
|
89
|
+
def dump_drop_indexes( out, indexes )
|
90
|
+
for index in indexes
|
91
|
+
out << "drop_index #{index.out_columns}#{index.out_canonic_opts(' # ')}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Generate code for adding given indexes.
|
96
|
+
def dump_add_indexes( out, indexes )
|
97
|
+
for index in indexes
|
98
|
+
out << "add_index #{index.out_columns}#{index.out_canonic_opts}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Generate code for dropping given columns.
|
103
|
+
def dump_drop_columns( out, columns )
|
104
|
+
for column in columns
|
105
|
+
if column.type == :primary_key
|
106
|
+
out << "drop_constraint #{column.out_name}, :type => :primary_key#{column.out_opts(' # ')}"
|
107
|
+
else
|
108
|
+
out << "drop_column #{column.out_name} # #{column.out_type}#{column.out_opts}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Generate code for adding given columns.
|
114
|
+
def dump_add_columns( out, columns )
|
115
|
+
for column in columns
|
116
|
+
if column.type == :primary_key
|
117
|
+
out << "add_primary_key #{column.out_name}#{column.out_opts}"
|
118
|
+
else
|
119
|
+
out << "add_column #{column.out_name}, #{column.out_type}#{column.out_opts}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Generate code for altering given column.
|
125
|
+
def dump_alter_column( out, from, to )
|
126
|
+
if from.allow_null != to.allow_null && to.allow_null
|
127
|
+
out << "set_column_allow_null #{to.out_name}"
|
128
|
+
end
|
129
|
+
if from.type_opts != to.type_opts
|
130
|
+
out << "set_column_type #{to.out_name}, #{to.out_type}#{to.out_opts}"
|
131
|
+
end
|
132
|
+
if from.default != to.default
|
133
|
+
out << "set_column_default #{to.out_name}, #{to.out_default}"
|
134
|
+
end
|
135
|
+
if from.allow_null != to.allow_null && ! to.allow_null
|
136
|
+
out << "set_column_not_null #{to.out_name}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Generate code for altering given columns.
|
141
|
+
def dump_alter_columns( out, from_columns, to_columns )
|
142
|
+
pairs = from_columns.zip( to_columns )
|
143
|
+
for from, to in pairs
|
144
|
+
fail "invalid column pair #{from.name} -> #{to.name}" unless from.name == to.name
|
145
|
+
dump_alter_column( out, from, to )
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Generate code for altering given table.
|
150
|
+
def dump_alter_table( out, from, to )
|
151
|
+
old_indexes, same_indexes, new_indexes = separate( from.indexes, to.indexes )
|
152
|
+
|
153
|
+
from_names = from.column_names
|
154
|
+
to_names = to.column_names
|
155
|
+
|
156
|
+
old_names, same_names, new_names = separate( from_names, to_names )
|
157
|
+
|
158
|
+
old_columns = from.named_columns( old_names )
|
159
|
+
new_columns = to.named_columns( new_names )
|
160
|
+
|
161
|
+
from_columns = from.named_columns( same_names )
|
162
|
+
to_columns = to.named_columns( same_names )
|
163
|
+
|
164
|
+
from_columns, same_columns, to_columns = separate( from_columns, to_columns )
|
165
|
+
|
166
|
+
return if [ old_indexes, new_indexes, old_columns, new_columns, to_columns ].all?{ |x| x.empty? }
|
167
|
+
|
168
|
+
out.dump "alter_table #{to.out_name}" do
|
169
|
+
dump_drop_indexes( out, old_indexes )
|
170
|
+
dump_drop_columns( out, old_columns )
|
171
|
+
dump_alter_columns( out, from_columns, to_columns )
|
172
|
+
dump_add_columns( out, new_columns )
|
173
|
+
dump_add_indexes( out, new_indexes )
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Generate code for altering given tables.
|
178
|
+
def dump_alter_tables( out, from_tables, to_tables )
|
179
|
+
pairs = from_tables.zip( to_tables )
|
180
|
+
for from, to in pairs
|
181
|
+
fail "invalid table pair #{from.name} -> #{to.name}" unless from.name == to.name
|
182
|
+
dump_alter_table( out, from, to )
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
public
|
187
|
+
|
188
|
+
# Generate code for changing one schema to another.
|
189
|
+
def changes( from, to, out = Dumper.new )
|
190
|
+
from_keys = prepare_keys( from.tables )
|
191
|
+
to_keys = prepare_keys( to.tables )
|
192
|
+
|
193
|
+
old_keys, same_keys, new_keys = separate( from_keys, to_keys )
|
194
|
+
|
195
|
+
from_names = from.table_names
|
196
|
+
to_names = to.table_names
|
197
|
+
|
198
|
+
old_names, same_names, new_names = separate( from_names, to_names )
|
199
|
+
|
200
|
+
old_tables = from.named_tables( old_names )
|
201
|
+
new_tables = to.named_tables( new_names )
|
202
|
+
|
203
|
+
from_tables = from.named_tables( same_names )
|
204
|
+
to_tables = to.named_tables( same_names )
|
205
|
+
|
206
|
+
dump_drop_foreign_keys( out, old_keys )
|
207
|
+
dump_drop_tables( out, old_tables )
|
208
|
+
dump_alter_tables( out, from_tables, to_tables )
|
209
|
+
dump_add_tables( out, new_tables )
|
210
|
+
dump_add_foreign_keys( out, new_keys )
|
211
|
+
|
212
|
+
out
|
213
|
+
end
|
214
|
+
|
215
|
+
# Generate one way Sequel migration.
|
216
|
+
def change_migration( from, to, out = Dumper.new )
|
217
|
+
out.dump "Sequel.migration" do
|
218
|
+
out.dump "change" do
|
219
|
+
changes( from, to, out )
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Generate both ways Sequel migration.
|
225
|
+
def full_migration( from, to, out = Dumper.new )
|
226
|
+
out.dump "Sequel.migration" do
|
227
|
+
out.dump "up" do
|
228
|
+
changes( from, to, out )
|
229
|
+
end
|
230
|
+
out.dump "down" do
|
231
|
+
changes( to, from, out )
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
alias migration full_migration
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
# EOF #
|