active_column 0.1.1 → 0.2
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 +1 -0
- data/.rvmrc +1 -7
- data/Gemfile.lock +60 -61
- data/README.md +7 -3
- data/active_column.gemspec +2 -3
- data/docs/Migrate.md +30 -7
- data/generators/ks_migration/USAGE +6 -0
- data/generators/ks_migration/ks_migration_generator.rb +17 -0
- data/generators/ks_migration/templates/migration.rb.erb +11 -0
- data/lib/active_column.rb +16 -9
- data/lib/active_column/configuration.rb +36 -0
- data/lib/active_column/helpers.rb +36 -0
- data/lib/active_column/migration.rb +76 -214
- data/lib/active_column/migrator.rb +231 -0
- data/lib/active_column/tasks/column_family.rb +28 -17
- data/lib/active_column/tasks/keyspace.rb +38 -3
- data/lib/active_column/tasks/ks.rake +142 -0
- data/lib/active_column/version.rb +1 -1
- data/spec/active_column/migration_spec.rb +58 -0
- data/spec/active_column/tasks/column_family_spec.rb +4 -2
- data/spec/active_column/tasks/keyspace_spec.rb +104 -4
- data/spec/spec_helper.rb +36 -11
- metadata +24 -44
- data/lib/active_column/connection.rb +0 -15
- data/lib/active_column/tasks/ks.rb +0 -76
@@ -1,267 +1,129 @@
|
|
1
1
|
module ActiveColumn
|
2
2
|
|
3
|
-
class
|
4
|
-
end
|
3
|
+
class Migration
|
5
4
|
|
6
|
-
|
7
|
-
def initialize(version)
|
8
|
-
super("Multiple migrations have the version number #{version}")
|
9
|
-
end
|
10
|
-
end
|
5
|
+
@@verbose = true
|
11
6
|
|
12
|
-
|
13
|
-
|
14
|
-
super("Multiple migrations have the name #{name}")
|
7
|
+
def self.verbose=(verbose)
|
8
|
+
@@verbose = verbose
|
15
9
|
end
|
16
|
-
end
|
17
10
|
|
18
|
-
|
19
|
-
|
20
|
-
super("No migration with version number #{version}")
|
11
|
+
def self.verbose
|
12
|
+
@@verbose
|
21
13
|
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class IllegalMigrationNameError < ActiveColumnError#:nodoc:
|
25
|
-
def initialize(name)
|
26
|
-
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
class Migration
|
31
14
|
|
15
|
+
# Returns the raw connection to Cassandra
|
32
16
|
def self.connection
|
33
|
-
|
17
|
+
ActiveColumn.connection
|
34
18
|
end
|
35
19
|
|
36
20
|
def self.migrate(direction)
|
37
21
|
return unless respond_to?(direction)
|
38
|
-
send direction
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.create_column_family(name, options = {})
|
42
|
-
ActiveColumn::Tasks::ColumnFamily.new.create(name, options)
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.drop_column_family(name)
|
46
|
-
ActiveColumn::Tasks::ColumnFamily.new.drop(name)
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
# MigrationProxy is used to defer loading of the actual migration classes
|
52
|
-
# until they are needed
|
53
|
-
class MigrationProxy
|
54
|
-
|
55
|
-
attr_accessor :name, :version, :filename
|
56
|
-
|
57
|
-
delegate :migrate, :announce, :write, :to=>:migration
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def migration
|
62
|
-
@migration ||= load_migration
|
63
|
-
end
|
64
|
-
|
65
|
-
def load_migration
|
66
|
-
require(File.expand_path(filename))
|
67
|
-
name.constantize
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
class Migrator
|
73
22
|
|
74
|
-
|
75
|
-
|
76
|
-
when
|
77
|
-
up(migrations_path, target_version)
|
78
|
-
when current_version == 0 && target_version == 0
|
79
|
-
when current_version > target_version
|
80
|
-
down(migrations_path, target_version)
|
81
|
-
else
|
82
|
-
up(migrations_path, target_version)
|
23
|
+
case direction
|
24
|
+
when :up then announce "migrating"
|
25
|
+
when :down then announce "reverting"
|
83
26
|
end
|
84
|
-
end
|
85
27
|
|
86
|
-
|
87
|
-
|
88
|
-
end
|
28
|
+
result = nil
|
29
|
+
time = Benchmark.measure { result = send("#{direction}") }
|
89
30
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
def self.up(migrations_path, target_version = nil)
|
95
|
-
self.new(:up, migrations_path, target_version).migrate
|
96
|
-
end
|
97
|
-
|
98
|
-
def self.down(migrations_path, target_version = nil)
|
99
|
-
self.new(:down, migrations_path, target_version).migrate
|
100
|
-
end
|
31
|
+
case direction
|
32
|
+
when :up then announce "migrated (%.4fs)" % time.real; write
|
33
|
+
when :down then announce "reverted (%.4fs)" % time.real; write
|
34
|
+
end
|
101
35
|
|
102
|
-
|
103
|
-
self.new(direction, migrations_path, target_version).run
|
36
|
+
result
|
104
37
|
end
|
105
38
|
|
106
|
-
|
107
|
-
|
39
|
+
# Creates a new column family with the given name. Column family configurations can be set within
|
40
|
+
# a block like this:
|
41
|
+
#
|
42
|
+
# create_column_family(:users) do |cf|
|
43
|
+
# cf.comment = 'Users column family'
|
44
|
+
# cf.comparator_type = 'TimeUUIDType'
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# A complete list of available configuration settings is here:
|
48
|
+
#
|
49
|
+
# http://github.com/fauna/cassandra/blob/master/vendor/0.7/gen-rb/cassandra_types.rb
|
50
|
+
#
|
51
|
+
# Scroll down to the CfDef definition.
|
52
|
+
def self.create_column_family(name, &block)
|
53
|
+
ActiveColumn.column_family_tasks.create(name, &block)
|
108
54
|
end
|
109
55
|
|
110
|
-
|
111
|
-
|
56
|
+
# Drops the given column family
|
57
|
+
def self.drop_column_family(name)
|
58
|
+
ActiveColumn.column_family_tasks.drop(name)
|
112
59
|
end
|
113
60
|
|
114
|
-
|
115
|
-
|
116
|
-
|
61
|
+
# Renames the column family from the old name to the new name
|
62
|
+
def self.rename_column_family(old_name, new_name)
|
63
|
+
ActiveColumn.column_family_tasks.rename(old_name, new_name)
|
117
64
|
end
|
118
65
|
|
119
|
-
def self.
|
120
|
-
|
121
|
-
cf = ActiveColumn::Tasks::ColumnFamily.new
|
122
|
-
if cf.exists?(sm_cf)
|
123
|
-
get_all_versions.max || 0
|
124
|
-
else
|
125
|
-
0
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
private
|
130
|
-
|
131
|
-
def self.move(direction, migrations_path, steps)
|
132
|
-
migrator = self.new(direction, migrations_path)
|
133
|
-
start_index = migrator.migrations.index(migrator.current_migration)
|
134
|
-
|
135
|
-
if start_index
|
136
|
-
finish = migrator.migrations[start_index + steps]
|
137
|
-
version = finish ? finish.version : 0
|
138
|
-
send(direction, migrations_path, version)
|
139
|
-
end
|
66
|
+
def self.write(text="")
|
67
|
+
puts(text) if verbose
|
140
68
|
end
|
141
69
|
|
142
|
-
|
70
|
+
def self.announce(message)
|
71
|
+
version = defined?(@version) ? @version : nil
|
143
72
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
unless cf.exists?(sm_cf)
|
149
|
-
cf.create(sm_cf, :comparator_type => 'LongType')
|
150
|
-
end
|
151
|
-
|
152
|
-
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
73
|
+
text = "#{version} #{name}: #{message}"
|
74
|
+
length = [0, 75 - text.length].max
|
75
|
+
write "== %s %s" % [text, "=" * length]
|
153
76
|
end
|
154
77
|
|
155
|
-
def
|
156
|
-
|
78
|
+
def self.say(message, subitem=false)
|
79
|
+
write "#{subitem ? " ->" : "--"} #{message}"
|
157
80
|
end
|
158
81
|
|
159
|
-
def
|
160
|
-
|
82
|
+
def self.say_with_time(message)
|
83
|
+
say(message)
|
84
|
+
result = nil
|
85
|
+
time = Benchmark.measure { result = yield }
|
86
|
+
say "%.4fs" % time.real, :subitem
|
87
|
+
say("#{result} rows", :subitem) if result.is_a?(Integer)
|
88
|
+
result
|
161
89
|
end
|
162
90
|
|
163
|
-
def
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
record_version_state_after_migrating(target)
|
169
|
-
end
|
91
|
+
def self.suppress_messages
|
92
|
+
save, self.verbose = verbose, false
|
93
|
+
yield
|
94
|
+
ensure
|
95
|
+
self.verbose = save
|
170
96
|
end
|
171
97
|
|
172
|
-
|
173
|
-
current = migrations.detect { |m| m.version == current_version }
|
174
|
-
target = migrations.detect { |m| m.version == @target_version }
|
175
|
-
|
176
|
-
if target.nil? && !@target_version.nil? && @target_version > 0
|
177
|
-
raise UnknownMigrationVersionError.new(@target_version)
|
178
|
-
end
|
179
|
-
|
180
|
-
start = up? ? 0 : (migrations.index(current) || 0)
|
181
|
-
finish = migrations.index(target) || migrations.size - 1
|
182
|
-
runnable = migrations[start..finish]
|
183
|
-
|
184
|
-
# skip the last migration if we're headed down, but not ALL the way down
|
185
|
-
runnable.pop if down? && !target.nil?
|
186
|
-
|
187
|
-
runnable.each do |migration|
|
188
|
-
#puts "Migrating to #{migration.name} (#{migration.version})"
|
189
|
-
|
190
|
-
# On our way up, we skip migrating the ones we've already migrated
|
191
|
-
next if up? && migrated.include?(migration.version.to_i)
|
192
|
-
|
193
|
-
# On our way down, we skip reverting the ones we've never migrated
|
194
|
-
if down? && !migrated.include?(migration.version.to_i)
|
195
|
-
migration.announce 'never migrated, skipping'; migration.write
|
196
|
-
next
|
197
|
-
end
|
198
|
-
|
199
|
-
migration.migrate(@direction)
|
200
|
-
record_version_state_after_migrating(migration)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def migrations
|
205
|
-
@migrations ||= begin
|
206
|
-
files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
|
207
|
-
|
208
|
-
migrations = files.inject([]) do |klasses, file|
|
209
|
-
version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
210
|
-
|
211
|
-
raise IllegalMigrationNameError.new(file) unless version
|
212
|
-
version = version.to_i
|
213
|
-
|
214
|
-
if klasses.detect { |m| m.version == version }
|
215
|
-
raise DuplicateMigrationVersionError.new(version)
|
216
|
-
end
|
98
|
+
end
|
217
99
|
|
218
|
-
|
219
|
-
|
220
|
-
|
100
|
+
# MigrationProxy is used to defer loading of the actual migration classes
|
101
|
+
# until they are needed
|
102
|
+
class MigrationProxy
|
221
103
|
|
222
|
-
|
223
|
-
migration.name = name.camelize
|
224
|
-
migration.version = version
|
225
|
-
migration.filename = file
|
226
|
-
klasses << migration
|
227
|
-
end
|
104
|
+
attr_accessor :name, :version, :filename
|
228
105
|
|
229
|
-
|
230
|
-
|
231
|
-
end
|
106
|
+
def migrate(*args)
|
107
|
+
migration.migrate *args
|
232
108
|
end
|
233
109
|
|
234
|
-
def
|
235
|
-
|
236
|
-
migrations.reject { |m| already_migrated.include?(m.version.to_i) }
|
110
|
+
def announce(*args)
|
111
|
+
migration.announce *args
|
237
112
|
end
|
238
113
|
|
239
|
-
def
|
240
|
-
|
114
|
+
def write(*args)
|
115
|
+
migration.write *args
|
241
116
|
end
|
242
117
|
|
243
118
|
private
|
244
119
|
|
245
|
-
def
|
246
|
-
|
247
|
-
sm_cf = self.class.schema_migrations_column_family
|
248
|
-
|
249
|
-
@migrated_versions ||= []
|
250
|
-
if down?
|
251
|
-
@migrated_versions.delete(migration.version)
|
252
|
-
cas.remove sm_cf, 'all', migration.version
|
253
|
-
else
|
254
|
-
@migrated_versions.push(migration.version).sort!
|
255
|
-
cas.insert sm_cf, 'all', { migration.version => migration.name }
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
def up?
|
260
|
-
@direction == :up
|
120
|
+
def migration
|
121
|
+
@migration ||= load_migration
|
261
122
|
end
|
262
123
|
|
263
|
-
def
|
264
|
-
|
124
|
+
def load_migration
|
125
|
+
require(File.expand_path(filename))
|
126
|
+
eval(name)
|
265
127
|
end
|
266
128
|
|
267
129
|
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
module ActiveColumn
|
2
|
+
class IrreversibleMigration < ActiveColumnError
|
3
|
+
end
|
4
|
+
|
5
|
+
class DuplicateMigrationVersionError < ActiveColumnError#:nodoc:
|
6
|
+
def initialize(version)
|
7
|
+
super("Multiple migrations have the version number #{version}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class DuplicateMigrationNameError < ActiveColumnError#:nodoc:
|
12
|
+
def initialize(name)
|
13
|
+
super("Multiple migrations have the name #{name}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class UnknownMigrationVersionError < ActiveColumnError #:nodoc:
|
18
|
+
def initialize(version)
|
19
|
+
super("No migration with version number #{version}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class IllegalMigrationNameError < ActiveColumnError#:nodoc:
|
24
|
+
def initialize(name)
|
25
|
+
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Migrator
|
30
|
+
|
31
|
+
def self.migrate(migrations_path, target_version = nil)
|
32
|
+
case
|
33
|
+
when target_version.nil?
|
34
|
+
up(migrations_path, target_version)
|
35
|
+
when current_version == 0 && target_version == 0
|
36
|
+
when current_version > target_version
|
37
|
+
down(migrations_path, target_version)
|
38
|
+
else
|
39
|
+
up(migrations_path, target_version)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.rollback(migrations_path, steps = 1)
|
44
|
+
move(:down, migrations_path, steps)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.forward(migrations_path, steps = 1)
|
48
|
+
move(:up, migrations_path, steps)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.up(migrations_path, target_version = nil)
|
52
|
+
self.new(:up, migrations_path, target_version).migrate
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.down(migrations_path, target_version = nil)
|
56
|
+
self.new(:down, migrations_path, target_version).migrate
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.run(direction, migrations_path, target_version)
|
60
|
+
self.new(direction, migrations_path, target_version).run
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.migrations_path
|
64
|
+
'ks/migrate'
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.schema_migrations_column_family
|
68
|
+
:schema_migrations
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.get_all_versions
|
72
|
+
cas = ActiveColumn.connection
|
73
|
+
cas.get(schema_migrations_column_family, 'all').map {|(name, _value)| name.to_i}.sort
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.current_version
|
77
|
+
sm_cf = schema_migrations_column_family
|
78
|
+
if ActiveColumn.column_family_tasks.exists?(sm_cf)
|
79
|
+
get_all_versions.max || 0
|
80
|
+
else
|
81
|
+
0
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def self.move(direction, migrations_path, steps)
|
88
|
+
migrator = self.new(direction, migrations_path)
|
89
|
+
start_index = migrator.migrations.index(migrator.current_migration)
|
90
|
+
|
91
|
+
if start_index
|
92
|
+
finish = migrator.migrations[start_index + steps]
|
93
|
+
version = finish ? finish.version : 0
|
94
|
+
send(direction, migrations_path, version)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
public
|
99
|
+
|
100
|
+
def initialize(direction, migrations_path, target_version = nil)
|
101
|
+
cf_tasks = ActiveColumn.column_family_tasks
|
102
|
+
sm_cf = self.class.schema_migrations_column_family
|
103
|
+
|
104
|
+
unless cf_tasks.exists?(sm_cf)
|
105
|
+
cf_tasks.create(sm_cf) do |cf|
|
106
|
+
cf.comparator_type = 'LongType'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
111
|
+
end
|
112
|
+
|
113
|
+
def current_version
|
114
|
+
migrated.last || 0
|
115
|
+
end
|
116
|
+
|
117
|
+
def current_migration
|
118
|
+
migrations.detect { |m| m.version == current_version }
|
119
|
+
end
|
120
|
+
|
121
|
+
def run
|
122
|
+
target = migrations.detect { |m| m.version == @target_version }
|
123
|
+
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
|
124
|
+
unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
|
125
|
+
target.migrate(@direction)
|
126
|
+
record_version_state_after_migrating(target)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def migrate
|
131
|
+
current = migrations.detect { |m| m.version == current_version }
|
132
|
+
target = migrations.detect { |m| m.version == @target_version }
|
133
|
+
|
134
|
+
if target.nil? && !@target_version.nil? && @target_version > 0
|
135
|
+
raise UnknownMigrationVersionError.new(@target_version)
|
136
|
+
end
|
137
|
+
|
138
|
+
start = up? ? 0 : (migrations.index(current) || 0)
|
139
|
+
finish = migrations.index(target) || migrations.size - 1
|
140
|
+
runnable = migrations[start..finish]
|
141
|
+
|
142
|
+
# skip the last migration if we're headed down, but not ALL the way down
|
143
|
+
runnable.pop if down? && !target.nil?
|
144
|
+
|
145
|
+
runnable.each do |migration|
|
146
|
+
#puts "Migrating to #{migration.name} (#{migration.version})"
|
147
|
+
|
148
|
+
# On our way up, we skip migrating the ones we've already migrated
|
149
|
+
next if up? && migrated.include?(migration.version.to_i)
|
150
|
+
|
151
|
+
# On our way down, we skip reverting the ones we've never migrated
|
152
|
+
if down? && !migrated.include?(migration.version.to_i)
|
153
|
+
migration.announce 'never migrated, skipping'; migration.write
|
154
|
+
next
|
155
|
+
end
|
156
|
+
|
157
|
+
migration.migrate(@direction)
|
158
|
+
record_version_state_after_migrating(migration)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def migrations
|
163
|
+
@migrations ||= begin
|
164
|
+
files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
|
165
|
+
|
166
|
+
migrations = files.inject([]) do |klasses, file|
|
167
|
+
version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
168
|
+
|
169
|
+
raise IllegalMigrationNameError.new(file) unless version
|
170
|
+
version = version.to_i
|
171
|
+
|
172
|
+
if klasses.detect { |m| m.version == version }
|
173
|
+
raise DuplicateMigrationVersionError.new(version)
|
174
|
+
end
|
175
|
+
|
176
|
+
if klasses.detect { |m| m.name == name.camelize }
|
177
|
+
raise DuplicateMigrationNameError.new(name.camelize)
|
178
|
+
end
|
179
|
+
|
180
|
+
migration = MigrationProxy.new
|
181
|
+
migration.name = to_camel name
|
182
|
+
migration.version = version
|
183
|
+
migration.filename = file
|
184
|
+
klasses << migration
|
185
|
+
end
|
186
|
+
|
187
|
+
migrations = migrations.sort_by { |m| m.version }
|
188
|
+
down? ? migrations.reverse : migrations
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def pending_migrations
|
193
|
+
already_migrated = migrated
|
194
|
+
migrations.reject { |m| already_migrated.include?(m.version.to_i) }
|
195
|
+
end
|
196
|
+
|
197
|
+
def migrated
|
198
|
+
@migrated_versions ||= self.class.get_all_versions
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def record_version_state_after_migrating(migration)
|
204
|
+
cas = ActiveColumn.connection
|
205
|
+
sm_cf = self.class.schema_migrations_column_family
|
206
|
+
|
207
|
+
@migrated_versions ||= []
|
208
|
+
if down?
|
209
|
+
@migrated_versions.delete(migration.version)
|
210
|
+
cas.remove sm_cf, 'all', migration.version
|
211
|
+
else
|
212
|
+
@migrated_versions.push(migration.version).sort!
|
213
|
+
cas.insert sm_cf, 'all', { migration.version => migration.name }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def up?
|
218
|
+
@direction == :up
|
219
|
+
end
|
220
|
+
|
221
|
+
def down?
|
222
|
+
@direction == :down
|
223
|
+
end
|
224
|
+
|
225
|
+
def to_camel(lower_case_and_underscored_word)
|
226
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|