miguel 0.1.0.pre1
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/.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 #
|